Linux-2.6.12-rc2

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

Let it rip!
diff --git a/sound/oss/dmasound/Kconfig b/sound/oss/dmasound/Kconfig
new file mode 100644
index 0000000..cb84558
--- /dev/null
+++ b/sound/oss/dmasound/Kconfig
@@ -0,0 +1,58 @@
+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_PMAC
+	tristate "PowerMac DMA sound support"
+	depends on PPC32 && PPC_PMAC && SOUND && I2C
+ 	select DMASOUND
+	help
+	  If you want to use the internal audio of your PowerMac 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 || APUS) && 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
diff --git a/sound/oss/dmasound/Makefile b/sound/oss/dmasound/Makefile
new file mode 100644
index 0000000..4611636
--- /dev/null
+++ b/sound/oss/dmasound/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for the DMA sound driver
+#
+
+dmasound_pmac-y			+= dmasound_awacs.o \
+				   trans_16.o dac3550a.o tas_common.o \
+				   tas3001c.o tas3001c_tables.o \
+				   tas3004.o tas3004_tables.o
+
+obj-$(CONFIG_DMASOUND_ATARI)	+= dmasound_core.o dmasound_atari.o
+obj-$(CONFIG_DMASOUND_PMAC)	+= dmasound_core.o dmasound_pmac.o
+obj-$(CONFIG_DMASOUND_PAULA)	+= dmasound_core.o dmasound_paula.o
+obj-$(CONFIG_DMASOUND_Q40)	+= dmasound_core.o dmasound_q40.o
diff --git a/sound/oss/dmasound/awacs_defs.h b/sound/oss/dmasound/awacs_defs.h
new file mode 100644
index 0000000..2194f46
--- /dev/null
+++ b/sound/oss/dmasound/awacs_defs.h
@@ -0,0 +1,251 @@
+/*********************************************************/
+/* This file was written by someone, somewhere, sometime */
+/* And is released into the Public Domain                */
+/*********************************************************/
+
+#ifndef _AWACS_DEFS_H_
+#define _AWACS_DEFS_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 GAINRIGHT(x)	((x) & MASK_GAINRIGHT)
+#define GAINLEFT(x)	(((x) << 4) & MASK_GAINLEFT)
+
+#define DEF_CD_GAIN 0x00bb
+#define DEF_MIC_GAIN 0x00cc
+
+/* 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 MASK_CMUTE	(0x1 << 7)	/* Output C (Speaker) Mute when 1 */
+#define MASK_SPKMUTE	MASK_CMUTE
+#define MASK_ADDR1RES2	(0x1 << 8)	/* Reserved */
+#define MASK_AMUTE	(0x1 << 9)	/* Output A (Headphone) Mute when 1 */
+#define MASK_HDMUTE	MASK_AMUTE
+#define MASK_PAROUT0	(0x1 << 10)	/* Parallel Output 0 */
+#define MASK_PAROUT1	(0x2 << 10)	/* Parallel Output 1 */
+
+#define MASK_MIC_BOOST  (0x4)           /* screamer mic boost */
+
+#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)
+
+/* 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_INPPORT	(0xf)		/* Input Port */
+#define MASK_HDPCONN	8		/* headphone 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. */
+
+/*******************/
+/* Burgundy values */
+/*******************/
+
+#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_OUTPUTSELECTS (0x2B << 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_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 (0x0A)
+
+#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 */
+#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 (0x0A)
+
+#define DEF_BURGUNDY_MASTER_VOLUME (0xFFFFFFFF)
+
+#define DEF_BURGUNDY_MORE_OUTPUTENABLES (0x7E)
+
+#define DEF_BURGUNDY_ATTENSPEAKER (0x44)
+#define DEF_BURGUNDY_ATTENLINEOUT (0xCC)
+#define DEF_BURGUNDY_ATTENHP (0xCC)
+
+/*********************/
+/* i2s layout values */
+/*********************/
+
+#define I2S_REG_INT_CTL			0x00
+#define I2S_REG_SERIAL_FORMAT		0x10
+#define I2S_REG_CODEC_MSG_OUT		0x20
+#define I2S_REG_CODEC_MSG_IN		0x30
+#define I2S_REG_FRAME_COUNT		0x40
+#define I2S_REG_FRAME_MATCH		0x50
+#define I2S_REG_DATAWORD_SIZES		0x60
+#define I2S_REG_PEAKLEVEL_SEL		0x70
+#define I2S_REG_PEAKLEVEL_IN0		0x80
+#define I2S_REG_PEAKLEVEL_IN1		0x90
+
+#endif /* _AWACS_DEFS_H_ */
diff --git a/sound/oss/dmasound/dac3550a.c b/sound/oss/dmasound/dac3550a.c
new file mode 100644
index 0000000..533895e
--- /dev/null
+++ b/sound/oss/dmasound/dac3550a.c
@@ -0,0 +1,210 @@
+/*
+ * Driver for the i2c/i2s based DAC3550a sound chip used
+ * on some Apple iBooks. Also known as "DACA".
+ *
+ *  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/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/ioport.h>
+#include <linux/sysctl.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+
+#include "dmasound.h"
+
+/* FYI: This code was derived from the tas3001c.c Texas/Tumbler mixer
+ * control code, as well as info derived from the AppleDACAAudio driver
+ * from Darwin CVS (main thing I derived being register numbers and 
+ * values, as well as when to make the calls). */
+
+#define I2C_DRIVERID_DACA (0xFDCB)
+
+#define DACA_VERSION	"0.1"
+#define DACA_DATE "20010930"
+
+static int cur_left_vol;
+static int cur_right_vol;
+static struct i2c_client *daca_client;
+
+static int daca_attach_adapter(struct i2c_adapter *adapter);
+static int daca_detect_client(struct i2c_adapter *adapter, int address);
+static int daca_detach_client(struct i2c_client *client);
+
+struct i2c_driver daca_driver = {  
+	.owner			= THIS_MODULE,
+	.name			= "DAC3550A driver  V " DACA_VERSION,
+	.id			= I2C_DRIVERID_DACA,
+	.flags			= I2C_DF_NOTIFY,
+	.attach_adapter		= daca_attach_adapter,
+	.detach_client		= daca_detach_client,
+};
+
+#define VOL_MAX ((1<<20) - 1)
+
+void daca_get_volume(uint * left_vol, uint  *right_vol)
+{
+	*left_vol = cur_left_vol >> 5;
+	*right_vol = cur_right_vol >> 5;
+}
+
+int daca_set_volume(uint left_vol, uint right_vol)
+{
+	unsigned short voldata;
+  
+	if (!daca_client)
+		return -1;
+
+	/* Derived from experience, not from any specific values */
+	left_vol <<= 5;
+	right_vol <<= 5;
+
+	if (left_vol > VOL_MAX)
+		left_vol = VOL_MAX;
+	if (right_vol > VOL_MAX)
+		right_vol = VOL_MAX;
+
+	voldata = ((left_vol >> 14)  & 0x3f) << 8;
+	voldata |= (right_vol >> 14)  & 0x3f;
+  
+	if (i2c_smbus_write_word_data(daca_client, 2, voldata) < 0) {
+		printk("daca: failed to set volume \n");
+		return -1; 
+	}
+
+	cur_left_vol = left_vol;
+	cur_right_vol = right_vol;
+  
+	return 0;
+}
+
+int daca_leave_sleep(void)
+{
+	if (!daca_client)
+		return -1;
+  
+	/* Do a short sleep, just to make sure I2C bus is awake and paying
+	 * attention to us
+	 */
+	msleep(20);
+	/* Write the sample rate reg the value it needs */
+	i2c_smbus_write_byte_data(daca_client, 1, 8);
+	daca_set_volume(cur_left_vol >> 5, cur_right_vol >> 5);
+	/* Another short delay, just to make sure the other I2C bus writes
+	 * have taken...
+	 */
+	msleep(20);
+	/* Write the global config reg - invert right power amp,
+	 * DAC on, use 5-volt mode */
+	i2c_smbus_write_byte_data(daca_client, 3, 0x45);
+
+	return 0;
+}
+
+int daca_enter_sleep(void)
+{
+	if (!daca_client)
+		return -1;
+
+	i2c_smbus_write_byte_data(daca_client, 1, 8);
+	daca_set_volume(cur_left_vol >> 5, cur_right_vol >> 5);
+
+	/* Write the global config reg - invert right power amp,
+	 * DAC on, enter low-power mode, use 5-volt mode
+	 */
+	i2c_smbus_write_byte_data(daca_client, 3, 0x65);
+
+	return 0;
+}
+
+static int daca_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!strncmp(adapter->name, "mac-io", 6))
+		daca_detect_client(adapter, 0x4d);
+	return 0;
+}
+
+static int daca_init_client(struct i2c_client * new_client)
+{
+	/* 
+	 * Probe is not working with the current i2c-keywest
+	 * driver. We try to use addr 0x4d on each adapters
+	 * instead, by setting the format register.
+	 * 
+	 * FIXME: I'm sure that can be obtained from the
+	 * device-tree. --BenH.
+	 */
+  
+	/* Write the global config reg - invert right power amp,
+	 * DAC on, use 5-volt mode
+	 */
+	if (i2c_smbus_write_byte_data(new_client, 3, 0x45))
+		return -1;
+
+	i2c_smbus_write_byte_data(new_client, 1, 8);
+	daca_client = new_client;
+	daca_set_volume(15000, 15000);
+
+	return 0;
+}
+
+static int daca_detect_client(struct i2c_adapter *adapter, int address)
+{
+	const char *client_name = "DAC 3550A Digital Equalizer";
+	struct i2c_client *new_client;
+	int rc = -ENODEV;
+
+	new_client = kmalloc(sizeof(*new_client), GFP_KERNEL);
+	if (!new_client)
+		return -ENOMEM;
+	memset(new_client, 0, sizeof(*new_client));
+
+	new_client->addr = address;
+	new_client->adapter = adapter;
+	new_client->driver = &daca_driver;
+	new_client->flags = 0;
+	strcpy(new_client->name, client_name);
+
+	if (daca_init_client(new_client))
+		goto bail;
+
+	/* Tell the i2c layer a new client has arrived */
+	if (i2c_attach_client(new_client))
+		goto bail;
+
+	return 0;
+ bail:
+	kfree(new_client);
+	return rc;
+}
+
+
+static int daca_detach_client(struct i2c_client *client)
+{
+	if (client == daca_client)
+		daca_client = NULL;
+
+  	i2c_detach_client(client);
+	kfree(client);
+	return 0;
+}
+
+void daca_cleanup(void)
+{
+	i2c_del_driver(&daca_driver);
+}
+
+int daca_init(void)
+{
+	printk("dac3550a driver version %s (%s)\n",DACA_VERSION,DACA_DATE);
+	return i2c_add_driver(&daca_driver);
+}
diff --git a/sound/oss/dmasound/dmasound.h b/sound/oss/dmasound/dmasound.h
new file mode 100644
index 0000000..9a2f50f
--- /dev/null
+++ b/sound/oss/dmasound/dmasound.h
@@ -0,0 +1,277 @@
+#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>
+#include <linux/config.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
+#undef HAS_RECORD
+
+#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)
+
+#if defined(CONFIG_DMASOUND_PMAC) || defined(CONFIG_DMASOUND_PMAC_MODULE)
+#define HAS_RECORD
+#endif
+
+    /*
+     *  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, int);
+    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)(mode_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 */
+#ifdef HAS_RECORD
+    TRANS *trans_read;	/* supported translations */
+#endif
+    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 open_mode;
+    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
+
+#ifdef HAS_RECORD
+extern struct sound_queue dmasound_read_sq;
+#define read_sq		dmasound_read_sq
+#endif
+
+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_bal;	/* Balance factor for expanding (not volume!) */
+extern int expand_read_bal;	/* Balance factor for reading */
+extern uint software_input_volume; /* software implemented recording volume! */
+
+#endif /* _dmasound_h_ */
diff --git a/sound/oss/dmasound/dmasound_atari.c b/sound/oss/dmasound/dmasound_atari.c
new file mode 100644
index 0000000..8daaf87
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_atari.c
@@ -0,0 +1,1600 @@
+/*
+ *  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 *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft);
+static ssize_t ata_ct_s8(const u_char *userPtr, size_t userCount,
+			 u_char frame[], ssize_t *frameUsed,
+			 ssize_t frameLeft);
+static ssize_t ata_ct_u8(const u_char *userPtr, size_t userCount,
+			 u_char frame[], ssize_t *frameUsed,
+			 ssize_t frameLeft);
+static ssize_t ata_ct_s16be(const u_char *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t ata_ct_u16be(const u_char *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t ata_ct_s16le(const u_char *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t ata_ct_u16le(const u_char *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t ata_ctx_law(const u_char *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+static ssize_t ata_ctx_s8(const u_char *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft);
+static ssize_t ata_ctx_u8(const u_char *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft);
+static ssize_t ata_ctx_s16be(const u_char *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft);
+static ssize_t ata_ctx_u16be(const u_char *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft);
+static ssize_t ata_ctx_s16le(const u_char *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft);
+static ssize_t ata_ctx_u16le(const u_char *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft);
+
+
+/*** Low level stuff *********************************************************/
+
+
+static void *AtaAlloc(unsigned int size, int 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, struct pt_regs *fp);
+
+/*** 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(mode_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 *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 *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 *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 *)userPtr)++))
+				return -EFAULT;
+			*p++ = data ^ 0x8080;
+			count--;
+		}
+	}
+	*frameUsed += used;
+	return used;
+}
+
+
+static ssize_t ata_ct_s16be(const u_char *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 *)userPtr)++))
+				return -EFAULT;
+			*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 *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 *)userPtr)++))
+				return -EFAULT;
+			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_long data;
+			if (get_user(data, ((u_int *)userPtr)++))
+				return -EFAULT;
+			*p++ = data ^ 0x80008000;
+			count--;
+		}
+		*frameUsed += used;
+	}
+	return used;
+}
+
+
+static ssize_t ata_ct_s16le(const u_char *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 *)userPtr)++))
+				return -EFAULT;
+			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 *)userPtr)++))
+				return -EFAULT;
+			data = le2be16dbl(data);
+			*p++ = data;
+			count--;
+		}
+		*frameUsed += used;
+	}
+	return used;
+}
+
+
+static ssize_t ata_ct_u16le(const u_char *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 *)userPtr)++))
+				return -EFAULT;
+			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 *)userPtr)++))
+				return -EFAULT;
+			data = le2be16dbl(data) ^ 0x80008000;
+			*p++ = data;
+			count--;
+		}
+		*frameUsed += used;
+	}
+	return used;
+}
+
+
+static ssize_t ata_ctx_law(const u_char *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 *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 *)userPtr)++))
+					return -EFAULT;
+				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 *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 *)userPtr)++))
+					return -EFAULT;
+				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 *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 *)userPtr)++))
+					return -EFAULT;
+				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 *)userPtr)++))
+					return -EFAULT;
+				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 *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 *)userPtr)++))
+					return -EFAULT;
+				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 *)userPtr)++))
+					return -EFAULT;
+				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 *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 *)userPtr)++))
+					return -EFAULT;
+				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 *)userPtr)++))
+					return -EFAULT;
+				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 *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 *)userPtr)++))
+					return -EFAULT;
+				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 *)userPtr)++))
+					return -EFAULT;
+				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, int 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. */
+	mfp.tim_ct_a = 0;	/* ++roman: Stop timer before programming! */
+	mfp.tim_dt_a = 1;	/* Cause interrupt after first event. */
+	mfp.tim_ct_a = 8;	/* Turn on event counting. */
+	/* Register interrupt handler. */
+	request_irq(IRQ_MFP_TIMA, AtaInterrupt, IRQ_TYPE_SLOW, "DMA sound",
+		    AtaInterrupt);
+	mfp.int_en_a |= 0x20;	/* Turn interrupt on. */
+	mfp.int_mk_a |= 0x20;
+	return 1;
+}
+
+#ifdef MODULE
+static void AtaIrqCleanUp(void)
+{
+	mfp.tim_ct_a = 0;	/* stop timer */
+	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, struct pt_regs *fp)
+{
+#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;
+		return IRQ_HANDLED;
+	}
+
+	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;
+	}
+
+	/* 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. */
+	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(mode_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 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 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 ((mfp.int_en_a & 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/sound/oss/dmasound/dmasound_awacs.c b/sound/oss/dmasound/dmasound_awacs.c
new file mode 100644
index 0000000..5281b88
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_awacs.c
@@ -0,0 +1,3176 @@
+/*
+ *  linux/sound/oss/dmasound/dmasound_awacs.c
+ *
+ *  PowerMac `AWACS' and `Burgundy' DMA Sound Driver
+ *  with some limited support for DACA & Tumbler
+ *
+ *  See linux/sound/oss/dmasound/dmasound_core.c for copyright and
+ *  history prior to 2001/01/26.
+ *
+ *	26/01/2001 ed 0.1 Iain Sandoe
+ *		- added version info.
+ *		- moved dbdma command buffer allocation to PMacXXXSqSetup()
+ *		- fixed up beep dbdma cmd buffers
+ *
+ *	08/02/2001 [0.2]
+ *		- make SNDCTL_DSP_GETFMTS return the correct info for the h/w
+ *		- move soft format translations to a separate file
+ *		- [0.3] make SNDCTL_DSP_GETCAPS return correct info.
+ *		- [0.4] more informative machine name strings.
+ *		- [0.5]
+ *		- record changes.
+ *		- made the default_hard/soft entries.
+ *	04/04/2001 [0.6]
+ *		- minor correction to bit assignments in awacs_defs.h
+ *		- incorporate mixer changes from 2.2.x back-port.
+ *		- take out passthru as a rec input (it isn't).
+ *              - make Input Gain slider work the 'right way up'.
+ *              - try to make the mixer sliders more logical - so now the
+ *                input selectors are just two-state (>50% == ON) and the
+ *                Input Gain slider handles the rest of the gain issues.
+ *              - try to pick slider representations that most closely match
+ *                the actual use - e.g. IGain for input gain... 
+ *              - first stab at over/under-run detection.
+ *		- minor cosmetic changes to IRQ identification.
+ *		- fix bug where rates > max would be reported as supported.
+ *              - first stab at over/under-run detection.
+ *              - make use of i2c for mixer settings conditional on perch
+ *                rather than cuda (some machines without perch have cuda).
+ *              - fix bug where TX stops when dbdma status comes up "DEAD"
+ *		  so far only reported on PowerComputing clones ... but.
+ *		- put in AWACS/Screamer register write timeouts.
+ *		- part way to partitioning the init() stuff
+ *		- first pass at 'tumbler' stuff (not support - just an attempt
+ *		  to allow the driver to load on new G4s).
+ *      01/02/2002 [0.7] - BenH
+ *	        - all sort of minor bits went in since the latest update, I
+ *	          bumped the version number for that reason
+ *
+ *      07/26/2002 [0.8] - BenH
+ *	        - More minor bits since last changelog (I should be more careful
+ *	          with those)
+ *	        - Support for snapper & better tumbler integration by Toby Sargeant
+ *	        - Headphone detect for scremer by Julien Blache
+ *	        - More tumbler fixed by Andreas Schwab
+ *	11/29/2003 [0.8.1] - Renzo Davoli (King Enzo)
+ *		- Support for Snapper line in
+ *		- snapper input resampling (for rates < 44100)
+ *		- software line gain control
+ */
+
+/* GENERAL FIXME/TODO: check that the assumptions about what is written to
+   mac-io is valid for DACA & Tumbler.
+
+   This driver is in bad need of a rewrite. The dbdma code has to be split,
+   some proper device-tree parsing code has to be written, etc...
+*/
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/soundcard.h>
+#include <linux/adb.h>
+#include <linux/nvram.h>
+#include <linux/tty.h>
+#include <linux/vt_kern.h>
+#include <linux/spinlock.h>
+#include <linux/kmod.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <asm/semaphore.h>
+#ifdef CONFIG_ADB_CUDA
+#include <linux/cuda.h>
+#endif
+#ifdef CONFIG_ADB_PMU
+#include <linux/pmu.h>
+#endif
+
+#include <linux/i2c-dev.h>
+
+#include <asm/uaccess.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/dbdma.h>
+#include <asm/pmac_feature.h>
+#include <asm/irq.h>
+#include <asm/nvram.h>
+
+#include "awacs_defs.h"
+#include "dmasound.h"
+#include "tas3001c.h"
+#include "tas3004.h"
+#include "tas_common.h"
+
+#define DMASOUND_AWACS_REVISION	0
+#define DMASOUND_AWACS_EDITION	7
+
+#define AWACS_SNAPPER   110	/* fake revision # for snapper */
+#define AWACS_BURGUNDY	100	/* fake revision # for burgundy */
+#define AWACS_TUMBLER    90	/* fake revision # for tumbler */
+#define AWACS_DACA	 80	/* fake revision # for daca (ibook) */
+#define AWACS_AWACS       2     /* holding revision for AWACS */
+#define AWACS_SCREAMER    3     /* holding revision for Screamer */
+/*
+ * Interrupt numbers and addresses, & info obtained from the device tree.
+ */
+static int awacs_irq, awacs_tx_irq, awacs_rx_irq;
+static volatile struct awacs_regs __iomem *awacs;
+static volatile u32 __iomem *i2s;
+static volatile struct dbdma_regs __iomem *awacs_txdma, *awacs_rxdma;
+static int awacs_rate_index;
+static int awacs_subframe;
+static struct device_node* awacs_node;
+static struct device_node* i2s_node;
+
+static char awacs_name[64];
+static int awacs_revision;
+static int awacs_sleeping;
+static DECLARE_MUTEX(dmasound_sem);
+
+static int sound_device_id;		/* exists after iMac revA */
+static int hw_can_byteswap = 1 ;	/* most pmac sound h/w can */
+
+/* model info */
+/* To be replaced with better interaction with pmac_feature.c */
+static int is_pbook_3X00;
+static int is_pbook_g3;
+
+/* expansion info */
+static int has_perch;
+static int has_ziva;
+
+/* for earlier powerbooks which need fiddling with mac-io to enable
+ * cd etc.
+*/
+static unsigned char __iomem *latch_base;
+static unsigned char __iomem *macio_base;
+
+/*
+ * Space for the DBDMA command blocks.
+ */
+static void *awacs_tx_cmd_space;
+static volatile struct dbdma_cmd *awacs_tx_cmds;
+static int number_of_tx_cmd_buffers;
+
+static void *awacs_rx_cmd_space;
+static volatile struct dbdma_cmd *awacs_rx_cmds;
+static int number_of_rx_cmd_buffers;
+
+/*
+ * Cached values of AWACS registers (we can't read them).
+ * Except on the burgundy (and screamer). XXX
+ */
+
+int awacs_reg[8];
+int awacs_reg1_save;
+
+/* tracking values for the mixer contents
+*/
+
+static int spk_vol;
+static int line_vol;
+static int passthru_vol;
+
+static int ip_gain;           /* mic preamp settings */
+static int rec_lev = 0x4545 ; /* default CD gain 69 % */
+static int mic_lev;
+static int cd_lev = 0x6363 ; /* 99 % */
+static int line_lev;
+
+static int hdp_connected;
+
+/*
+ * 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,
+};
+
+/* beep support */
+#define BEEP_SRATE	22050	/* 22050 Hz sample rate */
+#define BEEP_BUFLEN	512
+#define BEEP_VOLUME	15	/* 0 - 100 */
+
+static int beep_vol = BEEP_VOLUME;
+static int beep_playing;
+static int awacs_beep_state;
+static short *beep_buf;
+static void *beep_dbdma_cmd_space;
+static volatile struct dbdma_cmd *beep_dbdma_cmd;
+
+/* Burgundy functions */
+static void awacs_burgundy_wcw(unsigned addr,unsigned newval);
+static unsigned awacs_burgundy_rcw(unsigned addr);
+static void awacs_burgundy_write_volume(unsigned address, int volume);
+static int awacs_burgundy_read_volume(unsigned address);
+static void awacs_burgundy_write_mvolume(unsigned address, int volume);
+static int awacs_burgundy_read_mvolume(unsigned address);
+
+/* 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
+   if appeared elsewhere.
+
+   for the sake of memory efficiency we'll allocate this cmd as part of
+   the beep cmd stuff.
+*/
+
+static volatile struct dbdma_cmd *emergency_dbdma_cmd;
+
+#ifdef CONFIG_PMAC_PBOOK
+/*
+ * Stuff for restoring after a sleep.
+ */
+static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when);
+struct pmu_sleep_notifier awacs_sleep_notifier = {
+	awacs_sleep_notify, SLEEP_LEVEL_SOUND,
+};
+#endif /* CONFIG_PMAC_PBOOK */
+
+/* for (soft) sample rate translations */
+int expand_bal;		/* Balance factor for expanding (not volume!) */
+int expand_read_bal;	/* Balance factor for expanding reads (not volume!) */
+
+/*** Low level stuff *********************************************************/
+
+static void *PMacAlloc(unsigned int size, int flags);
+static void PMacFree(void *ptr, unsigned int size);
+static int PMacIrqInit(void);
+#ifdef MODULE
+static void PMacIrqCleanup(void);
+#endif
+static void PMacSilence(void);
+static void PMacInit(void);
+static int PMacSetFormat(int format);
+static int PMacSetVolume(int volume);
+static void PMacPlay(void);
+static void PMacRecord(void);
+static irqreturn_t pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs);
+static irqreturn_t pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs);
+static irqreturn_t pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs);
+static void awacs_write(int val);
+static int awacs_get_volume(int reg, int lshift);
+static int awacs_volume_setter(int volume, int n, int mute, int lshift);
+
+
+/*** Mid level stuff **********************************************************/
+
+static int PMacMixerIoctl(u_int cmd, u_long arg);
+static int PMacWriteSqSetup(void);
+static int PMacReadSqSetup(void);
+static void PMacAbortRead(void);
+
+extern TRANS transAwacsNormal ;
+extern TRANS transAwacsExpand ;
+extern TRANS transAwacsNormalRead ;
+extern TRANS transAwacsExpandRead ;
+
+extern int daca_init(void);
+extern void daca_cleanup(void);
+extern int daca_set_volume(uint left_vol, uint right_vol);
+extern void daca_get_volume(uint * left_vol, uint  *right_vol);
+extern int daca_enter_sleep(void);
+extern int daca_leave_sleep(void);
+
+#define TRY_LOCK()	\
+	if ((rc = down_interruptible(&dmasound_sem)) != 0)	\
+		return rc;
+#define LOCK()		down(&dmasound_sem);
+
+#define UNLOCK()	up(&dmasound_sem);
+
+/* We use different versions that the ones provided in dmasound.h
+ * 
+ * FIXME: Use different names ;)
+ */
+#undef IOCTL_IN
+#undef IOCTL_OUT
+
+#define IOCTL_IN(arg, ret)	\
+	rc = get_user(ret, (int __user *)(arg)); \
+	if (rc) break;
+#define IOCTL_OUT(arg, ret)	\
+	ioctl_return2((int __user *)(arg), ret)
+
+static inline int ioctl_return2(int __user *addr, int value)
+{
+	return value < 0 ? value : put_user(value, addr);
+}
+
+
+/*** AE - TUMBLER / SNAPPER START ************************************************/
+
+
+int gpio_audio_reset, gpio_audio_reset_pol;
+int gpio_amp_mute, gpio_amp_mute_pol;
+int gpio_headphone_mute, gpio_headphone_mute_pol;
+int gpio_headphone_detect, gpio_headphone_detect_pol;
+int gpio_headphone_irq;
+
+int
+setup_audio_gpio(const char *name, const char* compatible, int *gpio_addr, int* gpio_pol)
+{
+	struct device_node *np;
+	u32* pp;
+	
+	np = find_devices("gpio");
+	if (!np)
+		return -ENODEV;
+
+	np = np->child;
+	while(np != 0) {
+		if (name) {
+			char *property = get_property(np,"audio-gpio",NULL);
+			if (property != 0 && strcmp(property,name) == 0)
+				break;
+		} else if (compatible && device_is_compatible(np, compatible))
+			break;
+		np = np->sibling;
+	}
+	if (!np)
+		return -ENODEV;
+	pp = (u32 *)get_property(np, "AAPL,address", NULL);
+	if (!pp)
+		return -ENODEV;
+	*gpio_addr = (*pp) & 0x0000ffff;
+	pp = (u32 *)get_property(np, "audio-gpio-active-state", NULL);
+	if (pp)
+		*gpio_pol = *pp;
+	else
+		*gpio_pol = 1;
+	if (np->n_intrs > 0)
+		return np->intrs[0].line;
+	
+	return 0;
+}
+
+static inline void
+write_audio_gpio(int gpio_addr, int data)
+{
+	if (!gpio_addr)
+		return;
+	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio_addr, data ? 0x05 : 0x04);
+}
+
+static inline int
+read_audio_gpio(int gpio_addr)
+{
+	if (!gpio_addr)
+		return 0;
+	return ((pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_addr, 0) & 0x02) !=0);
+}
+
+/*
+ * Headphone interrupt via GPIO (Tumbler, Snapper, DACA)
+ */
+static irqreturn_t
+headphone_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dmasound.lock, flags);
+	if (read_audio_gpio(gpio_headphone_detect) == gpio_headphone_detect_pol) {
+		printk(KERN_INFO "Audio jack plugged, muting speakers.\n");
+		write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol);
+		write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol);
+		tas_output_device_change(sound_device_id,TAS_OUTPUT_HEADPHONES,0);
+	} else {
+		printk(KERN_INFO "Audio jack unplugged, enabling speakers.\n");
+		write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol);
+		write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol);
+		tas_output_device_change(sound_device_id,TAS_OUTPUT_INTERNAL_SPKR,0);
+	}
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+	return IRQ_HANDLED;
+}
+
+
+/* Initialize tumbler */
+
+static int
+tas_dmasound_init(void)
+{
+	setup_audio_gpio(
+		"audio-hw-reset",
+		NULL,
+		&gpio_audio_reset,
+		&gpio_audio_reset_pol);
+	setup_audio_gpio(
+		"amp-mute",
+		NULL,
+		&gpio_amp_mute,
+		&gpio_amp_mute_pol);
+	setup_audio_gpio("headphone-mute",
+		NULL,
+		&gpio_headphone_mute,
+		&gpio_headphone_mute_pol);
+	gpio_headphone_irq = setup_audio_gpio(
+		"headphone-detect",
+		NULL,
+		&gpio_headphone_detect,
+		&gpio_headphone_detect_pol);
+	/* Fix some broken OF entries in desktop machines */
+	if (!gpio_headphone_irq)
+		gpio_headphone_irq = setup_audio_gpio(
+			NULL,
+			"keywest-gpio15",
+			&gpio_headphone_detect,
+			&gpio_headphone_detect_pol);
+
+	write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol);
+	msleep(100);
+	write_audio_gpio(gpio_audio_reset, !gpio_audio_reset_pol);
+	msleep(100);
+  	if (gpio_headphone_irq) {
+		if (request_irq(gpio_headphone_irq,headphone_intr,0,"Headphone detect",NULL) < 0) {
+    			printk(KERN_ERR "tumbler: Can't request headphone interrupt\n");
+    			gpio_headphone_irq = 0;
+    		} else {
+			u8 val;
+			/* Activate headphone status interrupts */
+			val = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_headphone_detect, 0);
+			pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio_headphone_detect, val | 0x80);
+			/* Trigger it */
+  			headphone_intr(0,NULL,NULL);
+  		}
+  	}
+  	if (!gpio_headphone_irq) {
+  		/* Some machine enter this case ? */
+  		printk(KERN_WARNING "tumbler: Headphone detect IRQ not found, enabling all outputs !\n");
+  		write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol);
+  		write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol);
+  	}
+	return 0;
+}
+
+
+static int
+tas_dmasound_cleanup(void)
+{
+	if (gpio_headphone_irq)
+		free_irq(gpio_headphone_irq, NULL);
+	return 0;
+}
+
+/* We don't support 48k yet */
+static int tas_freqs[1] = { 44100 } ;
+static int tas_freqs_ok[1] = { 1 } ;
+
+/* don't know what to do really - just have to leave it where
+ * OF left things
+*/
+
+static int
+tas_set_frame_rate(void)
+{
+	if (i2s) {
+		out_le32(i2s + (I2S_REG_SERIAL_FORMAT >> 2), 0x41190000);
+		out_le32(i2s + (I2S_REG_DATAWORD_SIZES >> 2), 0x02000200);
+	}
+	dmasound.hard.speed = 44100 ;
+	awacs_rate_index = 0 ;
+	return 44100 ;
+}
+
+static int
+tas_mixer_ioctl(u_int cmd, u_long arg)
+{
+	int __user *argp = (int __user *)arg;
+	int data;
+	int rc;
+
+        rc=tas_device_ioctl(cmd, arg);
+        if (rc != -EINVAL) {
+        	return rc;
+        }
+
+        if ((cmd & ~0xff) == MIXER_WRITE(0) &&
+            tas_supported_mixers() & (1<<(cmd & 0xff))) {
+		rc = get_user(data, argp);
+                if (rc<0) return rc;
+		tas_set_mixer_level(cmd & 0xff, data);
+		tas_get_mixer_level(cmd & 0xff, &data);
+		return ioctl_return2(argp, data);
+        }
+        if ((cmd & ~0xff) == MIXER_READ(0) &&
+            tas_supported_mixers() & (1<<(cmd & 0xff))) {
+		tas_get_mixer_level(cmd & 0xff, &data);
+		return ioctl_return2(argp, data);
+        }
+
+	switch(cmd) {
+	case SOUND_MIXER_READ_DEVMASK:
+		data = tas_supported_mixers() | SOUND_MASK_SPEAKER;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_STEREODEVS:
+		data = tas_stereo_mixers();
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_CAPS:
+		rc = IOCTL_OUT(arg, 0);
+		break;
+	case SOUND_MIXER_READ_RECMASK:
+		// XXX FIXME: find a way to check what is really available */
+		data = SOUND_MASK_LINE | SOUND_MASK_MIC;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECSRC:
+		if (awacs_reg[0] & MASK_MUX_AUDIN)
+			data |= SOUND_MASK_LINE;
+		if (awacs_reg[0] & MASK_MUX_MIC)
+			data |= SOUND_MASK_MIC;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_RECSRC:
+ 		IOCTL_IN(arg, data);
+		data =0;
+ 		rc = IOCTL_OUT(arg, data);
+ 		break;
+	case SOUND_MIXER_WRITE_SPEAKER:	/* really bell volume */
+ 		IOCTL_IN(arg, data);
+ 		beep_vol = data & 0xff;
+ 		/* fall through */
+	case SOUND_MIXER_READ_SPEAKER:
+		rc = IOCTL_OUT(arg, (beep_vol<<8) | beep_vol);
+ 		break;
+	case SOUND_MIXER_OUTMASK:
+	case SOUND_MIXER_OUTSRC:
+	default:
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static void __init
+tas_init_frame_rates(unsigned int *prop, unsigned int l)
+{
+	int i ;
+	if (prop) {
+		for (i=0; i<1; i++)
+			tas_freqs_ok[i] = 0;
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			for (i = 0; i < 1; ++i) {
+				if (r == tas_freqs[i]) {
+					tas_freqs_ok[i] = 1;
+					break;
+				}
+			}
+		}
+	}
+	/* else we assume that all the rates are available */
+}
+
+
+/*** AE - TUMBLER / SNAPPER END ************************************************/
+
+
+
+/*** Low level stuff *********************************************************/
+
+/*
+ * PCI PowerMac, with AWACS, Screamer, Burgundy, DACA or Tumbler and DBDMA.
+ */
+static void *PMacAlloc(unsigned int size, int flags)
+{
+	return kmalloc(size, flags);
+}
+
+static void PMacFree(void *ptr, unsigned int size)
+{
+	kfree(ptr);
+}
+
+static int __init PMacIrqInit(void)
+{
+	if (awacs)
+		if (request_irq(awacs_irq, pmac_awacs_intr, 0, "Built-in Sound misc", NULL))
+			return 0;
+	if (request_irq(awacs_tx_irq, pmac_awacs_tx_intr, 0, "Built-in Sound out", NULL)
+	    || request_irq(awacs_rx_irq, pmac_awacs_rx_intr, 0, "Built-in Sound in", NULL))
+		return 0;
+	return 1;
+}
+
+#ifdef MODULE
+static void PMacIrqCleanup(void)
+{
+	/* turn off input & output dma */
+	DBDMA_DO_STOP(awacs_txdma);
+	DBDMA_DO_STOP(awacs_rxdma);
+
+	if (awacs)
+		/* disable interrupts from awacs interface */
+		out_le32(&awacs->control, in_le32(&awacs->control) & 0xfff);
+	
+	/* Switch off the sound clock */
+	pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0);
+	/* Make sure proper bits are set on pismo & tipb */
+	if ((machine_is_compatible("PowerBook3,1") ||
+	    machine_is_compatible("PowerBook3,2")) && awacs) {
+		awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1;
+		awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		msleep(200);
+	}
+	if (awacs)
+		free_irq(awacs_irq, NULL);
+	free_irq(awacs_tx_irq, NULL);
+	free_irq(awacs_rx_irq, NULL);
+	
+	if (awacs)
+		iounmap(awacs);
+	if (i2s)
+		iounmap(i2s);
+	iounmap(awacs_txdma);
+	iounmap(awacs_rxdma);
+
+	release_OF_resource(awacs_node, 0);
+	release_OF_resource(awacs_node, 1);
+	release_OF_resource(awacs_node, 2);
+
+	if (awacs_tx_cmd_space)
+		kfree(awacs_tx_cmd_space);
+	if (awacs_rx_cmd_space)
+		kfree(awacs_rx_cmd_space);
+	if (beep_dbdma_cmd_space)
+		kfree(beep_dbdma_cmd_space);
+	if (beep_buf)
+		kfree(beep_buf);
+#ifdef CONFIG_PMAC_PBOOK
+	pmu_unregister_sleep_notifier(&awacs_sleep_notifier);
+#endif
+}
+#endif /* MODULE */
+
+static void PMacSilence(void)
+{
+	/* turn off output dma */
+	DBDMA_DO_STOP(awacs_txdma);
+}
+
+/* don't know what to do really - just have to leave it where
+ * OF left things
+*/
+
+static int daca_set_frame_rate(void)
+{
+	if (i2s) {
+		out_le32(i2s + (I2S_REG_SERIAL_FORMAT >> 2), 0x41190000);
+		out_le32(i2s + (I2S_REG_DATAWORD_SIZES >> 2), 0x02000200);
+	}
+	dmasound.hard.speed = 44100 ;
+	awacs_rate_index = 0 ;
+	return 44100 ;
+}
+
+static int awacs_freqs[8] = {
+	44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350
+};
+static int awacs_freqs_ok[8] = { 1, 1, 1, 1, 1, 1, 1, 1 };
+
+static int
+awacs_set_frame_rate(int desired, int catch_r)
+{
+	int tolerance, i = 8 ;
+	/*
+	 * If we have a sample rate which is within catchRadius percent
+	 * of the requested value, we don't have to expand the samples.
+	 * Otherwise choose the next higher rate.
+	 * N.B.: burgundy awacs only works at 44100 Hz.
+	 */
+	do {
+		tolerance = catch_r * awacs_freqs[--i] / 100;
+		if (awacs_freqs_ok[i]
+		    && dmasound.soft.speed <= awacs_freqs[i] + tolerance)
+			break;
+	} while (i > 0);
+	dmasound.hard.speed = awacs_freqs[i];
+	awacs_rate_index = i;
+
+	out_le32(&awacs->control, MASK_IEPC | (i << 8) | 0x11 );
+	awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) | (i << 3);
+	awacs_write(awacs_reg[1] | MASK_ADDR1);
+	return dmasound.hard.speed;
+}
+
+static int
+burgundy_set_frame_rate(void)
+{
+	awacs_rate_index = 0 ;
+	awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) ;
+	/* XXX disable error interrupt on burgundy for now */
+	out_le32(&awacs->control, MASK_IEPC | 0 | 0x11 | MASK_IEE);
+	return 44100 ;
+}
+
+static int
+set_frame_rate(int desired, int catch_r)
+{
+	switch (awacs_revision) {
+		case AWACS_BURGUNDY:
+			dmasound.hard.speed = burgundy_set_frame_rate();
+			break ;
+		case AWACS_TUMBLER:
+		case AWACS_SNAPPER:
+			dmasound.hard.speed = tas_set_frame_rate();
+			break ;
+		case AWACS_DACA:
+			dmasound.hard.speed =
+			  daca_set_frame_rate();
+			break ;
+		default:
+			dmasound.hard.speed = awacs_set_frame_rate(desired,
+						catch_r);
+			break ;
+	}
+	return dmasound.hard.speed ;
+}
+
+static void
+awacs_recalibrate(void)
+{
+	/* Sorry for the horrible delays... I hope to get that improved
+	 * by making the whole PM process asynchronous in a future version
+	 */
+	msleep(750);
+	awacs_reg[1] |= MASK_CMUTE | MASK_AMUTE;
+	awacs_write(awacs_reg[1] | MASK_RECALIBRATE | MASK_ADDR1);
+	msleep(1000);
+	awacs_write(awacs_reg[1] | MASK_ADDR1);
+}
+
+static void PMacInit(void)
+{
+	int tolerance;
+
+	switch (dmasound.soft.format) {
+	    case AFMT_S16_LE:
+	    case AFMT_U16_LE:
+		if (hw_can_byteswap)
+			dmasound.hard.format = AFMT_S16_LE;
+		else
+			dmasound.hard.format = AFMT_S16_BE;
+		break;
+	default:
+		dmasound.hard.format = AFMT_S16_BE;
+		break;
+	}
+	dmasound.hard.stereo = 1;
+	dmasound.hard.size = 16;
+
+	/* set dmasound.hard.speed - on the basis of what we want (soft)
+	 * and the tolerance we'll allow.
+	*/
+	set_frame_rate(dmasound.soft.speed, catchRadius) ;
+
+	tolerance = (catchRadius * dmasound.hard.speed) / 100;
+	if (dmasound.soft.speed >= dmasound.hard.speed - tolerance) {
+		dmasound.trans_write = &transAwacsNormal;
+		dmasound.trans_read = &transAwacsNormalRead;
+	} else {
+		dmasound.trans_write = &transAwacsExpand;
+		dmasound.trans_read = &transAwacsExpandRead;
+	}
+
+	if (awacs) {
+		if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
+			out_le32(&awacs->byteswap, BS_VAL);
+		else
+			out_le32(&awacs->byteswap, 0);
+	}
+	
+	expand_bal = -dmasound.soft.speed;
+	expand_read_bal = -dmasound.soft.speed;
+}
+
+static int PMacSetFormat(int format)
+{
+	int size;
+	int req_format = format;
+		
+	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_LE:
+		if(!hw_can_byteswap)
+			format = AFMT_S16_BE;
+	case AFMT_S16_BE:
+		size = 16;
+		break;
+	case AFMT_U16_LE:
+		if(!hw_can_byteswap)
+			format = AFMT_U16_BE;
+	case AFMT_U16_BE:
+		size = 16;
+		break;
+	default: /* :-) */
+		printk(KERN_ERR "dmasound: unknown format 0x%x, using AFMT_U8\n",
+		       format);
+		size = 8;
+		format = AFMT_U8;
+	}
+	
+	if (req_format == format) {
+		dmasound.soft.format = format;
+		dmasound.soft.size = size;
+		if (dmasound.minDev == SND_DEV_DSP) {
+			dmasound.dsp.format = format;
+			dmasound.dsp.size = size;
+		}
+	}
+
+	return format;
+}
+
+#define AWACS_VOLUME_TO_MASK(x)	(15 - ((((x) - 1) * 15) / 99))
+#define AWACS_MASK_TO_VOLUME(y)	(100 - ((y) * 99 / 15))
+
+static int awacs_get_volume(int reg, int lshift)
+{
+	int volume;
+
+	volume = AWACS_MASK_TO_VOLUME((reg >> lshift) & 0xf);
+	volume |= AWACS_MASK_TO_VOLUME(reg & 0xf) << 8;
+	return volume;
+}
+
+static int awacs_volume_setter(int volume, int n, int mute, int lshift)
+{
+	int r1, rn;
+
+	if (mute && volume == 0) {
+		r1 = awacs_reg[1] | mute;
+	} else {
+		r1 = awacs_reg[1] & ~mute;
+		rn = awacs_reg[n] & ~(0xf | (0xf << lshift));
+		rn |= ((AWACS_VOLUME_TO_MASK(volume & 0xff) & 0xf) << lshift);
+		rn |= AWACS_VOLUME_TO_MASK((volume >> 8) & 0xff) & 0xf;
+		awacs_reg[n] = rn;
+		awacs_write((n << 12) | rn);
+		volume = awacs_get_volume(rn, lshift);
+	}
+	if (r1 != awacs_reg[1]) {
+		awacs_reg[1] = r1;
+		awacs_write(r1 | MASK_ADDR1);
+	}
+	return volume;
+}
+
+static int PMacSetVolume(int volume)
+{
+	printk(KERN_WARNING "Bogus call to PMacSetVolume !\n");
+	return 0;
+}
+
+static void awacs_setup_for_beep(int speed)
+{
+	out_le32(&awacs->control,
+		 (in_le32(&awacs->control) & ~0x1f00)
+		 | ((speed > 0 ? speed : awacs_rate_index) << 8));
+
+	if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE) && speed == -1)
+		out_le32(&awacs->byteswap, BS_VAL);
+	else
+		out_le32(&awacs->byteswap, 0);
+}
+
+/* CHECK: how much of this *really* needs IRQs masked? */
+static void __PMacPlay(void)
+{
+	volatile struct dbdma_cmd *cp;
+	int next_frg, count;
+
+	count = 300 ; /* > two cycles at the lowest sample rate */
+
+	/* what we want to send next */
+	next_frg = (write_sq.front + write_sq.active) % write_sq.max_count;
+
+	if (awacs_beep_state) {
+		/* sound takes precedence over beeps */
+		/* stop the dma channel */
+		out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+		while ( (in_le32(&awacs_txdma->status) & RUN) && count--)
+			udelay(1);
+		if (awacs)
+			awacs_setup_for_beep(-1);
+		out_le32(&awacs_txdma->cmdptr,
+			 virt_to_bus(&(awacs_tx_cmds[next_frg])));
+
+		beep_playing = 0;
+		awacs_beep_state = 0;
+	}
+	/* this won't allow more than two frags to be in the output queue at
+	   once. (or one, if the max frags is 2 - because count can't exceed
+	   2 in that case)
+	*/
+	while (write_sq.active < 2 && write_sq.active < write_sq.count) {
+		count = (write_sq.count == write_sq.active + 1) ?
+				write_sq.rear_size:write_sq.block_size ;
+		if (count < write_sq.block_size) {
+			if (!write_sq.syncing) /* last block not yet filled,*/
+				break; 	/* and we're not syncing or POST-ed */
+			else {
+				/* pretend the block is full to force a new
+				   block to be started on the next write */
+				write_sq.rear_size = write_sq.block_size ;
+				write_sq.syncing &= ~2 ; /* clear POST */
+			}
+		}
+		cp = &awacs_tx_cmds[next_frg];
+		st_le16(&cp->req_count, count);
+		st_le16(&cp->xfer_status, 0);
+		st_le16(&cp->command, OUTPUT_MORE + INTR_ALWAYS);
+		/* put a STOP at the end of the queue - but only if we have
+		   space for it.  This means that, if we under-run and we only
+		   have two fragments, we might re-play sound from an existing
+		   queued frag.  I guess the solution to that is not to set two
+		   frags if you are likely to under-run...
+		*/
+		if (write_sq.count < write_sq.max_count) {
+			if (++next_frg >= write_sq.max_count)
+				next_frg = 0 ; /* wrap */
+			/* if we get here then we've underrun so we will stop*/
+			st_le16(&awacs_tx_cmds[next_frg].command, DBDMA_STOP);
+		}
+		/* set the dbdma controller going, if it is not already */
+		if (write_sq.active == 0)
+			out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp));
+		(void)in_le32(&awacs_txdma->status);
+		out_le32(&awacs_txdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE));
+		++write_sq.active;
+	}
+}
+
+static void PMacPlay(void)
+{
+	LOCK();
+	if (!awacs_sleeping) {
+		unsigned long flags;
+
+		spin_lock_irqsave(&dmasound.lock, flags);
+		__PMacPlay();
+		spin_unlock_irqrestore(&dmasound.lock, flags);
+	}
+	UNLOCK();
+}
+
+static void PMacRecord(void)
+{
+	unsigned long flags;
+
+	if (read_sq.active)
+		return;
+
+	spin_lock_irqsave(&dmasound.lock, flags);
+
+	/* This is all we have to do......Just start it up.
+	*/
+	out_le32(&awacs_rxdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE));
+	read_sq.active = 1;
+
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+}
+
+/* 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 too small a 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 irqreturn_t
+pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	int i = write_sq.front;
+	int stat;
+	int i_nowrap = write_sq.front;
+	volatile struct dbdma_cmd *cp;
+	/* != 0 when we are dealing with a DEAD xfer */
+	static int emergency_in_use;
+
+	spin_lock(&dmasound.lock);
+	while (write_sq.active > 0) { /* we expect to have done something*/
+		if (emergency_in_use) /* we are dealing with DEAD xfer */
+			cp = emergency_dbdma_cmd ;
+		else
+			cp = &awacs_tx_cmds[i];
+		stat = ld_le16(&cp->xfer_status);
+		if (stat & DEAD) {
+			unsigned short req, res ;
+			unsigned int phy ;
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: tx-irq: xfer died - patching it up...\n") ;
+#endif
+			/* to clear DEAD status we must first clear RUN
+			   set it to quiescent to be on the safe side */
+			(void)in_le32(&awacs_txdma->status);
+			out_le32(&awacs_txdma->control,
+				(RUN|PAUSE|FLUSH|WAKE) << 16);
+			write_sq.died++ ;
+			if (!emergency_in_use) { /* new problem */
+				memcpy((void *)emergency_dbdma_cmd, (void *)cp,
+					sizeof(struct dbdma_cmd));
+				emergency_in_use = 1;
+				cp = emergency_dbdma_cmd;
+			}
+			/* 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, virt_to_bus(&awacs_tx_cmds[(i+1)%write_sq.max_count]));
+			st_le16(&cp->command, OUTPUT_MORE | BR_ALWAYS | INTR_ALWAYS);
+			
+			/* point at our patched up command block */
+			out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp));
+			/* we must re-start the controller */
+			(void)in_le32(&awacs_txdma->status);
+			/* should complete clearing the DEAD status */
+			out_le32(&awacs_txdma->control,
+				((RUN|WAKE) << 16) + (RUN|WAKE));
+			break; /* this block is still going */
+		}
+		if ((stat & ACTIVE) == 0)
+			break;	/* this frame is still going */
+		if (emergency_in_use)
+			emergency_in_use = 0 ; /* done that */
+		--write_sq.count;
+		--write_sq.active;
+		i_nowrap++;
+		if (++i >= write_sq.max_count)
+			i = 0;
+	}
+
+	/* if we stopped and we were not sync-ing - then we under-ran */
+	if( write_sq.syncing == 0 ){
+		stat = in_le32(&awacs_txdma->status) ;
+		/* we hit the dbdma_stop */
+		if( (stat & ACTIVE) == 0 ) write_sq.xruns++ ;
+	}
+
+	/* if we used some data up then wake the writer to supply some more*/
+	if (i_nowrap != write_sq.front)
+		WAKE_UP(write_sq.action_queue);
+	write_sq.front = i;
+
+	/* but make sure we funnel what we've already got */\
+	 if (!awacs_sleeping)
+		__PMacPlay();
+
+	/* make the wake-on-empty conditional on syncing */
+	if (!write_sq.active && (write_sq.syncing & 1))
+		WAKE_UP(write_sq.sync_queue); /* any time we're empty */
+	spin_unlock(&dmasound.lock);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t
+pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	int stat ;
+	/* For some reason on my PowerBook G3, I get one interrupt
+	 * when the interrupt vector is installed (like something is
+	 * pending).  This happens before the dbdma is initialized by
+	 * us, so I just check the command pointer and if it is zero,
+	 * just blow it off.
+	 */
+	if (in_le32(&awacs_rxdma->cmdptr) == 0)
+		return IRQ_HANDLED;
+
+	/* We also want to blow 'em off when shutting down.
+	*/
+	if (read_sq.active == 0)
+		return IRQ_HANDLED;
+
+	spin_lock(&dmasound.lock);
+	/* Check multiple buffers in case we were held off from
+	 * interrupt processing for a long time.  Geeze, I really hope
+	 * this doesn't happen.
+	 */
+	while ((stat=awacs_rx_cmds[read_sq.rear].xfer_status)) {
+
+		/* if we got a "DEAD" status then just log it for now.
+		   and try to restart dma.
+		   TODO: figure out how best to fix it up
+		*/
+		if (stat & DEAD){
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: rx-irq: DIED - attempting resurection\n");
+#endif
+			/* to clear DEAD status we must first clear RUN
+			   set it to quiescent to be on the safe side */
+			(void)in_le32(&awacs_txdma->status);
+			out_le32(&awacs_txdma->control,
+				(RUN|PAUSE|FLUSH|WAKE) << 16);
+			awacs_rx_cmds[read_sq.rear].xfer_status = 0;
+			awacs_rx_cmds[read_sq.rear].res_count = 0;
+			read_sq.died++ ;
+			(void)in_le32(&awacs_txdma->status);
+			/* re-start the same block */
+			out_le32(&awacs_rxdma->cmdptr,
+				virt_to_bus(&awacs_rx_cmds[read_sq.rear]));
+			/* we must re-start the controller */
+			(void)in_le32(&awacs_rxdma->status);
+			/* should complete clearing the DEAD status */
+			out_le32(&awacs_rxdma->control,
+				((RUN|WAKE) << 16) + (RUN|WAKE));
+			spin_unlock(&dmasound.lock);
+			return IRQ_HANDLED; /* try this block again */
+		}
+		/* Clear status and move on to next buffer.
+		*/
+		awacs_rx_cmds[read_sq.rear].xfer_status = 0;
+		read_sq.rear++;
+
+		/* Wrap the buffer ring.
+		*/
+		if (read_sq.rear >= read_sq.max_active)
+			read_sq.rear = 0;
+
+		/* If we have caught up to the front buffer, bump it.
+		 * This will cause weird (but not fatal) results if the
+		 * read loop is currently using this buffer.  The user is
+		 * behind in this case anyway, so weird things are going
+		 * to happen.
+		 */
+		if (read_sq.rear == read_sq.front) {
+			read_sq.front++;
+			read_sq.xruns++ ; /* we overan */
+			if (read_sq.front >= read_sq.max_active)
+				read_sq.front = 0;
+		}
+	}
+
+	WAKE_UP(read_sq.action_queue);
+	spin_unlock(&dmasound.lock);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t
+pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	int ctrl;
+	int status;
+	int r1;
+
+	spin_lock(&dmasound.lock);
+	ctrl = in_le32(&awacs->control);
+	status = in_le32(&awacs->codec_stat);
+
+	if (ctrl & MASK_PORTCHG) {
+		/* tested on Screamer, should work on others too */
+		if (awacs_revision == AWACS_SCREAMER) {
+			if (((status & MASK_HDPCONN) >> 3) && (hdp_connected == 0)) {
+				hdp_connected = 1;
+				
+				r1 = awacs_reg[1] | MASK_SPKMUTE;
+				awacs_reg[1] = r1;
+				awacs_write(r1 | MASK_ADDR_MUTE);
+			} else if (((status & MASK_HDPCONN) >> 3 == 0) && (hdp_connected == 1)) {
+				hdp_connected = 0;
+				
+				r1 = awacs_reg[1] & ~MASK_SPKMUTE;
+				awacs_reg[1] = r1;
+				awacs_write(r1 | MASK_ADDR_MUTE);
+			}
+		}
+	}
+	if (ctrl & MASK_CNTLERR) {
+		int err = (in_le32(&awacs->codec_stat) & MASK_ERRCODE) >> 16;
+		/* CHECK: we just swallow burgundy errors at the moment..*/
+		if (err != 0 && awacs_revision != AWACS_BURGUNDY)
+			printk(KERN_ERR "dmasound_pmac: error %x\n", err);
+	}
+	/* Writing 1s to the CNTLERR and PORTCHG bits clears them... */
+	out_le32(&awacs->control, ctrl);
+	spin_unlock(&dmasound.lock);
+	return IRQ_HANDLED;
+}
+
+static void
+awacs_write(int val)
+{
+	int count = 300 ;
+	if (awacs_revision >= AWACS_DACA || !awacs)
+		return ;
+
+	while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--)
+		udelay(1) ;	/* timeout is > 2 samples at lowest rate */
+	out_le32(&awacs->codec_ctrl, val | (awacs_subframe << 22));
+	(void)in_le32(&awacs->byteswap);
+}
+
+/* this is called when the beep timer expires... it will be called even
+   if the beep has been overidden by other sound output.
+*/
+static void awacs_nosound(unsigned long xx)
+{
+	unsigned long flags;
+	int count = 600 ; /* > four samples at lowest rate */
+
+	spin_lock_irqsave(&dmasound.lock, flags);
+	if (beep_playing) {
+		st_le16(&beep_dbdma_cmd->command, DBDMA_STOP);
+		out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+		while ((in_le32(&awacs_txdma->status) & RUN) && count--)
+			udelay(1);
+		if (awacs)
+			awacs_setup_for_beep(-1);
+		beep_playing = 0;
+	}
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+}
+
+/*
+ * We generate the beep with a single dbdma command that loops a buffer
+ * forever - without generating interrupts.
+ *
+ * So, to stop it you have to stop dma output as per awacs_nosound.
+ */
+static int awacs_beep_event(struct input_dev *dev, unsigned int type,
+		unsigned int code, int hz)
+{
+	unsigned long flags;
+	int beep_speed = 0;
+	int srate;
+	int period, ncycles, nsamples;
+	int i, j, f;
+	short *p;
+	static int beep_hz_cache;
+	static int beep_nsamples_cache;
+	static int beep_volume_cache;
+
+	if (type != EV_SND)
+		return -1;
+	switch (code) {
+	case SND_BELL:
+		if (hz)
+			hz = 1000;
+		break;
+	case SND_TONE:
+		break;
+	default:
+		return -1;
+	}
+
+	if (beep_buf == NULL)
+		return -1;
+
+	/* quick-hack fix for DACA, Burgundy & Tumbler */
+
+	if (awacs_revision >= AWACS_DACA){
+		srate = 44100 ;
+	} else {
+		for (i = 0; i < 8 && awacs_freqs[i] >= BEEP_SRATE; ++i)
+			if (awacs_freqs_ok[i])
+				beep_speed = i;
+		srate = awacs_freqs[beep_speed];
+	}
+
+	if (hz <= srate / BEEP_BUFLEN || hz > srate / 2) {
+		/* cancel beep currently playing */
+		awacs_nosound(0);
+		return 0;
+	}
+
+	spin_lock_irqsave(&dmasound.lock, flags);
+	if (beep_playing || write_sq.active || beep_buf == NULL) {
+		spin_unlock_irqrestore(&dmasound.lock, flags);
+		return -1;		/* too hard, sorry :-( */
+	}
+	beep_playing = 1;
+	st_le16(&beep_dbdma_cmd->command, OUTPUT_MORE + BR_ALWAYS);
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+
+	if (hz == beep_hz_cache && beep_vol == beep_volume_cache) {
+		nsamples = beep_nsamples_cache;
+	} 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_vol;
+			j = (j + f) & 0xffff;
+		}
+		beep_hz_cache = hz;
+		beep_volume_cache = beep_vol;
+		beep_nsamples_cache = nsamples;
+	}
+
+	st_le16(&beep_dbdma_cmd->req_count, nsamples*4);
+	st_le16(&beep_dbdma_cmd->xfer_status, 0);
+	st_le32(&beep_dbdma_cmd->cmd_dep, virt_to_bus(beep_dbdma_cmd));
+	st_le32(&beep_dbdma_cmd->phy_addr, virt_to_bus(beep_buf));
+	awacs_beep_state = 1;
+
+	spin_lock_irqsave(&dmasound.lock, flags);
+	if (beep_playing) {	/* i.e. haven't been terminated already */
+		int count = 300 ;
+		out_le32(&awacs_txdma->control, (RUN|WAKE|FLUSH|PAUSE) << 16);
+		while ((in_le32(&awacs_txdma->status) & RUN) && count--)
+			udelay(1); /* timeout > 2 samples at lowest rate*/
+		if (awacs)
+			awacs_setup_for_beep(beep_speed);
+		out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
+		(void)in_le32(&awacs_txdma->status);
+		out_le32(&awacs_txdma->control, RUN | (RUN << 16));
+	}
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+
+	return 0;
+}
+
+/* used in init and for wake-up */
+
+static void
+load_awacs(void)
+{
+	awacs_write(awacs_reg[0] + MASK_ADDR0);
+	awacs_write(awacs_reg[1] + MASK_ADDR1);
+	awacs_write(awacs_reg[2] + MASK_ADDR2);
+	awacs_write(awacs_reg[4] + MASK_ADDR4);
+
+	if (awacs_revision == AWACS_SCREAMER) {
+		awacs_write(awacs_reg[5] + MASK_ADDR5);
+		msleep(100);
+		awacs_write(awacs_reg[6] + MASK_ADDR6);
+		msleep(2);
+		awacs_write(awacs_reg[1] + MASK_ADDR1);
+		awacs_write(awacs_reg[7] + MASK_ADDR7);
+	}
+	if (awacs) {
+		if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
+			out_le32(&awacs->byteswap, BS_VAL);
+		else
+			out_le32(&awacs->byteswap, 0);
+	}
+}
+
+#ifdef CONFIG_PMAC_PBOOK
+/*
+ * Save state when going to sleep, restore it afterwards.
+ */
+/* FIXME: sort out disabling/re-enabling of read stuff as well */
+static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
+{
+	unsigned long flags;
+
+	switch (when) {
+	case PBOOK_SLEEP_NOW:		
+		LOCK();
+		awacs_sleeping = 1;
+		/* Tell the rest of the driver we are now going to sleep */
+		mb();
+		if (awacs_revision == AWACS_SCREAMER ||
+		    awacs_revision == AWACS_AWACS) {
+			awacs_reg1_save = awacs_reg[1];
+			awacs_reg[1] |= MASK_AMUTE | MASK_CMUTE;
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		}
+
+		PMacSilence();
+		/* stop rx - if going - a bit of a daft user... but */
+		out_le32(&awacs_rxdma->control, (RUN|WAKE|FLUSH << 16));
+		/* deny interrupts */
+		if (awacs)
+			disable_irq(awacs_irq);
+		disable_irq(awacs_tx_irq);
+		disable_irq(awacs_rx_irq);
+		/* Chip specific sleep code */
+		switch (awacs_revision) {
+			case AWACS_TUMBLER:
+			case AWACS_SNAPPER:
+				write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol);
+				write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol);
+				tas_enter_sleep();
+				write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol);
+				break ;
+			case AWACS_DACA:
+				daca_enter_sleep();
+				break ;
+			case AWACS_BURGUNDY:
+				break ;
+			case AWACS_SCREAMER:
+			case AWACS_AWACS:
+			default:
+				out_le32(&awacs->control, 0x11) ;
+				break ;
+		}
+		/* Disable sound clock */
+		pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0);
+		/* According to Darwin, we do that after turning off the sound
+		 * chip clock. All this will have to be cleaned up once we properly
+		 * parse the OF sound-objects
+		 */
+		if ((machine_is_compatible("PowerBook3,1") ||
+		    machine_is_compatible("PowerBook3,2")) && awacs) {
+			awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1;
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+			msleep(200);
+		}
+		break;
+	case PBOOK_WAKE:
+		/* Enable sound clock */
+		pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 1);
+		if ((machine_is_compatible("PowerBook3,1") ||
+		    machine_is_compatible("PowerBook3,2")) && awacs) {
+			msleep(100);
+			awacs_reg[1] &= ~(MASK_PAROUT0 | MASK_PAROUT1);
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+			msleep(300);
+		} else
+			msleep(1000);
+ 		/* restore settings */
+		switch (awacs_revision) {
+			case AWACS_TUMBLER:
+			case AWACS_SNAPPER:
+				write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol);
+				write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol);
+				write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol);
+				msleep(100);
+				write_audio_gpio(gpio_audio_reset, !gpio_audio_reset_pol);
+				msleep(150);
+				tas_leave_sleep(); /* Stub for now */
+				headphone_intr(0,NULL,NULL);
+				break;
+			case AWACS_DACA:
+				msleep(10); /* Check this !!! */
+				daca_leave_sleep();
+				break ;		/* dont know how yet */
+			case AWACS_BURGUNDY:
+				break ;
+			case AWACS_SCREAMER:
+			case AWACS_AWACS:
+			default:
+		 		load_awacs() ;
+				break ;
+		}
+		/* Recalibrate chip */
+		if (awacs_revision == AWACS_SCREAMER && awacs)
+			awacs_recalibrate();
+		/* Make sure dma is stopped */
+		PMacSilence();
+		if (awacs)
+			enable_irq(awacs_irq);
+		enable_irq(awacs_tx_irq);
+ 		enable_irq(awacs_rx_irq);
+ 		if (awacs) {
+ 			/* OK, allow ints back again */
+	 		out_le32(&awacs->control, MASK_IEPC
+ 			 	| (awacs_rate_index << 8) | 0x11
+ 				 | (awacs_revision < AWACS_DACA ? MASK_IEE: 0));
+ 		}
+ 		if (macio_base && is_pbook_g3) {
+			/* FIXME: should restore the setup we had...*/
+			out_8(macio_base + 0x37, 3);
+ 		} else if (is_pbook_3X00) {
+			in_8(latch_base + 0x190);
+		}
+		/* Remove mute */
+		if (awacs_revision == AWACS_SCREAMER ||
+		    awacs_revision == AWACS_AWACS) {
+			awacs_reg[1] = awacs_reg1_save;
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		}
+ 		awacs_sleeping = 0;
+		/* Resume pending sounds. */
+		/* we don't try to restart input... */
+		spin_lock_irqsave(&dmasound.lock, flags);
+		__PMacPlay();
+		spin_unlock_irqrestore(&dmasound.lock, flags);
+		UNLOCK();
+	}
+	return PBOOK_SLEEP_OK;
+}
+#endif /* CONFIG_PMAC_PBOOK */
+
+
+/* All the burgundy functions: */
+
+/* Waits for busy flag to clear */
+inline static void
+awacs_burgundy_busy_wait(void)
+{
+	int count = 50; /* > 2 samples at 44k1 */
+	while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--)
+		udelay(1) ;
+}
+
+inline static void
+awacs_burgundy_extend_wait(void)
+{
+	int count = 50 ; /* > 2 samples at 44k1 */
+	while ((!(in_le32(&awacs->codec_stat) & MASK_EXTEND)) && count--)
+		udelay(1) ;
+	count = 50;
+	while ((in_le32(&awacs->codec_stat) & MASK_EXTEND) && count--)
+		udelay(1);
+}
+
+static void
+awacs_burgundy_wcw(unsigned addr, unsigned val)
+{
+	out_le32(&awacs->codec_ctrl, addr + 0x200c00 + (val & 0xff));
+	awacs_burgundy_busy_wait();
+	out_le32(&awacs->codec_ctrl, addr + 0x200d00 +((val>>8) & 0xff));
+	awacs_burgundy_busy_wait();
+	out_le32(&awacs->codec_ctrl, addr + 0x200e00 +((val>>16) & 0xff));
+	awacs_burgundy_busy_wait();
+	out_le32(&awacs->codec_ctrl, addr + 0x200f00 +((val>>24) & 0xff));
+	awacs_burgundy_busy_wait();
+}
+
+static unsigned
+awacs_burgundy_rcw(unsigned addr)
+{
+	unsigned val = 0;
+	unsigned long flags;
+
+	/* should have timeouts here */
+	spin_lock_irqsave(&dmasound.lock, flags);
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100000);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += (in_le32(&awacs->codec_stat) >> 4) & 0xff;
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100100);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<8;
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100200);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<16;
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100300);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<24;
+
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+
+	return val;
+}
+
+
+static void
+awacs_burgundy_wcb(unsigned addr, unsigned val)
+{
+	out_le32(&awacs->codec_ctrl, addr + 0x300000 + (val & 0xff));
+	awacs_burgundy_busy_wait();
+}
+
+static unsigned
+awacs_burgundy_rcb(unsigned addr)
+{
+	unsigned val = 0;
+	unsigned long flags;
+
+	/* should have timeouts here */
+	spin_lock_irqsave(&dmasound.lock, flags);
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100000);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += (in_le32(&awacs->codec_stat) >> 4) & 0xff;
+
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+
+	return val;
+}
+
+static int
+awacs_burgundy_check(void)
+{
+	/* Checks to see the chip is alive and kicking */
+	int error = in_le32(&awacs->codec_ctrl) & MASK_ERRCODE;
+
+	return error == 0xf0000;
+}
+
+static int
+awacs_burgundy_init(void)
+{
+	if (awacs_burgundy_check()) {
+		printk(KERN_WARNING "dmasound_pmac: burgundy not working :-(\n");
+		return 1;
+	}
+
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_OUTPUTENABLES,
+			   DEF_BURGUNDY_OUTPUTENABLES);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+			   DEF_BURGUNDY_MORE_OUTPUTENABLES);
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_OUTPUTSELECTS,
+			   DEF_BURGUNDY_OUTPUTSELECTS);
+
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_INPSEL21,
+			   DEF_BURGUNDY_INPSEL21);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_INPSEL3,
+			   DEF_BURGUNDY_INPSEL3);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINCD,
+			   DEF_BURGUNDY_GAINCD);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINLINE,
+			   DEF_BURGUNDY_GAINLINE);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINMIC,
+			   DEF_BURGUNDY_GAINMIC);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINMODEM,
+			   DEF_BURGUNDY_GAINMODEM);
+
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER,
+			   DEF_BURGUNDY_ATTENSPEAKER);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENLINEOUT,
+			   DEF_BURGUNDY_ATTENLINEOUT);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENHP,
+			   DEF_BURGUNDY_ATTENHP);
+
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_MASTER_VOLUME,
+			   DEF_BURGUNDY_MASTER_VOLUME);
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLCD,
+			   DEF_BURGUNDY_VOLCD);
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLLINE,
+			   DEF_BURGUNDY_VOLLINE);
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLMIC,
+			   DEF_BURGUNDY_VOLMIC);
+	return 0;
+}
+
+static void
+awacs_burgundy_write_volume(unsigned address, int volume)
+{
+	int hardvolume,lvolume,rvolume;
+
+	lvolume = (volume & 0xff) ? (volume & 0xff) + 155 : 0;
+	rvolume = ((volume >>8)&0xff) ? ((volume >> 8)&0xff ) + 155 : 0;
+
+	hardvolume = lvolume + (rvolume << 16);
+
+	awacs_burgundy_wcw(address, hardvolume);
+}
+
+static int
+awacs_burgundy_read_volume(unsigned address)
+{
+	int softvolume,wvolume;
+
+	wvolume = awacs_burgundy_rcw(address);
+
+	softvolume = (wvolume & 0xff) - 155;
+	softvolume += (((wvolume >> 16) & 0xff) - 155)<<8;
+
+	return softvolume > 0 ? softvolume : 0;
+}
+
+static int
+awacs_burgundy_read_mvolume(unsigned address)
+{
+	int lvolume,rvolume,wvolume;
+
+	wvolume = awacs_burgundy_rcw(address);
+
+	wvolume &= 0xffff;
+
+	rvolume = (wvolume & 0xff) - 155;
+	lvolume = ((wvolume & 0xff00)>>8) - 155;
+
+	return lvolume + (rvolume << 8);
+}
+
+static void
+awacs_burgundy_write_mvolume(unsigned address, int volume)
+{
+	int lvolume,rvolume,hardvolume;
+
+	lvolume = (volume &0xff) ? (volume & 0xff) + 155 :0;
+	rvolume = ((volume >>8) & 0xff) ? (volume >> 8) + 155 :0;
+
+	hardvolume = lvolume + (rvolume << 8);
+	hardvolume += (hardvolume << 16);
+
+	awacs_burgundy_wcw(address, hardvolume);
+}
+
+/* End burgundy functions */
+
+/* Set up output volumes on machines with the 'perch/whisper' extension card.
+ * this has an SGS i2c chip (7433) which is accessed using the cuda.
+ *
+ * TODO: split this out and make use of the other parts of the SGS chip to
+ * do Bass, Treble etc.
+ */
+
+static void
+awacs_enable_amp(int spkr_vol)
+{
+#ifdef CONFIG_ADB_CUDA
+	struct adb_request req;
+
+	if (sys_ctrler != SYS_CTRLER_CUDA)
+		return;
+
+	/* turn on headphones */
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
+		     0x8a, 4, 0);
+	while (!req.complete) cuda_poll();
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
+		     0x8a, 6, 0);
+	while (!req.complete) cuda_poll();
+
+	/* turn on speaker */
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
+		     0x8a, 3, (100 - (spkr_vol & 0xff)) * 32 / 100);
+	while (!req.complete) cuda_poll();
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
+		     0x8a, 5, (100 - ((spkr_vol >> 8) & 0xff)) * 32 / 100);
+	while (!req.complete) cuda_poll();
+
+	cuda_request(&req, NULL, 5, CUDA_PACKET,
+		     CUDA_GET_SET_IIC, 0x8a, 1, 0x29);
+	while (!req.complete) cuda_poll();
+#endif /* CONFIG_ADB_CUDA */
+}
+
+
+/*** Mid level stuff *********************************************************/
+
+
+/*
+ * /dev/mixer abstraction
+ */
+
+static void do_line_lev(int data)
+{
+		line_lev = data ;
+		awacs_reg[0] &= ~MASK_MUX_AUDIN;
+		if ((data & 0xff) >= 50)
+			awacs_reg[0] |= MASK_MUX_AUDIN;
+		awacs_write(MASK_ADDR0 | awacs_reg[0]);
+}
+
+static void do_ip_gain(int data)
+{
+	ip_gain = data ;
+	data &= 0xff;
+	awacs_reg[0] &= ~MASK_GAINLINE;
+	if (awacs_revision == AWACS_SCREAMER) {
+		awacs_reg[6] &= ~MASK_MIC_BOOST ;
+		if (data >= 33) {
+			awacs_reg[0] |= MASK_GAINLINE;
+			if( data >= 66)
+				awacs_reg[6] |= MASK_MIC_BOOST ;
+		}
+		awacs_write(MASK_ADDR6 | awacs_reg[6]) ;
+	} else {
+		if (data >= 50)
+			awacs_reg[0] |= MASK_GAINLINE;
+	}
+	awacs_write(MASK_ADDR0 | awacs_reg[0]);
+}
+
+static void do_mic_lev(int data)
+{
+	mic_lev = data ;
+	data &= 0xff;
+	awacs_reg[0] &= ~MASK_MUX_MIC;
+	if (data >= 50)
+		awacs_reg[0] |= MASK_MUX_MIC;
+	awacs_write(MASK_ADDR0 | awacs_reg[0]);
+}
+
+static void do_cd_lev(int data)
+{
+	cd_lev = data ;
+	awacs_reg[0] &= ~MASK_MUX_CD;
+	if ((data & 0xff) >= 50)
+		awacs_reg[0] |= MASK_MUX_CD;
+	awacs_write(MASK_ADDR0 | awacs_reg[0]);
+}
+
+static void do_rec_lev(int data)
+{
+	int left, right ;
+	rec_lev = data ;
+	/* need to fudge this to use the volume setter routine */
+	left = 100 - (data & 0xff) ; if( left < 0 ) left = 0 ;
+	right = 100 - ((data >> 8) & 0xff) ; if( right < 0 ) right = 0 ;
+	left |= (right << 8 );
+	left = awacs_volume_setter(left, 0, 0, 4);
+}
+
+static void do_passthru_vol(int data)
+{
+	passthru_vol = data ;
+	awacs_reg[1] &= ~MASK_LOOPTHRU;
+	if (awacs_revision == AWACS_SCREAMER) {
+		if( data ) { /* switch it on for non-zero */
+			awacs_reg[1] |= MASK_LOOPTHRU;
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		}
+		data = awacs_volume_setter(data, 5, 0, 6) ;
+	} else {
+		if ((data & 0xff) >= 50)
+			awacs_reg[1] |= MASK_LOOPTHRU;
+		awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		data = (awacs_reg[1] & MASK_LOOPTHRU)? 100: 0;
+	}
+}
+
+static int awacs_mixer_ioctl(u_int cmd, u_long arg)
+{
+	int data;
+	int rc;
+
+	switch (cmd) {
+	case SOUND_MIXER_READ_CAPS:
+		/* say we will allow multiple inputs?  prob. wrong
+			so I'm switching it to single */
+		return IOCTL_OUT(arg, 1);
+	case SOUND_MIXER_READ_DEVMASK:
+		data  = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER
+			| SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD
+			| SOUND_MASK_IGAIN | SOUND_MASK_RECLEV
+			| SOUND_MASK_ALTPCM
+			| SOUND_MASK_MONITOR;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECMASK:
+		data = SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECSRC:
+		data = 0;
+		if (awacs_reg[0] & MASK_MUX_AUDIN)
+			data |= SOUND_MASK_LINE;
+		if (awacs_reg[0] & MASK_MUX_MIC)
+			data |= SOUND_MASK_MIC;
+		if (awacs_reg[0] & MASK_MUX_CD)
+			data |= SOUND_MASK_CD;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_RECSRC:
+		IOCTL_IN(arg, data);
+		data &= (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD);
+		awacs_reg[0] &= ~(MASK_MUX_CD | MASK_MUX_MIC
+				  | MASK_MUX_AUDIN);
+		if (data & SOUND_MASK_LINE)
+			awacs_reg[0] |= MASK_MUX_AUDIN;
+		if (data & SOUND_MASK_MIC)
+			awacs_reg[0] |= MASK_MUX_MIC;
+		if (data & SOUND_MASK_CD)
+			awacs_reg[0] |= MASK_MUX_CD;
+		awacs_write(awacs_reg[0] | MASK_ADDR0);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_STEREODEVS:
+		data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER| SOUND_MASK_RECLEV  ;
+		if (awacs_revision == AWACS_SCREAMER)
+			data |= SOUND_MASK_MONITOR ;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_VOLUME:
+		IOCTL_IN(arg, data);
+		line_vol = data ;
+		awacs_volume_setter(data, 2, 0, 6);
+		/* fall through */
+	case SOUND_MIXER_READ_VOLUME:
+		rc = IOCTL_OUT(arg, line_vol);
+		break;
+	case SOUND_MIXER_WRITE_SPEAKER:
+		IOCTL_IN(arg, data);
+		spk_vol = data ;
+		if (has_perch)
+			awacs_enable_amp(data);
+		else
+			(void)awacs_volume_setter(data, 4, MASK_CMUTE, 6);
+		/* fall though */
+	case SOUND_MIXER_READ_SPEAKER:
+		rc = IOCTL_OUT(arg, spk_vol);
+		break;
+	case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */
+		IOCTL_IN(arg, data);
+		beep_vol = data & 0xff;
+		/* fall through */
+	case SOUND_MIXER_READ_ALTPCM:
+		rc = IOCTL_OUT(arg, beep_vol);
+		break;
+	case SOUND_MIXER_WRITE_LINE:
+		IOCTL_IN(arg, data);
+		do_line_lev(data) ;
+		/* fall through */
+	case SOUND_MIXER_READ_LINE:
+		rc = IOCTL_OUT(arg, line_lev);
+		break;
+	case SOUND_MIXER_WRITE_IGAIN:
+		IOCTL_IN(arg, data);
+		do_ip_gain(data) ;
+		/* fall through */
+	case SOUND_MIXER_READ_IGAIN:
+		rc = IOCTL_OUT(arg, ip_gain);
+		break;
+	case SOUND_MIXER_WRITE_MIC:
+		IOCTL_IN(arg, data);
+		do_mic_lev(data);
+		/* fall through */
+	case SOUND_MIXER_READ_MIC:
+		rc = IOCTL_OUT(arg, mic_lev);
+		break;
+	case SOUND_MIXER_WRITE_CD:
+		IOCTL_IN(arg, data);
+		do_cd_lev(data);
+		/* fall through */
+	case SOUND_MIXER_READ_CD:
+		rc = IOCTL_OUT(arg, cd_lev);
+		break;
+	case SOUND_MIXER_WRITE_RECLEV:
+		IOCTL_IN(arg, data);
+		do_rec_lev(data) ;
+		/* fall through */
+	case SOUND_MIXER_READ_RECLEV:
+		rc = IOCTL_OUT(arg, rec_lev);
+		break;
+	case MIXER_WRITE(SOUND_MIXER_MONITOR):
+		IOCTL_IN(arg, data);
+		do_passthru_vol(data) ;
+		/* fall through */
+	case MIXER_READ(SOUND_MIXER_MONITOR):
+		rc = IOCTL_OUT(arg, passthru_vol);
+		break;
+	default:
+		rc = -EINVAL;
+	}
+	
+	return rc;
+}
+
+static void awacs_mixer_init(void)
+{
+	awacs_volume_setter(line_vol, 2, 0, 6);
+	if (has_perch)
+		awacs_enable_amp(spk_vol);
+	else
+		(void)awacs_volume_setter(spk_vol, 4, MASK_CMUTE, 6);
+	do_line_lev(line_lev) ;
+	do_ip_gain(ip_gain) ;
+	do_mic_lev(mic_lev) ;
+	do_cd_lev(cd_lev) ;
+	do_rec_lev(rec_lev) ;
+	do_passthru_vol(passthru_vol) ;
+}
+
+static int burgundy_mixer_ioctl(u_int cmd, u_long arg)
+{
+	int data;
+	int rc;
+
+	/* We are, we are, we are... Burgundy or better */
+	switch(cmd) {
+	case SOUND_MIXER_READ_DEVMASK:
+		data = SOUND_MASK_VOLUME | SOUND_MASK_CD |
+			SOUND_MASK_LINE | SOUND_MASK_MIC |
+			SOUND_MASK_SPEAKER | SOUND_MASK_ALTPCM;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECMASK:
+		data = SOUND_MASK_LINE | SOUND_MASK_MIC
+			| SOUND_MASK_CD;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECSRC:
+		data = 0;
+		if (awacs_reg[0] & MASK_MUX_AUDIN)
+			data |= SOUND_MASK_LINE;
+		if (awacs_reg[0] & MASK_MUX_MIC)
+			data |= SOUND_MASK_MIC;
+		if (awacs_reg[0] & MASK_MUX_CD)
+			data |= SOUND_MASK_CD;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_RECSRC:
+		IOCTL_IN(arg, data);
+		data &= (SOUND_MASK_LINE
+			 | SOUND_MASK_MIC | SOUND_MASK_CD);
+		awacs_reg[0] &= ~(MASK_MUX_CD | MASK_MUX_MIC
+				  | MASK_MUX_AUDIN);
+		if (data & SOUND_MASK_LINE)
+			awacs_reg[0] |= MASK_MUX_AUDIN;
+		if (data & SOUND_MASK_MIC)
+			awacs_reg[0] |= MASK_MUX_MIC;
+		if (data & SOUND_MASK_CD)
+			awacs_reg[0] |= MASK_MUX_CD;
+		awacs_write(awacs_reg[0] | MASK_ADDR0);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_STEREODEVS:
+		data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER
+			| SOUND_MASK_RECLEV | SOUND_MASK_CD
+			| SOUND_MASK_LINE;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_CAPS:
+		rc = IOCTL_OUT(arg, 0);
+		break;
+	case SOUND_MIXER_WRITE_VOLUME:
+		IOCTL_IN(arg, data);
+		awacs_burgundy_write_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME, data);
+				/* Fall through */
+	case SOUND_MIXER_READ_VOLUME:
+		rc = IOCTL_OUT(arg, awacs_burgundy_read_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME));
+		break;
+	case SOUND_MIXER_WRITE_SPEAKER:
+		IOCTL_IN(arg, data);
+		if (!(data & 0xff)) {
+			/* Mute the left speaker */
+			awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+					   awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) & ~0x2);
+		} else {
+			/* Unmute the left speaker */
+			awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+					   awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) | 0x2);
+		}
+		if (!(data & 0xff00)) {
+			/* Mute the right speaker */
+			awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+					   awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) & ~0x4);
+		} else {
+			/* Unmute the right speaker */
+			awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+					   awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) | 0x4);
+		}
+
+		data = (((data&0xff)*16)/100 > 0xf ? 0xf :
+			(((data&0xff)*16)/100)) +
+			((((data>>8)*16)/100 > 0xf ? 0xf :
+			  ((((data>>8)*16)/100)))<<4);
+
+		awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER, ~data);
+				/* Fall through */
+	case SOUND_MIXER_READ_SPEAKER:
+		data = awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER);
+		data = (((data & 0xf)*100)/16) + ((((data>>4)*100)/16)<<8);
+		rc = IOCTL_OUT(arg, (~data) & 0x0000ffff);
+		break;
+	case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */
+		IOCTL_IN(arg, data);
+		beep_vol = data & 0xff;
+				/* fall through */
+	case SOUND_MIXER_READ_ALTPCM:
+		rc = IOCTL_OUT(arg, beep_vol);
+		break;
+	case SOUND_MIXER_WRITE_LINE:
+		IOCTL_IN(arg, data);
+		awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLLINE, data);
+
+				/* fall through */
+	case SOUND_MIXER_READ_LINE:
+		data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLLINE);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_MIC:
+		IOCTL_IN(arg, data);
+				/* Mic is mono device */
+		data = (data << 8) + (data << 24);
+		awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLMIC, data);
+				/* fall through */
+	case SOUND_MIXER_READ_MIC:
+		data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLMIC);
+		data <<= 24;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_CD:
+		IOCTL_IN(arg, data);
+		awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLCD, data);
+				/* fall through */
+	case SOUND_MIXER_READ_CD:
+		data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLCD);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_RECLEV:
+		IOCTL_IN(arg, data);
+		data = awacs_volume_setter(data, 0, 0, 4);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECLEV:
+		data = awacs_get_volume(awacs_reg[0], 4);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_OUTMASK:
+	case SOUND_MIXER_OUTSRC:
+	default:
+		rc = -EINVAL;
+	}
+	
+	return rc;
+}
+
+static int daca_mixer_ioctl(u_int cmd, u_long arg)
+{
+	int data;
+	int rc;
+
+	/* And the DACA's no genius either! */
+
+	switch(cmd) {
+	case SOUND_MIXER_READ_DEVMASK:
+		data = SOUND_MASK_VOLUME;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECMASK:
+		data = 0;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECSRC:
+		data = 0;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_RECSRC:
+		IOCTL_IN(arg, data);
+		data =0;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_STEREODEVS:
+		data = SOUND_MASK_VOLUME;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_CAPS:
+		rc = IOCTL_OUT(arg, 0);
+		break;
+	case SOUND_MIXER_WRITE_VOLUME:
+		IOCTL_IN(arg, data);
+		daca_set_volume(data, data);
+		/* Fall through */
+	case SOUND_MIXER_READ_VOLUME:
+		daca_get_volume(& data, &data);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_OUTMASK:
+	case SOUND_MIXER_OUTSRC:
+	default:
+		rc = -EINVAL;
+	}
+	return rc;
+}
+
+static int PMacMixerIoctl(u_int cmd, u_long arg)
+{
+	int rc;
+	
+	/* Different IOCTLS for burgundy and, eventually, DACA & Tumbler */
+
+	TRY_LOCK();
+	
+	switch (awacs_revision){
+		case AWACS_BURGUNDY:
+			rc = burgundy_mixer_ioctl(cmd, arg);
+			break ;
+		case AWACS_DACA:
+			rc = daca_mixer_ioctl(cmd, arg);
+			break;
+		case AWACS_TUMBLER:
+		case AWACS_SNAPPER:
+			rc = tas_mixer_ioctl(cmd, arg);
+			break ;
+		default: /* ;-)) */
+			rc = awacs_mixer_ioctl(cmd, arg);
+	}
+
+	UNLOCK();
+	
+	return rc;
+}
+
+static void PMacMixerInit(void)
+{
+	switch (awacs_revision) {
+		case AWACS_TUMBLER:
+		  printk("AE-Init tumbler mixer\n");
+		  break ;
+		case AWACS_SNAPPER:
+		  printk("AE-Init snapper mixer\n");
+		  break ;
+		case AWACS_DACA:
+		case AWACS_BURGUNDY:
+			break ;	/* don't know yet */
+		case AWACS_AWACS:
+		case AWACS_SCREAMER:
+		default:
+			awacs_mixer_init() ;
+			break ;
+	}
+}
+
+/* Write/Read sq setup functions:
+   Check to see if we have enough (or any) dbdma cmd buffers for the
+   user's fragment settings.  If not, allocate some. If this fails we will
+   point at the beep buffer - as an emergency provision - to stop dma tromping
+   on some random bit of memory (if someone lets it go anyway).
+   The command buffers are then set up to point to the fragment buffers
+   (allocated elsewhere).  We need n+1 commands the last of which holds
+   a NOP + loop to start.
+*/
+
+static int PMacWriteSqSetup(void)
+{
+	int i, count = 600 ;
+	volatile struct dbdma_cmd *cp;
+
+	LOCK();
+	
+	/* stop the controller from doing any output - if it isn't already.
+	   it _should_ be before this is called anyway */
+
+	out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+	while ((in_le32(&awacs_txdma->status) & RUN) && count--)
+		udelay(1);
+#ifdef DEBUG_DMASOUND
+if (count <= 0)
+	printk("dmasound_pmac: write sq setup: timeout waiting for dma to stop\n");
+#endif
+
+	if ((write_sq.max_count + 1) > number_of_tx_cmd_buffers) {
+		if (awacs_tx_cmd_space)
+			kfree(awacs_tx_cmd_space);
+		number_of_tx_cmd_buffers = 0;
+
+		/* we need nbufs + 1 (for the loop) and we should request + 1
+		   again because the DBDMA_ALIGN might pull the start up by up
+		   to sizeof(struct dbdma_cmd) - 4.
+		*/
+
+		awacs_tx_cmd_space = kmalloc
+			((write_sq.max_count + 1 + 1) * sizeof(struct dbdma_cmd),
+			 GFP_KERNEL);
+		if (awacs_tx_cmd_space == NULL) {
+			/* don't leave it dangling - nasty but better than a
+			   random address */
+			out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
+			printk(KERN_ERR
+			   "dmasound_pmac: can't allocate dbdma cmd buffers"
+			   ", driver disabled\n");
+			UNLOCK();
+			return -ENOMEM;
+		}
+		awacs_tx_cmds = (volatile struct dbdma_cmd *)
+			DBDMA_ALIGN(awacs_tx_cmd_space);
+		number_of_tx_cmd_buffers = write_sq.max_count + 1;
+	}
+
+	cp = awacs_tx_cmds;
+	memset((void *)cp, 0, (write_sq.max_count+1) * sizeof(struct dbdma_cmd));
+	for (i = 0; i < write_sq.max_count; ++i, ++cp) {
+		st_le32(&cp->phy_addr, virt_to_bus(write_sq.buffers[i]));
+	}
+	st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS);
+	st_le32(&cp->cmd_dep, virt_to_bus(awacs_tx_cmds));
+	/* point the controller at the command stack - ready to go */
+	out_le32(&awacs_txdma->cmdptr, virt_to_bus(awacs_tx_cmds));
+	UNLOCK();
+	return 0;
+}
+
+static int PMacReadSqSetup(void)
+{
+	int i, count = 600;
+	volatile struct dbdma_cmd *cp;
+
+	LOCK();
+	
+	/* stop the controller from doing any input - if it isn't already.
+	   it _should_ be before this is called anyway */
+	
+	out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+	while ((in_le32(&awacs_rxdma->status) & RUN) && count--)
+		udelay(1);
+#ifdef DEBUG_DMASOUND
+if (count <= 0)
+	printk("dmasound_pmac: read sq setup: timeout waiting for dma to stop\n");
+#endif
+
+	if ((read_sq.max_count+1) > number_of_rx_cmd_buffers ) {
+		if (awacs_rx_cmd_space)
+			kfree(awacs_rx_cmd_space);
+		number_of_rx_cmd_buffers = 0;
+
+		/* we need nbufs + 1 (for the loop) and we should request + 1 again
+		   because the DBDMA_ALIGN might pull the start up by up to
+		   sizeof(struct dbdma_cmd) - 4 (assuming kmalloc aligns 32 bits).
+		*/
+
+		awacs_rx_cmd_space = kmalloc
+			((read_sq.max_count + 1 + 1) * sizeof(struct dbdma_cmd),
+			 GFP_KERNEL);
+		if (awacs_rx_cmd_space == NULL) {
+			/* don't leave it dangling - nasty but better than a
+			   random address */
+			out_le32(&awacs_rxdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
+			printk(KERN_ERR
+			   "dmasound_pmac: can't allocate dbdma cmd buffers"
+			   ", driver disabled\n");
+			UNLOCK();
+			return -ENOMEM;
+		}
+		awacs_rx_cmds = (volatile struct dbdma_cmd *)
+			DBDMA_ALIGN(awacs_rx_cmd_space);
+		number_of_rx_cmd_buffers = read_sq.max_count + 1 ;
+	}
+	cp = awacs_rx_cmds;
+	memset((void *)cp, 0, (read_sq.max_count+1) * sizeof(struct dbdma_cmd));
+
+	/* Set dma buffers up in a loop */
+	for (i = 0; i < read_sq.max_count; i++,cp++) {
+		st_le32(&cp->phy_addr, virt_to_bus(read_sq.buffers[i]));
+		st_le16(&cp->command, INPUT_MORE + INTR_ALWAYS);
+		st_le16(&cp->req_count, read_sq.block_size);
+		st_le16(&cp->xfer_status, 0);
+	}
+
+	/* The next two lines make the thing loop around.
+	*/
+	st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS);
+	st_le32(&cp->cmd_dep, virt_to_bus(awacs_rx_cmds));
+	/* point the controller at the command stack - ready to go */
+	out_le32(&awacs_rxdma->cmdptr, virt_to_bus(awacs_rx_cmds));
+
+	UNLOCK();
+	return 0;
+}
+
+/* TODO: this needs work to guarantee that when it returns DMA has stopped
+   but in a more elegant way than is done here....
+*/
+
+static void PMacAbortRead(void)
+{
+	int i;
+	volatile struct dbdma_cmd *cp;
+
+	LOCK();
+	/* give it a chance to update the output and provide the IRQ
+	   that is expected.
+	*/
+
+	out_le32(&awacs_rxdma->control, ((FLUSH) << 16) + FLUSH );
+
+	cp = awacs_rx_cmds;
+	for (i = 0; i < read_sq.max_count; i++,cp++)
+		st_le16(&cp->command, DBDMA_STOP);
+	/*
+	 * We should probably wait for the thing to stop before we
+	 * release the memory.
+	 */
+
+	msleep(100) ; /* give it a (small) chance to act */
+
+	/* apply the sledgehammer approach - just stop it now */
+
+	out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+	UNLOCK();
+}
+
+extern char *get_afmt_string(int);
+static int PMacStateInfo(char *b, size_t sp)
+{
+	int i, len = 0;
+	len = sprintf(b,"HW rates: ");
+	switch (awacs_revision){
+		case AWACS_DACA:
+		case AWACS_BURGUNDY:
+			len += sprintf(b,"44100 ") ;
+			break ;
+		case AWACS_TUMBLER:
+		case AWACS_SNAPPER:
+			for (i=0; i<1; i++){
+				if (tas_freqs_ok[i])
+					len += sprintf(b+len,"%d ", tas_freqs[i]) ;
+			}
+			break ;
+		case AWACS_AWACS:
+		case AWACS_SCREAMER:
+		default:
+			for (i=0; i<8; i++){
+				if (awacs_freqs_ok[i])
+					len += sprintf(b+len,"%d ", awacs_freqs[i]) ;
+			}
+			break ;
+	}
+	len += sprintf(b+len,"s/sec\n") ;
+	if (len < sp) {
+		len += sprintf(b+len,"HW AFMTS: ");
+		i = AFMT_U16_BE ;
+		while (i) {
+			if (i & dmasound.mach.hardware_afmts)
+				len += sprintf(b+len,"%s ",
+					get_afmt_string(i & dmasound.mach.hardware_afmts));
+			i >>= 1 ;
+		}
+		len += sprintf(b+len,"\n") ;
+	}
+	return len ;
+}
+
+/*** Machine definitions *****************************************************/
+
+static SETTINGS def_hard = {
+	.format	= AFMT_S16_BE,
+	.stereo	= 1,
+	.size	= 16,
+	.speed	= 44100
+} ;
+
+static SETTINGS def_soft = {
+	.format	= AFMT_S16_BE,
+	.stereo	= 1,
+	.size	= 16,
+	.speed	= 44100
+} ;
+
+static MACHINE machPMac = {
+	.name		= awacs_name,
+	.name2		= "PowerMac Built-in Sound",
+	.owner		= THIS_MODULE,
+	.dma_alloc	= PMacAlloc,
+	.dma_free	= PMacFree,
+	.irqinit	= PMacIrqInit,
+#ifdef MODULE
+	.irqcleanup	= PMacIrqCleanup,
+#endif /* MODULE */
+	.init		= PMacInit,
+	.silence	= PMacSilence,
+	.setFormat	= PMacSetFormat,
+	.setVolume	= PMacSetVolume,
+	.play		= PMacPlay,
+	.record		= NULL,		/* default to no record */
+	.mixer_init	= PMacMixerInit,
+	.mixer_ioctl	= PMacMixerIoctl,
+	.write_sq_setup	= PMacWriteSqSetup,
+	.read_sq_setup	= PMacReadSqSetup,
+	.state_info	= PMacStateInfo,
+	.abort_read	= PMacAbortRead,
+	.min_dsp_speed	= 7350,
+	.max_dsp_speed	= 44100,
+	.version	= ((DMASOUND_AWACS_REVISION<<8) + DMASOUND_AWACS_EDITION)
+};
+
+
+/*** Config & Setup **********************************************************/
+
+/* Check for pmac models that we care about in terms of special actions.
+*/
+
+void __init
+set_model(void)
+{
+	/* portables/lap-tops */
+
+	if (machine_is_compatible("AAPL,3400/2400") ||
+	    machine_is_compatible("AAPL,3500"))	{
+		is_pbook_3X00 = 1 ;
+	}
+	if (machine_is_compatible("PowerBook1,1")  || /* lombard */
+	    machine_is_compatible("AAPL,PowerBook1998")){ /* wallstreet */
+		is_pbook_g3 = 1 ;
+		return ;
+	}
+}
+
+/* Get the OF node that tells us about the registers, interrupts etc. to use
+   for sound IO.
+
+   On most machines the sound IO OF node is the 'davbus' node.  On newer pmacs
+   with DACA (& Tumbler) the node to use is i2s-a.  On much older machines i.e.
+   before 9500 there is no davbus node and we have to use the 'awacs' property.
+
+  In the latter case we signal this by setting the codec value - so that the
+  code that looks for chip properties knows how to go about it.
+*/
+
+static struct device_node* __init
+get_snd_io_node(void)
+{
+	struct device_node *np = NULL;
+
+	/* set up awacs_node for early OF which doesn't have a full set of
+	 * properties on davbus
+	*/
+
+	awacs_node = find_devices("awacs");
+	if (awacs_node)
+		awacs_revision = AWACS_AWACS;
+
+	/* powermac models after 9500 (other than those which use DACA or
+	 * Tumbler) have a node called "davbus".
+	 */
+	np = find_devices("davbus");
+	/*
+	 * if we didn't find a davbus device, try 'i2s-a' since
+	 * this seems to be what iBooks (& Tumbler) have.
+	 */
+	if (np == NULL)
+		np = i2s_node = find_devices("i2s-a");
+
+	/* if we didn't find this - perhaps we are on an early model
+	 * which _only_ has an 'awacs' node
+	*/
+	if (np == NULL && awacs_node)
+		np = awacs_node ;
+
+	/* if we failed all these return null - this will cause the
+	 * driver to give up...
+	*/
+	return np ;
+}
+
+/* Get the OF node that contains the info about the sound chip, inputs s-rates
+   etc.
+   This node does not exist (or contains much reduced info) on earlier machines
+   we have to deduce the info other ways for these.
+*/
+
+static struct device_node* __init
+get_snd_info_node(struct device_node *io)
+{
+	struct device_node *info;
+
+	info = find_devices("sound");
+	while (info && info->parent != io)
+		info = info->next;
+	return info;
+}
+
+/* Find out what type of codec we have.
+*/
+
+static int __init
+get_codec_type(struct device_node *info)
+{
+	/* already set if pre-davbus model and info will be NULL */
+	int codec = awacs_revision ;
+
+	if (info) {
+		/* must do awacs first to allow screamer to overide it */
+		if (device_is_compatible(info, "awacs"))
+			codec = AWACS_AWACS ;
+		if (device_is_compatible(info, "screamer"))
+			codec = AWACS_SCREAMER;
+		if (device_is_compatible(info, "burgundy"))
+			codec = AWACS_BURGUNDY ;
+		if (device_is_compatible(info, "daca"))
+			codec = AWACS_DACA;
+		if (device_is_compatible(info, "tumbler"))
+			codec = AWACS_TUMBLER;
+		if (device_is_compatible(info, "snapper"))
+			codec = AWACS_SNAPPER;
+	}
+	return codec ;
+}
+
+/* find out what type, if any, of expansion card we have
+*/
+static void __init
+get_expansion_type(void)
+{
+	if (find_devices("perch") != NULL)
+		has_perch = 1;
+
+	if (find_devices("pb-ziva-pc") != NULL)
+		has_ziva = 1;
+	/* need to work out how we deal with iMac SRS module */
+}
+
+/* set up frame rates.
+ * I suspect that these routines don't quite go about it the right way:
+ * - where there is more than one rate - I think that the first property
+ * value is the number of rates.
+ * TODO: check some more device trees and modify accordingly
+ *       Set dmasound.mach.max_dsp_rate on the basis of these routines.
+*/
+
+static void __init
+awacs_init_frame_rates(unsigned int *prop, unsigned int l)
+{
+	int i ;
+	if (prop) {
+		for (i=0; i<8; i++)
+			awacs_freqs_ok[i] = 0 ;
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			for (i = 0; i < 8; ++i) {
+				if (r == awacs_freqs[i]) {
+					awacs_freqs_ok[i] = 1;
+					break;
+				}
+			}
+		}
+	}
+	/* else we assume that all the rates are available */
+}
+
+static void __init
+burgundy_init_frame_rates(unsigned int *prop, unsigned int l)
+{
+	int temp[9] ;
+	int i = 0 ;
+	if (prop) {
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			temp[i] = r ;
+			i++ ; if(i>=9) i=8;
+		}
+	}
+#ifdef DEBUG_DMASOUND
+if (i > 1){
+	int j;
+	printk("dmasound_pmac: burgundy with multiple frame rates\n");
+	for(j=0; j<i; j++)
+		printk("%d ", temp[j]) ;
+	printk("\n") ;
+}
+#endif
+}
+
+static void __init
+daca_init_frame_rates(unsigned int *prop, unsigned int l)
+{
+	int temp[9] ;
+	int i = 0 ;
+	if (prop) {
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			temp[i] = r ;
+			i++ ; if(i>=9) i=8;
+
+		}
+	}
+#ifdef DEBUG_DMASOUND
+if (i > 1){
+	int j;
+	printk("dmasound_pmac: DACA with multiple frame rates\n");
+	for(j=0; j<i; j++)
+		printk("%d ", temp[j]) ;
+	printk("\n") ;
+}
+#endif
+}
+
+static void __init
+init_frame_rates(unsigned int *prop, unsigned int l)
+{
+	switch (awacs_revision) {
+		case AWACS_TUMBLER:
+		case AWACS_SNAPPER:
+			tas_init_frame_rates(prop, l);
+			break ;
+		case AWACS_DACA:
+			daca_init_frame_rates(prop, l);
+			break ;
+		case AWACS_BURGUNDY:
+			burgundy_init_frame_rates(prop, l);
+			break ;
+		default:
+			awacs_init_frame_rates(prop, l);
+			break ;
+	}
+}
+
+/* find things/machines that can't do mac-io byteswap
+*/
+
+static void __init
+set_hw_byteswap(struct device_node *io)
+{
+	struct device_node *mio ;
+	unsigned int kl = 0 ;
+
+	/* if seems that Keylargo can't byte-swap  */
+
+	for (mio = io->parent; mio ; mio = mio->parent) {
+		if (strcmp(mio->name, "mac-io") == 0) {
+			if (device_is_compatible(mio, "Keylargo"))
+				kl = 1;
+			break;
+		}
+	}
+	hw_can_byteswap = !kl;
+}
+
+/* Allocate the resources necessary for beep generation.  This cannot be (quite)
+   done statically (yet) because we cannot do virt_to_bus() on static vars when
+   the code is loaded as a module.
+
+   for the sake of saving the possibility that two allocations will incur the
+   overhead of two pull-ups in DBDMA_ALIGN() we allocate the 'emergency' dmdma
+   command here as well... even tho' it is not part of the beep process.
+*/
+
+int32_t
+__init setup_beep(void)
+{
+	/* Initialize beep stuff */
+	/* want one cmd buffer for beeps, and a second one for emergencies
+	   - i.e. dbdma error conditions.
+	   ask for three to allow for pull up in DBDMA_ALIGN().
+	*/
+	beep_dbdma_cmd_space =
+		kmalloc((2 + 1) * sizeof(struct dbdma_cmd), GFP_KERNEL);
+	if(beep_dbdma_cmd_space == NULL) {
+		printk(KERN_ERR "dmasound_pmac: no beep dbdma cmd space\n") ;
+		return -ENOMEM ;
+	}
+	beep_dbdma_cmd = (volatile struct dbdma_cmd *)
+			DBDMA_ALIGN(beep_dbdma_cmd_space);
+	/* set up emergency dbdma cmd */
+	emergency_dbdma_cmd = beep_dbdma_cmd+1 ;
+	beep_buf = (short *) kmalloc(BEEP_BUFLEN * 4, GFP_KERNEL);
+	if (beep_buf == NULL) {
+		printk(KERN_ERR "dmasound_pmac: no memory for beep buffer\n");
+		if( beep_dbdma_cmd_space ) kfree(beep_dbdma_cmd_space) ;
+		return -ENOMEM ;
+	}
+	return 0 ;
+}
+
+static struct input_dev awacs_beep_dev = {
+	.evbit		= { BIT(EV_SND) },
+	.sndbit		= { BIT(SND_BELL) | BIT(SND_TONE) },
+	.event		= awacs_beep_event,
+	.name		= "dmasound beeper",
+	.phys		= "macio/input0", /* what the heck is this?? */
+	.id		= {
+		.bustype	= BUS_HOST,
+	},
+};
+
+int __init dmasound_awacs_init(void)
+{
+	struct device_node *io = NULL, *info = NULL;
+	int vol, res;
+
+	if (_machine != _MACH_Pmac)
+		return -ENODEV;
+
+	awacs_subframe = 0;
+	awacs_revision = 0;
+	hw_can_byteswap = 1 ; /* most can */
+
+	/* look for models we need to handle specially */
+	set_model() ;
+
+	/* find the OF node that tells us about the dbdma stuff
+	*/
+	io = get_snd_io_node();
+	if (io == NULL) {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: couldn't find sound io OF node\n");
+#endif
+		return -ENODEV ;
+	}
+
+	/* find the OF node that tells us about the sound sub-system
+	 * this doesn't exist on pre-davbus machines (earlier than 9500)
+	*/
+	if (awacs_revision != AWACS_AWACS) { /* set for pre-davbus */
+		info = get_snd_info_node(io) ;
+		if (info == NULL){
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: couldn't find 'sound' OF node\n");
+#endif
+			return -ENODEV ;
+		}
+	}
+
+	awacs_revision = get_codec_type(info) ;
+	if (awacs_revision == 0) {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: couldn't find a Codec we can handle\n");
+#endif
+		return -ENODEV ; /* we don't know this type of h/w */
+	}
+
+	/* set up perch, ziva, SRS or whatever else we have as sound
+	 *  expansion.
+	*/
+	get_expansion_type();
+
+	/* we've now got enough information to make up the audio topology.
+	 * we will map the sound part of mac-io now so that we can probe for
+	 * other info if necessary (early AWACS we want to read chip ids)
+	 */
+
+	if (io->n_addrs < 3 || io->n_intrs < 3) {
+		/* OK - maybe we need to use the 'awacs' node (on earlier
+		 * machines).
+		*/
+		if (awacs_node) {
+			io = awacs_node ;
+			if (io->n_addrs < 3 || io->n_intrs < 3) {
+				printk("dmasound_pmac: can't use %s"
+					" (%d addrs, %d intrs)\n",
+		      		 io->full_name, io->n_addrs, io->n_intrs);
+				return -ENODEV;
+			}
+		} else {
+			printk("dmasound_pmac: can't use %s (%d addrs, %d intrs)\n",
+		 	      io->full_name, io->n_addrs, io->n_intrs);
+		}
+	}
+
+	if (!request_OF_resource(io, 0, NULL)) {
+		printk(KERN_ERR "dmasound: can't request IO resource !\n");
+		return -ENODEV;
+	}
+	if (!request_OF_resource(io, 1, " (tx dma)")) {
+		release_OF_resource(io, 0);
+		printk(KERN_ERR "dmasound: can't request TX DMA resource !\n");
+		return -ENODEV;
+	}
+
+	if (!request_OF_resource(io, 2, " (rx dma)")) {
+		release_OF_resource(io, 0);
+		release_OF_resource(io, 1);
+		printk(KERN_ERR "dmasound: can't request RX DMA resource !\n");
+		return -ENODEV;
+	}
+
+	/* all OF versions I've seen use this value */
+	if (i2s_node)
+		i2s = ioremap(io->addrs[0].address, 0x1000);
+	else
+		awacs = ioremap(io->addrs[0].address, 0x1000);
+	awacs_txdma = ioremap(io->addrs[1].address, 0x100);
+	awacs_rxdma = ioremap(io->addrs[2].address, 0x100);
+
+	/* first of all make sure that the chip is powered up....*/
+	pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, io, 0, 1);
+	if (awacs_revision == AWACS_SCREAMER && awacs)
+		awacs_recalibrate();
+
+	awacs_irq = io->intrs[0].line;
+	awacs_tx_irq = io->intrs[1].line;
+	awacs_rx_irq = io->intrs[2].line;
+
+	/* Hack for legacy crap that will be killed someday */
+	awacs_node = io;
+
+	/* if we have an awacs or screamer - probe the chip to make
+	 * sure we have the right revision.
+	*/
+
+	if (awacs_revision <= AWACS_SCREAMER){
+		uint32_t temp, rev, mfg ;
+		/* find out the awacs revision from the chip */
+		temp = in_le32(&awacs->codec_stat);
+		rev = (temp >> 12) & 0xf;
+		mfg = (temp >>  8) & 0xf;
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: Awacs/Screamer Codec Mfct: %d Rev %d\n", mfg, rev);
+#endif
+		if (rev >= AWACS_SCREAMER)
+			awacs_revision = AWACS_SCREAMER ;
+		else
+			awacs_revision = rev ;
+	}
+
+	dmasound.mach = machPMac;
+
+	/* find out other bits & pieces from OF, these may be present
+	   only on some models ... so be careful.
+	*/
+
+	/* in the absence of a frame rates property we will use the defaults
+	*/
+
+	if (info) {
+		unsigned int *prop, l;
+
+		sound_device_id = 0;
+		/* device ID appears post g3 b&w */
+		prop = (unsigned int *)get_property(info, "device-id", NULL);
+		if (prop != 0)
+			sound_device_id = *prop;
+
+		/* look for a property saying what sample rates
+		   are available */
+
+		prop = (unsigned int *)get_property(info, "sample-rates", &l);
+		if (prop == 0)
+			prop = (unsigned int *) get_property
+				(info, "output-frame-rates", &l);
+
+		/* if it's there use it to set up frame rates */
+		init_frame_rates(prop, l) ;
+	}
+
+	if (awacs)
+		out_le32(&awacs->control, 0x11); /* set everything quiesent */
+
+	set_hw_byteswap(io) ; /* figure out if the h/w can do it */
+
+#ifdef CONFIG_NVRAM
+	/* get default volume from nvram */
+	vol = ((pmac_xpram_read( 8 ) & 7 ) << 1 );
+#else
+	vol = 0;
+#endif
+
+	/* set up tracking values */
+	spk_vol = vol * 100 ;
+	spk_vol /= 7 ; /* get set value to a percentage */
+	spk_vol |= (spk_vol << 8) ; /* equal left & right */
+ 	line_vol = passthru_vol = spk_vol ;
+
+	/* fill regs that are shared between AWACS & Burgundy */
+
+	awacs_reg[2] = vol + (vol << 6);
+	awacs_reg[4] = vol + (vol << 6);
+	awacs_reg[5] = vol + (vol << 6); /* screamer has loopthru vol control */
+	awacs_reg[6] = 0; /* maybe should be vol << 3 for PCMCIA speaker */
+	awacs_reg[7] = 0;
+
+	awacs_reg[0] = MASK_MUX_CD;
+	awacs_reg[1] = MASK_LOOPTHRU;
+
+	/* FIXME: Only machines with external SRS module need MASK_PAROUT */
+	if (has_perch || sound_device_id == 0x5
+	    || /*sound_device_id == 0x8 ||*/ sound_device_id == 0xb)
+		awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1;
+
+	switch (awacs_revision) {
+		case AWACS_TUMBLER:
+                        tas_register_driver(&tas3001c_hooks);
+			tas_init(I2C_DRIVERID_TAS3001C, I2C_DRIVERNAME_TAS3001C);
+			tas_dmasound_init();
+			tas_post_init();
+			break ;
+		case AWACS_SNAPPER:
+                        tas_register_driver(&tas3004_hooks);
+			tas_init(I2C_DRIVERID_TAS3004,I2C_DRIVERNAME_TAS3004);
+			tas_dmasound_init();
+			tas_post_init();
+			break;
+		case AWACS_DACA:
+			daca_init();
+			break;	
+		case AWACS_BURGUNDY:
+			awacs_burgundy_init();
+			break ;
+		case AWACS_SCREAMER:
+		case AWACS_AWACS:
+		default:
+			load_awacs();
+			break ;
+	}
+
+	/* enable/set-up external modules - when we know how */
+
+	if (has_perch)
+		awacs_enable_amp(100 * 0x101);
+
+	/* Reset dbdma channels */
+	out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16);
+	while (in_le32(&awacs_txdma->status) & RUN)
+		udelay(1);
+	out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16);
+	while (in_le32(&awacs_rxdma->status) & RUN)
+		udelay(1);
+
+	/* Initialize beep stuff */
+	if ((res=setup_beep()))
+		return res ;
+
+#ifdef CONFIG_PMAC_PBOOK
+	pmu_register_sleep_notifier(&awacs_sleep_notifier);
+#endif /* CONFIG_PMAC_PBOOK */
+
+	/* 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 (is_pbook_3X00) {
+		/*
+		 * 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.
+		 */
+		latch_base = ioremap (0xf301a000, 0x1000);
+		in_8(latch_base + 0x190);
+
+	} else if (is_pbook_g3) {
+		struct device_node* mio;
+		macio_base = NULL;
+		for (mio = io->parent; mio; mio = mio->parent) {
+			if (strcmp(mio->name, "mac-io") == 0
+			    && mio->n_addrs > 0) {
+				macio_base = ioremap(mio->addrs[0].address, 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.
+		 *  -- paulus.
+		 */
+		if (macio_base)
+			out_8(macio_base + 0x37, 3);
+	}
+
+	if (hw_can_byteswap)
+ 		dmasound.mach.hardware_afmts = (AFMT_S16_BE | AFMT_S16_LE) ;
+ 	else
+		dmasound.mach.hardware_afmts = AFMT_S16_BE ;
+
+	/* shut out chips that do output only.
+	 * may need to extend this to machines which have no inputs - even tho'
+	 * they use screamer - IIRC one of the powerbooks is like this.
+	 */
+
+	if (awacs_revision != AWACS_DACA) {
+		dmasound.mach.capabilities = DSP_CAP_DUPLEX ;
+		dmasound.mach.record = PMacRecord ;
+	}
+
+	dmasound.mach.default_hard = def_hard ;
+	dmasound.mach.default_soft = def_soft ;
+
+	switch (awacs_revision) {
+		case AWACS_BURGUNDY:
+			sprintf(awacs_name, "PowerMac Burgundy ") ;
+			break ;
+		case AWACS_DACA:
+			sprintf(awacs_name, "PowerMac DACA ") ;
+			break ;
+		case AWACS_TUMBLER:
+			sprintf(awacs_name, "PowerMac Tumbler ") ;
+			break ;
+		case AWACS_SNAPPER:
+			sprintf(awacs_name, "PowerMac Snapper ") ;
+			break ;
+		case AWACS_SCREAMER:
+			sprintf(awacs_name, "PowerMac Screamer ") ;
+			break ;
+		case AWACS_AWACS:
+		default:
+			sprintf(awacs_name, "PowerMac AWACS rev %d ", awacs_revision) ;
+			break ;
+	}
+
+	/*
+	 * XXX: we should handle errors here, but that would mean
+	 * rewriting the whole init code.  later..
+	 */
+	input_register_device(&awacs_beep_dev);
+
+	return dmasound_init();
+}
+
+static void __exit dmasound_awacs_cleanup(void)
+{
+	input_unregister_device(&awacs_beep_dev);
+
+	switch (awacs_revision) {
+		case AWACS_TUMBLER:
+		case AWACS_SNAPPER:
+			tas_dmasound_cleanup();
+			tas_cleanup();
+			break ;
+		case AWACS_DACA:
+			daca_cleanup();
+			break;
+	}
+	dmasound_deinit();
+
+}
+
+MODULE_DESCRIPTION("PowerMac built-in audio driver.");
+MODULE_LICENSE("GPL");
+
+module_init(dmasound_awacs_init);
+module_exit(dmasound_awacs_cleanup);
diff --git a/sound/oss/dmasound/dmasound_core.c b/sound/oss/dmasound/dmasound_core.c
new file mode 100644
index 0000000..c9302a1
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_core.c
@@ -0,0 +1,1829 @@
+/*
+ *  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/smp_lock.h>
+
+#include <asm/uaccess.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_CORE_REVISION 1
+#define DMASOUND_CORE_EDITION 6
+
+    /*
+     *  Declarations
+     */
+
+int dmasound_catchRadius = 0;
+MODULE_PARM(dmasound_catchRadius, "i");
+
+static unsigned int numWriteBufs = DEFAULT_N_BUFFERS;
+MODULE_PARM(numWriteBufs, "i");
+static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ;	/* in bytes */
+MODULE_PARM(writeBufSize, "i");
+
+#ifdef HAS_RECORD
+static unsigned int numReadBufs = DEFAULT_N_BUFFERS;
+MODULE_PARM(numReadBufs, "i");
+static unsigned int readBufSize = DEFAULT_BUFF_SIZE;	/* in bytes */
+MODULE_PARM(readBufSize, "i");
+#endif
+
+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 */
+
+/* software implemented recording volume! */
+uint software_input_volume = SW_INPUT_VOLUME_SCALE * SW_INPUT_VOLUME_DEFAULT;
+EXPORT_SYMBOL(software_input_volume);
+
+/* control over who can modify resources shared between play/record */
+static mode_t shared_resource_owner;
+static int shared_resources_initialised;
+
+    /*
+     *  Mid level stuff
+     */
+
+struct sound_settings dmasound = { .lock = SPIN_LOCK_UNLOCKED };
+
+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)
+{
+	if (!try_module_get(dmasound.mach.owner))
+		return -ENODEV;
+	mixer.busy = 1;
+	return 0;
+}
+
+static int mixer_release(struct inode *inode, struct file *file)
+{
+	lock_kernel();
+	mixer.busy = 0;
+	module_put(dmasound.mach.owner);
+	unlock_kernel();
+	return 0;
+}
+static int mixer_ioctl(struct inode *inode, 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 struct file_operations mixer_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.ioctl		= mixer_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) ;
+#ifdef HAS_RECORD
+struct sound_queue dmasound_read_sq;
+static void sq_reset_input(void) ;
+#endif
+
+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;
+	}
+#ifdef HAS_RECORD
+	else {
+	    sq->rear = 0;
+	    setup_func = dmasound.mach.read_sq_setup;
+	}
+#endif
+	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.open_mode & O_NONBLOCK)
+				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);
+#ifdef HAS_RECORD
+	if (file->f_mode & FMODE_READ)
+		poll_wait(file, &read_sq.action_queue, wait);
+	if (file->f_mode & FMODE_READ)
+		if (read_sq.block_size - read_sq.rear_size > 0)
+			mask |= POLLIN | POLLRDNORM;
+#endif
+	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;
+
+}
+
+#ifdef HAS_RECORD
+    /*
+     *  Here is how the values are used for reading.
+     *  The value 'active' simply indicates the DMA is running.  This is done
+     *  so the driver semantics are DMA starts when the first read is posted.
+     *  The value 'front' indicates the buffer we should next send to the user.
+     *  The value 'rear' indicates the buffer the DMA is currently filling.
+     *  When 'front' == 'rear' the buffer "ring" is empty (we always have an
+     *  empty available).  The 'rear_size' is used to track partial offsets
+     *  into the buffer we are currently returning to the user.
+
+     *  This level (> [1.5]) doesn't care what strategy the LL driver uses with
+     *  DMA on over-run.  It can leave it running (and keep active == 1) or it
+     *  can kill it and set active == 0 in which case this routine will spot
+     *  it and restart the DMA.
+     */
+
+static ssize_t sq_read(struct file *file, char __user *dst, size_t uLeft,
+		       loff_t *ppos)
+{
+
+	ssize_t	uRead, bLeft, bUsed, uUsed;
+
+	if (uLeft == 0)
+		return 0;
+
+	/* cater for the compatibility mode - record compiled in but no LL */
+	if (dmasound.mach.record == NULL)
+		return -EINVAL ;
+
+	/* see comment in sq_write()
+	*/
+
+	if( shared_resources_initialised == 0) {
+		dmasound.mach.init() ;
+		shared_resources_initialised = 1 ;
+	}
+
+	/* set up the sq if it is not already done. see comments in sq_write().
+	*/
+
+	if (read_sq.locked == 0) {
+		if ((uRead = sq_setup(&read_sq)) < 0)
+			return uRead ;
+	}
+
+	uRead = 0;
+
+	/* Move what the user requests, depending upon other options.
+	*/
+	while (uLeft > 0) {
+
+		/* we happened to get behind and the LL driver killed DMA
+		   then we should set it going again.  This also sets it
+		   going the first time through.
+		*/
+		if ( !read_sq.active )
+			dmasound.mach.record();
+
+		/* When front == rear, the DMA is not done yet.
+		*/
+		while (read_sq.front == read_sq.rear) {
+			if (read_sq.open_mode & O_NONBLOCK) {
+			       return uRead > 0 ? uRead : -EAGAIN;
+			}
+			SLEEP(read_sq.action_queue);
+			if (signal_pending(current))
+				return uRead > 0 ? uRead : -EINTR;
+		}
+
+		/* The amount we move is either what is left in the
+		 * current buffer or what the user wants.
+		 */
+		bLeft = read_sq.block_size - read_sq.rear_size;
+		bUsed = read_sq.rear_size;
+		uUsed = sound_copy_translate(dmasound.trans_read, dst, uLeft,
+					     read_sq.buffers[read_sq.front],
+					     &bUsed, bLeft);
+		if (uUsed <= 0)
+			return uUsed;
+		dst += uUsed;
+		uRead += uUsed;
+		uLeft -= uUsed;
+		read_sq.rear_size += bUsed;
+		if (read_sq.rear_size >= read_sq.block_size) {
+			read_sq.rear_size = 0;
+			read_sq.front++;
+			if (read_sq.front >= read_sq.max_active)
+				read_sq.front = 0;
+		}
+	}
+	return uRead;
+}
+#endif /* HAS_RECORD */
+
+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,
+			      mode_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, mode_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->open_mode = file->f_mode;
+	}
+	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 )
+
+#ifdef HAS_RECORD
+#define read_sq_init_waitqueue()	sq_init_waitqueue(&read_sq)
+#if 0 /* blocking open() */
+#define read_sq_wake_up(file)		sq_wake_up(&read_sq, file, FMODE_READ)
+#endif
+#define read_sq_release_buffers()	sq_release_buffers(&read_sq)
+#define read_sq_open(file)	\
+	sq_open2(&read_sq, file, FMODE_READ, numReadBufs, readBufSize )
+#else
+#define read_sq_init_waitqueue()	do {} while (0)
+#if 0 /* blocking open() */
+#define read_sq_wake_up(file)		do {} while (0)
+#endif
+#define read_sq_release_buffers()	do {} while (0)
+#define sq_reset_input()		do {} while (0)
+#endif
+
+static int sq_open(struct inode *inode, struct file *file)
+{
+	int rc;
+
+	if (!try_module_get(dmasound.mach.owner))
+		return -ENODEV;
+
+	rc = write_sq_open(file); /* checks the f_mode */
+	if (rc)
+		goto out;
+#ifdef HAS_RECORD
+	if (dmasound.mach.record) {
+		rc = read_sq_open(file); /* checks the f_mode */
+		if (rc)
+			goto out;
+	} else { /* no record function installed; in compat mode */
+		if (file->f_mode & FMODE_READ) {
+			/* TODO: if O_RDWR, release any resources grabbed by write part */
+			rc = -ENXIO;
+			goto out;
+		}
+	}
+#else /* !HAS_RECORD */
+	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;
+	}
+#endif /* HAS_RECORD */
+
+	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
+
+	return 0;
+ out:
+	module_put(dmasound.mach.owner);
+	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 ;
+}
+
+#ifdef HAS_RECORD
+
+static void sq_reset_input(void)
+{
+	if (dmasound.mach.record && read_sq.active) {
+		if (dmasound.mach.abort_read) { /* this routine must really be present */
+			read_sq.syncing = 1 ;
+			/* this can use the read_sq.sync_queue to sleep if
+			   necessary - it should not return until DMA
+			   is really stopped - because we might deallocate
+			   the buffers as the next action...
+			*/
+			dmasound.mach.abort_read() ;
+		} else {
+			printk(KERN_ERR
+			"dmasound_core: %s has no abort_read()!! all bets are off\n",
+				dmasound.mach.name) ;
+		}
+	}
+	read_sq.syncing =
+	read_sq.active =
+	read_sq.front =
+	read_sq.count =
+	read_sq.rear = 0 ;
+
+	/* OK - we can unlock the parameters and fragment settings */
+	read_sq.locked = 0 ;
+	read_sq.user_frags = 0 ;
+	read_sq.user_frag_size = 0 ;
+}
+
+#endif
+
+static void sq_reset(void)
+{
+	sq_reset_output() ;
+	sq_reset_input() ;
+	/* 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;
+
+	lock_kernel();
+
+#ifdef HAS_RECORD
+	/* probably best to do the read side first - so that time taken to do it
+	   overlaps with playing any remaining output samples.
+	*/
+	if (file->f_mode & FMODE_READ) {
+		sq_reset_input() ; /* make sure dma is stopped and all is quiet */
+		read_sq_release_buffers();
+		read_sq.busy = 0;
+	}
+#endif
+
+	if (file->f_mode & FMODE_WRITE) {
+		if (write_sq.busy)
+			rc = sq_fsync(file, file->f_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() */
+
+	unlock_kernel();
+
+	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(mode_t md)
+{
+	if (shared_resource_owner)
+		return (shared_resource_owner & md ) ;
+	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)
+{
+#ifdef HAS_RECORD
+	if (dmasound.mach.record)
+		if (read_sq.locked)
+			return 0 ;
+#endif
+	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 inode *inode, 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 ;
+#ifdef HAS_RECORD
+		if (dmasound.mach.record && (file->f_mode & FMODE_READ)) {
+			if ( !read_sq.locked )
+				sq_setup(&read_sq) ; /* set params */
+			size = read_sq.user_frag_size ;
+		}
+#endif
+		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_READ) && dmasound.mach.record)
+			sq_reset_input() ;
+		if (file->f_mode & FMODE_WRITE) {
+			result = sq_fsync(file, file->f_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;
+#ifdef HAS_RECORD
+		if ((file->f_mode & FMODE_READ) && dmasound.mach.record) {
+			result = set_queue_frags(&read_sq, nbufs, size) ;
+			if (result)
+				return result ;
+		}
+#endif
+		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(inode, file, cmd, arg);
+	}
+	return -EINVAL;
+}
+
+static struct file_operations sq_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= sq_write,
+	.poll		= sq_poll,
+	.ioctl		= sq_ioctl,
+	.open		= sq_open,
+	.release	= sq_release,
+#ifdef HAS_RECORD
+	.read		= NULL	/* default to no read for compat mode */
+#endif
+};
+
+static int sq_init(void)
+{
+#ifndef MODULE
+	int sq_unit;
+#endif
+
+#ifdef HAS_RECORD
+	if (dmasound.mach.record)
+		sq_fops.read = sq_read ;
+#endif
+	sq_unit = register_sound_dsp(&sq_fops, -1);
+	if (sq_unit < 0) {
+		printk(KERN_ERR "dmasound_core: couldn't register fops\n") ;
+		return sq_unit ;
+	}
+
+	write_sq_init_waitqueue();
+	read_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.
+*/
+
+#ifdef HAS_RECORD
+#define STAT_BUFF_LEN 1024
+#else
+#define STAT_BUFF_LEN 768
+#endif
+
+/* 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 */
+
+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;
+
+	if (state.busy)
+		return -EBUSY;
+
+	if (!try_module_get(dmasound.mach.owner))
+		return -ENODEV;
+	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) ;
+#ifdef HAS_RECORD
+	if (dmasound.mach.record)
+		len += sprintf(buffer+len,"%9s:%8d%6d\n",
+			"read", read_sq.numBufs, read_sq.bufSize) ;
+#endif
+	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 HAS_RECORD
+	if (dmasound.mach.record)
+		len += sprintf(buffer+len,"%9s:%7d%8d%7d%5d%5d%4d%7d%2d%2d%2d%2d%7d\n",
+			"read", read_sq.max_count, read_sq.block_size,
+			read_sq.max_active, read_sq.front, read_sq.rear,
+			read_sq.count, read_sq.rear_size, read_sq.active,
+			read_sq.busy, read_sq.syncing, read_sq.locked, read_sq.xruns) ;
+#endif
+#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;
+	return 0;
+}
+
+static int state_release(struct inode *inode, struct file *file)
+{
+	lock_kernel();
+	state.busy = 0;
+	module_put(dmasound.mach.owner);
+	unlock_kernel();
+	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 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) ;
+#ifdef HAS_RECORD
+	if (dmasound.mach.record)
+		printk(KERN_INFO
+			"Read  will use %4d fragments of %7d bytes as default\n",
+			numReadBufs, readBufSize) ;
+#endif
+
+	return 0;
+}
+
+#ifdef MODULE
+
+void dmasound_deinit(void)
+{
+	if (irq_installed) {
+		sound_silence();
+		dmasound.mach.irqcleanup();
+		irq_installed = 0;
+	}
+
+	write_sq_release_buffers();
+	read_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]) {
+#ifdef HAS_RECORD
+        case 5:
+                if ((ints[5] < 0) || (ints[5] > MAX_CATCH_RADIUS))
+                        printk("dmasound_setup: invalid catch radius, using default = %d\n", catchRadius);
+                else
+                        catchRadius = ints[5];
+                /* fall through */
+        case 4:
+                if (ints[4] < MIN_BUFFERS)
+                        printk("dmasound_setup: invalid number of read buffers, using default = %d\n",
+                                 numReadBufs);
+                else
+                        numReadBufs = ints[4];
+                /* fall through */
+        case 3:
+		if ((size = ints[3]) < 256)  /* check for small buffer specs */
+			size <<= 10 ;
+                if (size < MIN_BUFSIZE || size > MAX_BUFSIZE)
+                        printk("dmasound_setup: invalid read buffer size, using default = %d\n", readBufSize);
+                else
+                        readBufSize = size;
+                /* fall through */
+#else
+	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 */
+#endif
+	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);
+#ifdef HAS_RECORD
+EXPORT_SYMBOL(dmasound_read_sq);
+#endif
+EXPORT_SYMBOL(dmasound_catchRadius);
+#ifdef HAS_8BIT_TABLES
+EXPORT_SYMBOL(dmasound_ulaw2dma8);
+EXPORT_SYMBOL(dmasound_alaw2dma8);
+#endif
+EXPORT_SYMBOL(get_afmt_string) ;
diff --git a/sound/oss/dmasound/dmasound_paula.c b/sound/oss/dmasound/dmasound_paula.c
new file mode 100644
index 0000000..558db53
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_paula.c
@@ -0,0 +1,743 @@
+/*
+ *  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/config.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/soundcard.h>
+#include <linux/interrupt.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
+
+   /*
+    *	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, int 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, struct pt_regs *fp);
+
+#ifdef CONFIG_HEARTBEAT
+
+    /*
+     *  Heartbeat interferes with sound since the 7 kHz low-pass filter and the
+     *  power LED are controlled by the same line.
+     */
+
+#ifdef CONFIG_APUS
+#define mach_heartbeat	ppc_md.heartbeat
+#endif
+
+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 *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 *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 *userPtr, size_t userCount,	\
+			u_char frame[], ssize_t *frameUsed,		\
+			ssize_t frameLeft)				\
+{									\
+	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, ((u_short *)userPtr)++))	\
+				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, ((u_short *)userPtr)++))	\
+				return -EFAULT;				\
+			data = convsample(data);			\
+			*lefth++ = data>>8;				\
+			*leftl++ = (data>>2) & 0x3f;			\
+			if (get_user(data, ((u_short *)userPtr)++))	\
+				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, int 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, struct pt_regs *fp)
+{
+	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: overlowed 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 **********************************************************/
+
+
+int __init dmasound_paula_init(void)
+{
+	int err;
+
+	if (MACH_IS_AMIGA && AMIGAHW_PRESENT(AMI_AUDIO)) {
+	    if (!request_mem_region(CUSTOM_PHYSADDR+0xa0, 0x40,
+				    "dmasound [Paula]"))
+		return -EBUSY;
+	    dmasound.mach = machAmiga;
+	    dmasound.mach.default_hard = def_hard ;
+	    dmasound.mach.default_soft = def_soft ;
+	    err = dmasound_init();
+	    if (err)
+		release_mem_region(CUSTOM_PHYSADDR+0xa0, 0x40);
+	    return err;
+	} else
+	    return -ENODEV;
+}
+
+static void __exit dmasound_paula_cleanup(void)
+{
+	dmasound_deinit();
+	release_mem_region(CUSTOM_PHYSADDR+0xa0, 0x40);
+}
+
+module_init(dmasound_paula_init);
+module_exit(dmasound_paula_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c
new file mode 100644
index 0000000..92c25a0
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_q40.c
@@ -0,0 +1,634 @@
+/*
+ *  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, int 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, struct pt_regs *fp);
+static irqreturn_t Q40MonoInterrupt(int irq, void *dummy, struct pt_regs *fp);
+static void Q40Interrupt(void);
+
+
+/*** Mid level stuff *********************************************************/
+
+
+
+/* userCount, frameUsed, frameLeft == byte counts */
+static ssize_t q40_ct_law(const u_char *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 *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 *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 *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 *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 *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 *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 *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 *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, int 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. */
+	request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
+		    "DMA sound", Q40Interrupt);
+
+	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;
+
+	/* 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)
+	  	request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
+		    "Q40 sound", Q40Interrupt);
+	  else
+	        request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0,
+		    "Q40 sound", Q40Interrupt);
+
+	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, struct pt_regs *fp)
+{
+	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, struct pt_regs *fp)
+{
+	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 **********************************************************/
+
+
+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/sound/oss/dmasound/tas3001c.c b/sound/oss/dmasound/tas3001c.c
new file mode 100644
index 0000000..f227c9f
--- /dev/null
+++ b/sound/oss/dmasound/tas3001c.c
@@ -0,0 +1,850 @@
+/*
+ * Driver for the i2c/i2s based TA3004 sound chip used
+ * on some Apple hardware. Also known as "snapper".
+ *
+ * Tobias Sargeant <tobias.sargeant@bigpond.com>
+ * Based upon, tas3001c.c by Christopher C. Chimelis <chris@debian.org>:
+ *
+ *   TODO:
+ *   -----
+ *   * Enable control over input line 2 (is this connected?)
+ *   * Implement sleep support (at least mute everything and
+ *   * set gains to minimum during sleep)
+ *   * Look into some of Darwin's tweaks regarding the mute
+ *   * lines (delays & different behaviour on some HW)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/ioport.h>
+#include <linux/sysctl.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/soundcard.h>
+#include <linux/workqueue.h>
+#include <asm/uaccess.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+
+#include "dmasound.h"
+#include "tas_common.h"
+#include "tas3001c.h"
+
+#include "tas_ioctl.h"
+
+#define TAS3001C_BIQUAD_FILTER_COUNT  6
+#define TAS3001C_BIQUAD_CHANNEL_COUNT 2
+
+#define VOL_DEFAULT	(100 * 4 / 5)
+#define INPUT_DEFAULT	(100 * 4 / 5)
+#define BASS_DEFAULT	(100 / 2)
+#define TREBLE_DEFAULT	(100 / 2)
+
+struct tas3001c_data_t {
+	struct tas_data_t super;
+	int device_id;
+	int output_id;
+	int speaker_id;
+	struct tas_drce_t drce_state;
+};
+
+
+static const union tas_biquad_t
+tas3001c_eq_unity={
+	.buf = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 }
+};
+
+
+static inline unsigned char db_to_regval(short db) {
+	int r=0;
+
+	r=(db+0x59a0) / 0x60;
+
+	if (r < 0x91) return 0x91;
+	if (r > 0xef) return 0xef;
+	return r;
+}
+
+static inline short quantize_db(short db) {
+	return db_to_regval(db) * 0x60 - 0x59a0;
+}
+
+
+static inline int
+register_width(enum tas3001c_reg_t r)
+{
+	switch(r) {
+	case TAS3001C_REG_MCR:
+ 	case TAS3001C_REG_TREBLE:
+	case TAS3001C_REG_BASS:
+		return 1;
+
+	case TAS3001C_REG_DRC:
+		return 2;
+
+	case TAS3001C_REG_MIXER1:
+	case TAS3001C_REG_MIXER2:
+		return 3;
+
+	case TAS3001C_REG_VOLUME:
+		return 6;
+
+	case TAS3001C_REG_LEFT_BIQUAD0:
+	case TAS3001C_REG_LEFT_BIQUAD1:
+	case TAS3001C_REG_LEFT_BIQUAD2:
+	case TAS3001C_REG_LEFT_BIQUAD3:
+	case TAS3001C_REG_LEFT_BIQUAD4:
+	case TAS3001C_REG_LEFT_BIQUAD5:
+	case TAS3001C_REG_LEFT_BIQUAD6:
+
+	case TAS3001C_REG_RIGHT_BIQUAD0:
+	case TAS3001C_REG_RIGHT_BIQUAD1:
+	case TAS3001C_REG_RIGHT_BIQUAD2:
+	case TAS3001C_REG_RIGHT_BIQUAD3:
+	case TAS3001C_REG_RIGHT_BIQUAD4:
+	case TAS3001C_REG_RIGHT_BIQUAD5:
+	case TAS3001C_REG_RIGHT_BIQUAD6:
+		return 15;
+
+	default:
+		return 0;
+	}
+}
+
+static int
+tas3001c_write_register(	struct tas3001c_data_t *self,
+				enum tas3001c_reg_t reg_num,
+				char *data,
+				uint write_mode)
+{
+	if (reg_num==TAS3001C_REG_MCR ||
+	    reg_num==TAS3001C_REG_BASS ||
+	    reg_num==TAS3001C_REG_TREBLE) {
+		return tas_write_byte_register(&self->super,
+					       (uint)reg_num,
+					       *data,
+					       write_mode);
+	} else {
+		return tas_write_register(&self->super,
+					  (uint)reg_num,
+					  register_width(reg_num),
+					  data,
+					  write_mode);
+	}
+}
+
+static int
+tas3001c_sync_register(	struct tas3001c_data_t *self,
+			enum tas3001c_reg_t reg_num)
+{
+	if (reg_num==TAS3001C_REG_MCR ||
+	    reg_num==TAS3001C_REG_BASS ||
+	    reg_num==TAS3001C_REG_TREBLE) {
+		return tas_sync_byte_register(&self->super,
+					      (uint)reg_num,
+					      register_width(reg_num));
+	} else {
+		return tas_sync_register(&self->super,
+					 (uint)reg_num,
+					 register_width(reg_num));
+	}
+}
+
+static int
+tas3001c_read_register(	struct tas3001c_data_t *self,
+			enum tas3001c_reg_t reg_num,
+			char *data,
+			uint write_mode)
+{
+	return tas_read_register(&self->super,
+				 (uint)reg_num,
+				 register_width(reg_num),
+				 data);
+}
+
+static inline int
+tas3001c_fast_load(struct tas3001c_data_t *self, int fast)
+{
+	if (fast)
+		self->super.shadow[TAS3001C_REG_MCR][0] |= 0x80;
+	else
+		self->super.shadow[TAS3001C_REG_MCR][0] &= 0x7f;
+	return tas3001c_sync_register(self,TAS3001C_REG_MCR);
+}
+
+static uint
+tas3001c_supported_mixers(struct tas3001c_data_t *self)
+{
+	return SOUND_MASK_VOLUME |
+		SOUND_MASK_PCM |
+		SOUND_MASK_ALTPCM |
+		SOUND_MASK_TREBLE |
+		SOUND_MASK_BASS;
+}
+
+static int
+tas3001c_mixer_is_stereo(struct tas3001c_data_t *self,int mixer)
+{
+	switch(mixer) {
+	case SOUND_MIXER_VOLUME:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static uint
+tas3001c_stereo_mixers(struct tas3001c_data_t *self)
+{
+	uint r=tas3001c_supported_mixers(self);
+	uint i;
+	
+	for (i=1; i<SOUND_MIXER_NRDEVICES; i++)
+		if (r&(1<<i) && !tas3001c_mixer_is_stereo(self,i))
+			r &= ~(1<<i);
+	return r;
+}
+
+static int
+tas3001c_get_mixer_level(struct tas3001c_data_t *self,int mixer,uint *level)
+{
+	if (!self)
+		return -1;
+		
+	*level=self->super.mixer[mixer];
+	
+	return 0;
+}
+
+static int
+tas3001c_set_mixer_level(struct tas3001c_data_t *self,int mixer,uint level)
+{
+	int rc;
+	tas_shadow_t *shadow;
+
+	uint temp;
+	uint offset=0;
+
+	if (!self)
+		return -1;
+		
+	shadow=self->super.shadow;
+
+	if (!tas3001c_mixer_is_stereo(self,mixer))
+		level = tas_mono_to_stereo(level);
+
+	switch(mixer) {
+	case SOUND_MIXER_VOLUME:
+		temp = tas3001c_gain.master[level&0xff];
+		shadow[TAS3001C_REG_VOLUME][0] = (temp >> 16) & 0xff;
+		shadow[TAS3001C_REG_VOLUME][1] = (temp >> 8)  & 0xff;
+		shadow[TAS3001C_REG_VOLUME][2] = (temp >> 0)  & 0xff;
+		temp = tas3001c_gain.master[(level>>8)&0xff];
+		shadow[TAS3001C_REG_VOLUME][3] = (temp >> 16) & 0xff;
+		shadow[TAS3001C_REG_VOLUME][4] = (temp >> 8)  & 0xff;
+		shadow[TAS3001C_REG_VOLUME][5] = (temp >> 0)  & 0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+		break;
+	case SOUND_MIXER_ALTPCM:
+		/* tas3001c_fast_load(self, 1); */
+		level = tas_mono_to_stereo(level);
+		temp = tas3001c_gain.mixer[level&0xff];
+		shadow[TAS3001C_REG_MIXER2][offset+0] = (temp >> 16) & 0xff;
+		shadow[TAS3001C_REG_MIXER2][offset+1] = (temp >> 8)  & 0xff;
+		shadow[TAS3001C_REG_MIXER2][offset+2] = (temp >> 0)  & 0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+		/* tas3001c_fast_load(self, 0); */
+		break;
+	case SOUND_MIXER_PCM:
+		/* tas3001c_fast_load(self, 1); */
+		level = tas_mono_to_stereo(level);
+		temp = tas3001c_gain.mixer[level&0xff];
+		shadow[TAS3001C_REG_MIXER1][offset+0] = (temp >> 16) & 0xff;
+		shadow[TAS3001C_REG_MIXER1][offset+1] = (temp >> 8)  & 0xff;
+		shadow[TAS3001C_REG_MIXER1][offset+2] = (temp >> 0)  & 0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+		/* tas3001c_fast_load(self, 0); */
+		break;
+	case SOUND_MIXER_TREBLE:
+		temp = tas3001c_gain.treble[level&0xff];
+		shadow[TAS3001C_REG_TREBLE][0]=temp&0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+		break;
+	case SOUND_MIXER_BASS:
+		temp = tas3001c_gain.bass[level&0xff];
+		shadow[TAS3001C_REG_BASS][0]=temp&0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_BASS);
+		break;
+	default:
+		rc = -1;
+		break;
+	}
+	if (rc < 0)
+		return rc;
+	self->super.mixer[mixer]=level;
+	return 0;
+}
+
+static int
+tas3001c_leave_sleep(struct tas3001c_data_t *self)
+{
+	unsigned char mcr = (1<<6)+(2<<4)+(2<<2);
+
+	if (!self)
+		return -1;
+
+	/* Make sure something answers on the i2c bus */
+	if (tas3001c_write_register(self, TAS3001C_REG_MCR, &mcr,
+	    WRITE_NORMAL|FORCE_WRITE) < 0)
+	    	return -1;
+
+	tas3001c_fast_load(self, 1);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD0);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD3);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD4);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD5);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD0);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD3);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD4);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD5);
+
+	tas3001c_fast_load(self, 0);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_BASS);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+
+	return 0;
+}
+
+static int
+tas3001c_enter_sleep(struct tas3001c_data_t *self)
+{
+	/* Stub for now, but I have the details on low-power mode */
+	if (!self)
+		return -1; 
+	return 0;
+}
+
+static int
+tas3001c_sync_biquad(	struct tas3001c_data_t *self,
+			u_int channel,
+			u_int filter)
+{
+	enum tas3001c_reg_t reg;
+
+	if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter;
+
+	return tas3001c_sync_register(self,reg);
+}
+
+static int
+tas3001c_write_biquad_shadow(	struct tas3001c_data_t *self,
+				u_int channel,
+				u_int filter,
+				const union tas_biquad_t *biquad)
+{
+	tas_shadow_t *shadow=self->super.shadow;
+	enum tas3001c_reg_t reg;
+
+	if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter;
+
+	SET_4_20(shadow[reg], 0,biquad->coeff.b0);
+	SET_4_20(shadow[reg], 3,biquad->coeff.b1);
+	SET_4_20(shadow[reg], 6,biquad->coeff.b2);
+	SET_4_20(shadow[reg], 9,biquad->coeff.a1);
+	SET_4_20(shadow[reg],12,biquad->coeff.a2);
+
+	return 0;
+}
+
+static int
+tas3001c_write_biquad(	struct tas3001c_data_t *self,
+			u_int channel,
+			u_int filter,
+			const union tas_biquad_t *biquad)
+{
+	int rc;
+
+	rc=tas3001c_write_biquad_shadow(self, channel, filter, biquad);
+	if (rc < 0) return rc;
+
+	return tas3001c_sync_biquad(self, channel, filter);
+}
+
+static int
+tas3001c_write_biquad_list(	struct tas3001c_data_t *self,
+				u_int filter_count,
+				u_int flags,
+				struct tas_biquad_ctrl_t *biquads)
+{
+	int i;
+	int rc;
+
+	if (flags & TAS_BIQUAD_FAST_LOAD) tas3001c_fast_load(self,1);
+
+	for (i=0; i<filter_count; i++) {
+		rc=tas3001c_write_biquad(self,
+					 biquads[i].channel,
+					 biquads[i].filter,
+					 &biquads[i].data);
+		if (rc < 0) break;
+	}
+
+	if (flags & TAS_BIQUAD_FAST_LOAD) {
+		tas3001c_fast_load(self,0);
+
+		(void)tas3001c_sync_register(self,TAS3001C_REG_BASS);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+	}
+
+	return rc;
+}
+
+static int
+tas3001c_read_biquad(	struct tas3001c_data_t *self,
+			u_int channel,
+			u_int filter,
+			union tas_biquad_t *biquad)
+{
+	tas_shadow_t *shadow=self->super.shadow;
+	enum tas3001c_reg_t reg;
+
+	if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter;
+
+	biquad->coeff.b0=GET_4_20(shadow[reg], 0);
+	biquad->coeff.b1=GET_4_20(shadow[reg], 3);
+	biquad->coeff.b2=GET_4_20(shadow[reg], 6);
+	biquad->coeff.a1=GET_4_20(shadow[reg], 9);
+	biquad->coeff.a2=GET_4_20(shadow[reg],12);
+	
+	return 0;	
+}
+
+static int
+tas3001c_eq_rw(	struct tas3001c_data_t *self,
+		u_int cmd,
+		u_long arg)
+{
+	int rc;
+	struct tas_biquad_ctrl_t biquad;
+	void __user *argp = (void __user *)arg;
+
+	if (copy_from_user(&biquad, argp, sizeof(struct tas_biquad_ctrl_t))) {
+		return -EFAULT;
+	}
+
+	if (cmd & SIOC_IN) {
+		rc=tas3001c_write_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+		if (rc != 0) return rc;
+	}
+
+	if (cmd & SIOC_OUT) {
+		rc=tas3001c_read_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+		if (rc != 0) return rc;
+
+		if (copy_to_user(argp, &biquad, sizeof(struct tas_biquad_ctrl_t))) {
+			return -EFAULT;
+		}
+
+	}
+	return 0;
+}
+
+static int
+tas3001c_eq_list_rw(	struct tas3001c_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	int rc;
+	int filter_count;
+	int flags;
+	int i,j;
+	char sync_required[2][6];
+	struct tas_biquad_ctrl_t biquad;
+	struct tas_biquad_ctrl_list_t __user *argp = (void __user *)arg;
+
+	memset(sync_required,0,sizeof(sync_required));
+
+	if (copy_from_user(&filter_count, &argp->filter_count, sizeof(int)))
+		return -EFAULT;
+
+	if (copy_from_user(&flags, &argp->flags, sizeof(int)))
+		return -EFAULT;
+
+	if (cmd & SIOC_IN) {
+	}
+
+	for (i=0; i < filter_count; i++) {
+		if (copy_from_user(&biquad, &argp->biquads[i],
+				   sizeof(struct tas_biquad_ctrl_t))) {
+			return -EFAULT;
+		}
+
+		if (cmd & SIOC_IN) {
+			sync_required[biquad.channel][biquad.filter]=1;
+			rc=tas3001c_write_biquad_shadow(self, biquad.channel, biquad.filter, &biquad.data);
+			if (rc != 0) return rc;
+		}
+
+		if (cmd & SIOC_OUT) {
+			rc=tas3001c_read_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+			if (rc != 0) return rc;
+
+			if (copy_to_user(&argp->biquads[i], &biquad,
+					 sizeof(struct tas_biquad_ctrl_t))) {
+				return -EFAULT;
+			}
+		}
+	}
+
+	if (cmd & SIOC_IN) {
+		if (flags & TAS_BIQUAD_FAST_LOAD) tas3001c_fast_load(self,1);
+		for (i=0; i<2; i++) {
+			for (j=0; j<6; j++) {
+				if (sync_required[i][j]) {
+					rc=tas3001c_sync_biquad(self, i, j);
+					if (rc < 0) return rc;
+				}
+			}
+		}
+		if (flags & TAS_BIQUAD_FAST_LOAD) {
+			tas3001c_fast_load(self,0);
+			/* now we need to set up the mixers again,
+			   because leaving fast mode resets them. */
+			(void)tas3001c_sync_register(self,TAS3001C_REG_BASS);
+			(void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+			(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+			(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+			(void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+		}
+	}
+
+	return 0;
+}
+
+static int
+tas3001c_update_drce(	struct tas3001c_data_t *self,
+			int flags,
+			struct tas_drce_t *drce)
+{
+	tas_shadow_t *shadow;
+	shadow=self->super.shadow;
+
+	shadow[TAS3001C_REG_DRC][1] = 0xc1;
+
+	if (flags & TAS_DRCE_THRESHOLD) {
+		self->drce_state.threshold=quantize_db(drce->threshold);
+		shadow[TAS3001C_REG_DRC][2] = db_to_regval(self->drce_state.threshold);
+	}
+
+	if (flags & TAS_DRCE_ENABLE) {
+		self->drce_state.enable = drce->enable;
+	}
+
+	if (!self->drce_state.enable) {
+		shadow[TAS3001C_REG_DRC][0] = 0xf0;
+	}
+
+#ifdef DEBUG_DRCE
+	printk("DRCE IOCTL: set [ ENABLE:%x THRESH:%x\n",
+	       self->drce_state.enable,
+	       self->drce_state.threshold);
+
+	printk("DRCE IOCTL: reg [ %02x %02x ]\n",
+	       (unsigned char)shadow[TAS3001C_REG_DRC][0],
+	       (unsigned char)shadow[TAS3001C_REG_DRC][1]);
+#endif
+
+	return tas3001c_sync_register(self, TAS3001C_REG_DRC);
+}
+
+static int
+tas3001c_drce_rw(	struct tas3001c_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	int rc;
+	struct tas_drce_ctrl_t drce_ctrl;
+	void __user *argp = (void __user *)arg;
+
+	if (copy_from_user(&drce_ctrl, argp, sizeof(struct tas_drce_ctrl_t)))
+		return -EFAULT;
+
+#ifdef DEBUG_DRCE
+	printk("DRCE IOCTL: input [ FLAGS:%x ENABLE:%x THRESH:%x\n",
+	       drce_ctrl.flags,
+	       drce_ctrl.data.enable,
+	       drce_ctrl.data.threshold);
+#endif
+
+	if (cmd & SIOC_IN) {
+		rc = tas3001c_update_drce(self, drce_ctrl.flags, &drce_ctrl.data);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (cmd & SIOC_OUT) {
+		if (drce_ctrl.flags & TAS_DRCE_ENABLE)
+			drce_ctrl.data.enable = self->drce_state.enable;
+
+		if (drce_ctrl.flags & TAS_DRCE_THRESHOLD)
+			drce_ctrl.data.threshold = self->drce_state.threshold;
+
+		if (copy_to_user(argp, &drce_ctrl,
+				 sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+	}
+
+	return 0;
+}
+
+static void
+tas3001c_update_device_parameters(struct tas3001c_data_t *self)
+{
+	int i,j;
+
+	if (!self) return;
+
+	if (self->output_id == TAS_OUTPUT_HEADPHONES) {
+		tas3001c_fast_load(self, 1);
+
+		for (i=0; i<TAS3001C_BIQUAD_CHANNEL_COUNT; i++) {
+			for (j=0; j<TAS3001C_BIQUAD_FILTER_COUNT; j++) {
+				tas3001c_write_biquad(self, i, j, &tas3001c_eq_unity);
+			}
+		}
+
+		tas3001c_fast_load(self, 0);
+
+		(void)tas3001c_sync_register(self,TAS3001C_REG_BASS);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+
+		return;
+	}
+
+	for (i=0; tas3001c_eq_prefs[i]; i++) {
+		struct tas_eq_pref_t *eq = tas3001c_eq_prefs[i];
+
+		if (eq->device_id == self->device_id &&
+		    (eq->output_id == 0 || eq->output_id == self->output_id) &&
+		    (eq->speaker_id == 0 || eq->speaker_id == self->speaker_id)) {
+
+			tas3001c_update_drce(self, TAS_DRCE_ALL, eq->drce);
+			tas3001c_write_biquad_list(self, eq->filter_count, TAS_BIQUAD_FAST_LOAD, eq->biquads);
+
+			break;
+		}
+	}
+}
+
+static void
+tas3001c_device_change_handler(void *self)
+{
+	if (self)
+		tas3001c_update_device_parameters(self);
+}
+
+static struct work_struct device_change;
+
+static int
+tas3001c_output_device_change(	struct tas3001c_data_t *self,
+				int device_id,
+				int output_id,
+				int speaker_id)
+{
+	self->device_id=device_id;
+	self->output_id=output_id;
+	self->speaker_id=speaker_id;
+
+	schedule_work(&device_change);
+	return 0;
+}
+
+static int
+tas3001c_device_ioctl(	struct tas3001c_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	uint __user *argp = (void __user *)arg;
+	switch (cmd) {
+	case TAS_READ_EQ:
+	case TAS_WRITE_EQ:
+		return tas3001c_eq_rw(self, cmd, arg);
+
+	case TAS_READ_EQ_LIST:
+	case TAS_WRITE_EQ_LIST:
+		return tas3001c_eq_list_rw(self, cmd, arg);
+
+	case TAS_READ_EQ_FILTER_COUNT:
+		put_user(TAS3001C_BIQUAD_FILTER_COUNT, argp);
+		return 0;
+
+	case TAS_READ_EQ_CHANNEL_COUNT:
+		put_user(TAS3001C_BIQUAD_CHANNEL_COUNT, argp);
+		return 0;
+
+	case TAS_READ_DRCE:
+	case TAS_WRITE_DRCE:
+		return tas3001c_drce_rw(self, cmd, arg);
+
+	case TAS_READ_DRCE_CAPS:
+		put_user(TAS_DRCE_ENABLE | TAS_DRCE_THRESHOLD, argp);
+		return 0;
+
+	case TAS_READ_DRCE_MIN:
+	case TAS_READ_DRCE_MAX: {
+		struct tas_drce_ctrl_t drce_ctrl;
+
+		if (copy_from_user(&drce_ctrl, argp,
+				   sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+
+		if (drce_ctrl.flags & TAS_DRCE_THRESHOLD) {
+			if (cmd == TAS_READ_DRCE_MIN) {
+				drce_ctrl.data.threshold=-36<<8;
+			} else {
+				drce_ctrl.data.threshold=-6<<8;
+			}
+		}
+
+		if (copy_to_user(argp, &drce_ctrl,
+				 sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+	}
+	}
+
+	return -EINVAL;
+}
+
+static int
+tas3001c_init_mixer(struct tas3001c_data_t *self)
+{
+	unsigned char mcr = (1<<6)+(2<<4)+(2<<2);
+
+	/* Make sure something answers on the i2c bus */
+	if (tas3001c_write_register(self, TAS3001C_REG_MCR, &mcr,
+	    WRITE_NORMAL|FORCE_WRITE) < 0)
+		return -1;
+
+	tas3001c_fast_load(self, 1);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD0);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD3);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD4);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD5);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD6);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD0);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD3);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD4);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD5);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD6);
+
+	tas3001c_fast_load(self, 0);
+
+	tas3001c_set_mixer_level(self, SOUND_MIXER_VOLUME, VOL_DEFAULT<<8 | VOL_DEFAULT);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_PCM, INPUT_DEFAULT<<8 | INPUT_DEFAULT);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0);
+
+	tas3001c_set_mixer_level(self, SOUND_MIXER_BASS, BASS_DEFAULT);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_TREBLE, TREBLE_DEFAULT);
+
+	return 0;
+}
+
+static int
+tas3001c_uninit_mixer(struct tas3001c_data_t *self)
+{
+	tas3001c_set_mixer_level(self, SOUND_MIXER_VOLUME, 0);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_PCM,    0);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0);
+
+	tas3001c_set_mixer_level(self, SOUND_MIXER_BASS,   0);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_TREBLE, 0);
+
+	return 0;
+}
+
+static int
+tas3001c_init(struct i2c_client *client)
+{
+	struct tas3001c_data_t *self;
+	size_t sz = sizeof(*self) + (TAS3001C_REG_MAX*sizeof(tas_shadow_t));
+	int i, j;
+
+	self = kmalloc(sz, GFP_KERNEL);
+	if (!self)
+		return -ENOMEM;
+	memset(self, 0, sz);
+
+	self->super.client = client;
+	self->super.shadow = (tas_shadow_t *)(self+1);
+	self->output_id = TAS_OUTPUT_HEADPHONES;
+
+	dev_set_drvdata(&client->dev, self);
+
+	for (i = 0; i < TAS3001C_BIQUAD_CHANNEL_COUNT; i++)
+		for (j = 0; j < TAS3001C_BIQUAD_FILTER_COUNT; j++)
+			tas3001c_write_biquad_shadow(self, i, j,
+				&tas3001c_eq_unity);
+
+	INIT_WORK(&device_change, tas3001c_device_change_handler, self);
+	return 0;
+}
+
+static void
+tas3001c_uninit(struct tas3001c_data_t *self)
+{
+	tas3001c_uninit_mixer(self);
+	kfree(self);
+}
+
+struct tas_driver_hooks_t tas3001c_hooks = {
+	.init			= (tas_hook_init_t)tas3001c_init,
+	.post_init		= (tas_hook_post_init_t)tas3001c_init_mixer,
+	.uninit			= (tas_hook_uninit_t)tas3001c_uninit,
+	.get_mixer_level	= (tas_hook_get_mixer_level_t)tas3001c_get_mixer_level,
+	.set_mixer_level	= (tas_hook_set_mixer_level_t)tas3001c_set_mixer_level,
+	.enter_sleep		= (tas_hook_enter_sleep_t)tas3001c_enter_sleep,
+	.leave_sleep		= (tas_hook_leave_sleep_t)tas3001c_leave_sleep,
+	.supported_mixers	= (tas_hook_supported_mixers_t)tas3001c_supported_mixers,
+	.mixer_is_stereo	= (tas_hook_mixer_is_stereo_t)tas3001c_mixer_is_stereo,
+	.stereo_mixers		= (tas_hook_stereo_mixers_t)tas3001c_stereo_mixers,
+	.output_device_change	= (tas_hook_output_device_change_t)tas3001c_output_device_change,
+	.device_ioctl		= (tas_hook_device_ioctl_t)tas3001c_device_ioctl
+};
diff --git a/sound/oss/dmasound/tas3001c.h b/sound/oss/dmasound/tas3001c.h
new file mode 100644
index 0000000..3660da3
--- /dev/null
+++ b/sound/oss/dmasound/tas3001c.h
@@ -0,0 +1,64 @@
+/*
+ * Header file for the i2c/i2s based TA3001c sound chip used
+ * on some Apple hardware. Also known as "tumbler".
+ *
+ *  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.
+ *
+ * Written by Christopher C. Chimelis <chris@debian.org>
+ */
+
+#ifndef _TAS3001C_H_
+#define _TAS3001C_H_
+
+#include <linux/types.h>
+
+#include "tas_common.h"
+#include "tas_eq_prefs.h"
+
+/*
+ * Macros that correspond to the registers that we write to
+ * when setting the various values.
+ */
+
+#define TAS3001C_VERSION	"0.3"
+#define TAS3001C_DATE	        "20011214"
+
+#define I2C_DRIVERNAME_TAS3001C "TAS3001c driver V " TAS3001C_VERSION
+#define I2C_DRIVERID_TAS3001C   (I2C_DRIVERID_TAS_BASE+0)
+
+extern  struct tas_driver_hooks_t tas3001c_hooks;
+extern struct tas_gain_t tas3001c_gain;
+extern struct tas_eq_pref_t *tas3001c_eq_prefs[];
+
+enum tas3001c_reg_t {
+  TAS3001C_REG_MCR                    = 0x01,
+  TAS3001C_REG_DRC                    = 0x02,
+
+  TAS3001C_REG_VOLUME                 = 0x04,
+  TAS3001C_REG_TREBLE                 = 0x05,
+  TAS3001C_REG_BASS                   = 0x06,
+  TAS3001C_REG_MIXER1                 = 0x07,
+  TAS3001C_REG_MIXER2                 = 0x08,
+
+  TAS3001C_REG_LEFT_BIQUAD0           = 0x0a,
+  TAS3001C_REG_LEFT_BIQUAD1           = 0x0b,
+  TAS3001C_REG_LEFT_BIQUAD2           = 0x0c,
+  TAS3001C_REG_LEFT_BIQUAD3           = 0x0d,
+  TAS3001C_REG_LEFT_BIQUAD4           = 0x0e,
+  TAS3001C_REG_LEFT_BIQUAD5           = 0x0f,
+  TAS3001C_REG_LEFT_BIQUAD6           = 0x10,
+  
+  TAS3001C_REG_RIGHT_BIQUAD0          = 0x13,
+  TAS3001C_REG_RIGHT_BIQUAD1          = 0x14,
+  TAS3001C_REG_RIGHT_BIQUAD2          = 0x15,
+  TAS3001C_REG_RIGHT_BIQUAD3          = 0x16,
+  TAS3001C_REG_RIGHT_BIQUAD4          = 0x17,
+  TAS3001C_REG_RIGHT_BIQUAD5          = 0x18,
+  TAS3001C_REG_RIGHT_BIQUAD6          = 0x19,
+
+  TAS3001C_REG_MAX                    = 0x20
+};
+
+#endif /* _TAS3001C_H_ */
diff --git a/sound/oss/dmasound/tas3001c_tables.c b/sound/oss/dmasound/tas3001c_tables.c
new file mode 100644
index 0000000..1768fa9
--- /dev/null
+++ b/sound/oss/dmasound/tas3001c_tables.c
@@ -0,0 +1,375 @@
+#include "tas_common.h"
+#include "tas_eq_prefs.h"
+
+static struct tas_drce_t eqp_0e_2_1_drce = {
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = -15.33  * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_0e_2_1_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } },
+};
+
+static struct tas_eq_pref_t eqp_0e_2_1 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x0e,
+  .output_id     = TAS_OUTPUT_EXTERNAL_SPKR,
+  .speaker_id    = 0x01,
+
+  .drce          = &eqp_0e_2_1_drce,
+
+  .filter_count  = 12,
+  .biquads       = eqp_0e_2_1_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_10_1_0_drce={
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = -12.46  * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_10_1_0_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0F4A12, 0xE16BDA, 0x0F4A12, 0xE173F0, 0x0E9C3A } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x02DD54, 0x05BAA8, 0x02DD54, 0xF8001D, 0x037532 } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0E2FC7, 0xE4D5DC, 0x0D7477, 0xE4D5DC, 0x0BA43F } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0E7899, 0xE67CCA, 0x0D0E93, 0xE67CCA, 0x0B872D } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0F4A12, 0xE16BDA, 0x0F4A12, 0xE173F0, 0x0E9C3A } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x02DD54, 0x05BAA8, 0x02DD54, 0xF8001D, 0x037532 } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0E2FC7, 0xE4D5DC, 0x0D7477, 0xE4D5DC, 0x0BA43F } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0E7899, 0xE67CCA, 0x0D0E93, 0xE67CCA, 0x0B872D } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+};
+
+static struct tas_eq_pref_t eqp_10_1_0 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x10,
+  .output_id     = TAS_OUTPUT_INTERNAL_SPKR,
+  .speaker_id    = 0x00,
+
+  .drce          = &eqp_10_1_0_drce,
+
+  .filter_count  = 12,
+  .biquads       = eqp_10_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_15_2_1_drce={
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = -15.33  * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_15_2_1_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } },
+};
+
+static struct tas_eq_pref_t eqp_15_2_1 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x15,
+  .output_id     = TAS_OUTPUT_EXTERNAL_SPKR,
+  .speaker_id    = 0x01,
+
+  .drce          = &eqp_15_2_1_drce,
+
+  .filter_count  = 12,
+  .biquads       = eqp_15_2_1_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_15_1_0_drce={
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = 0.0     * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_15_1_0_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FAD08, 0xE0A5EF, 0x0FAD08, 0xE0A79D, 0x0F5BBE } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x04B38D, 0x09671B, 0x04B38D, 0x000F71, 0x02BEC5 } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FDD32, 0xE0A56F, 0x0F8A69, 0xE0A56F, 0x0F679C } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0FD284, 0xE135FB, 0x0F2161, 0xE135FB, 0x0EF3E5 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x0E81B1, 0xE6283F, 0x0CE49D, 0xE6283F, 0x0B664F } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x0F2D62, 0xE98797, 0x0D1E19, 0xE98797, 0x0C4B7B } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FAD08, 0xE0A5EF, 0x0FAD08, 0xE0A79D, 0x0F5BBE } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x04B38D, 0x09671B, 0x04B38D, 0x000F71, 0x02BEC5 } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FDD32, 0xE0A56F, 0x0F8A69, 0xE0A56F, 0x0F679C } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0FD284, 0xE135FB, 0x0F2161, 0xE135FB, 0x0EF3E5 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x0E81B1, 0xE6283F, 0x0CE49D, 0xE6283F, 0x0B664F } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x0F2D62, 0xE98797, 0x0D1E19, 0xE98797, 0x0C4B7B } } },
+};
+
+static struct tas_eq_pref_t eqp_15_1_0 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x15,
+  .output_id     = TAS_OUTPUT_INTERNAL_SPKR,
+  .speaker_id    = 0x00,
+
+  .drce          = &eqp_15_1_0_drce,
+
+  .filter_count  = 12,
+  .biquads       = eqp_15_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_0f_2_1_drce={
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = -15.33  * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_0f_2_1_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } },
+};
+
+static struct tas_eq_pref_t eqp_0f_2_1 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x0f,
+  .output_id     = TAS_OUTPUT_EXTERNAL_SPKR,
+  .speaker_id    = 0x01,
+
+  .drce          = &eqp_0f_2_1_drce,
+
+  .filter_count  = 12,
+  .biquads       = eqp_0f_2_1_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_0f_1_0_drce={
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = -15.33  * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_0f_1_0_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } },
+};
+
+static struct tas_eq_pref_t eqp_0f_1_0 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x0f,
+  .output_id     = TAS_OUTPUT_INTERNAL_SPKR,
+  .speaker_id    = 0x00,
+
+  .drce          = &eqp_0f_1_0_drce,
+
+  .filter_count  = 12,
+  .biquads       = eqp_0f_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static uint tas3001c_master_tab[]={
+	       0x0,       0x75,       0x9c,       0xbb,
+	      0xdb,       0xfb,      0x11e,      0x143,
+	     0x16b,      0x196,      0x1c3,      0x1f5,
+	     0x229,      0x263,      0x29f,      0x2e1,
+	     0x328,      0x373,      0x3c5,      0x41b,
+	     0x478,      0x4dc,      0x547,      0x5b8,
+	     0x633,      0x6b5,      0x740,      0x7d5,
+	     0x873,      0x91c,      0x9d2,      0xa92,
+	     0xb5e,      0xc39,      0xd22,      0xe19,
+	     0xf20,     0x1037,     0x1161,     0x129e,
+	    0x13ed,     0x1551,     0x16ca,     0x185d,
+	    0x1a08,     0x1bcc,     0x1dac,     0x1fa7,
+	    0x21c1,     0x23fa,     0x2655,     0x28d6,
+	    0x2b7c,     0x2e4a,     0x3141,     0x3464,
+	    0x37b4,     0x3b35,     0x3ee9,     0x42d3,
+	    0x46f6,     0x4b53,     0x4ff0,     0x54ce,
+	    0x59f2,     0x5f5f,     0x6519,     0x6b24,
+	    0x7183,     0x783c,     0x7f53,     0x86cc,
+	    0x8ead,     0x96fa,     0x9fba,     0xa8f2,
+	    0xb2a7,     0xbce1,     0xc7a5,     0xd2fa,
+	    0xdee8,     0xeb75,     0xf8aa,    0x1068e,
+	   0x1152a,    0x12487,    0x134ad,    0x145a5,
+	   0x1577b,    0x16a37,    0x17df5,    0x192bd,
+	   0x1a890,    0x1bf7b,    0x1d78d,    0x1f0d1,
+	   0x20b55,    0x22727,    0x24456,    0x262f2,
+	   0x2830b
+};
+
+static uint tas3001c_mixer_tab[]={
+	       0x0,      0x748,      0x9be,      0xbaf,
+	     0xda4,      0xfb1,     0x11de,     0x1431,
+	    0x16ad,     0x1959,     0x1c37,     0x1f4b,
+	    0x2298,     0x2628,     0x29fb,     0x2e12,
+	    0x327d,     0x3734,     0x3c47,     0x41b4,
+	    0x4787,     0x4dbe,     0x546d,     0x5b86,
+	    0x632e,     0x6b52,     0x7400,     0x7d54,
+	    0x873b,     0x91c6,     0x9d1a,     0xa920,
+	    0xb5e5,     0xc38c,     0xd21b,     0xe18f,
+	    0xf1f5,    0x1036a,    0x1160f,    0x129d6,
+	   0x13ed0,    0x1550c,    0x16ca0,    0x185c9,
+	   0x1a07b,    0x1bcc3,    0x1dab9,    0x1fa75,
+	   0x21c0f,    0x23fa3,    0x26552,    0x28d64,
+	   0x2b7c9,    0x2e4a2,    0x31411,    0x3463b,
+	   0x37b44,    0x3b353,    0x3ee94,    0x42d30,
+	   0x46f55,    0x4b533,    0x4fefc,    0x54ce5,
+	   0x59f25,    0x5f5f6,    0x65193,    0x6b23c,
+	   0x71835,    0x783c3,    0x7f52c,    0x86cc0,
+	   0x8eacc,    0x96fa5,    0x9fba0,    0xa8f1a,
+	   0xb2a71,    0xbce0a,    0xc7a4a,    0xd2fa0,
+	   0xdee7b,    0xeb752,    0xf8a9f,   0x1068e4,
+	  0x1152a3,   0x12486a,   0x134ac8,   0x145a55,
+	  0x1577ac,   0x16a370,   0x17df51,   0x192bc2,
+	  0x1a88f8,   0x1bf7b7,   0x1d78c9,   0x1f0d04,
+	  0x20b542,   0x227268,   0x244564,   0x262f26,
+	  0x2830af
+};
+
+static uint tas3001c_treble_tab[]={
+	      0x96,       0x95,       0x95,       0x94,
+	      0x93,       0x92,       0x92,       0x91,
+	      0x90,       0x90,       0x8f,       0x8e,
+	      0x8d,       0x8d,       0x8c,       0x8b,
+	      0x8a,       0x8a,       0x89,       0x88,
+	      0x88,       0x87,       0x86,       0x85,
+	      0x85,       0x84,       0x83,       0x83,
+	      0x82,       0x81,       0x80,       0x80,
+	      0x7f,       0x7e,       0x7e,       0x7d,
+	      0x7c,       0x7b,       0x7b,       0x7a,
+	      0x79,       0x78,       0x78,       0x77,
+	      0x76,       0x76,       0x75,       0x74,
+	      0x73,       0x73,       0x72,       0x71,
+	      0x71,       0x70,       0x6e,       0x6d,
+	      0x6d,       0x6c,       0x6b,       0x6a,
+	      0x69,       0x68,       0x67,       0x66,
+	      0x65,       0x63,       0x62,       0x62,
+	      0x60,       0x5f,       0x5d,       0x5c,
+	      0x5a,       0x58,       0x56,       0x55,
+	      0x53,       0x51,       0x4f,       0x4c,
+	      0x4a,       0x48,       0x45,       0x43,
+	      0x40,       0x3d,       0x3a,       0x37,
+	      0x35,       0x32,       0x2e,       0x2a,
+	      0x27,       0x22,       0x1e,       0x1a,
+	      0x15,       0x11,        0xc,        0x7,
+	       0x1
+};
+
+static uint tas3001c_bass_tab[]={
+	      0x86,       0x83,       0x81,       0x7f,
+	      0x7d,       0x7b,       0x79,       0x78,
+	      0x76,       0x75,       0x74,       0x72,
+	      0x71,       0x6f,       0x6e,       0x6d,
+	      0x6c,       0x6b,       0x69,       0x67,
+	      0x65,       0x64,       0x61,       0x60,
+	      0x5e,       0x5d,       0x5c,       0x5b,
+	      0x5a,       0x59,       0x58,       0x57,
+	      0x56,       0x55,       0x55,       0x54,
+	      0x53,       0x52,       0x50,       0x4f,
+	      0x4d,       0x4c,       0x4b,       0x49,
+	      0x47,       0x45,       0x44,       0x42,
+	      0x41,       0x3f,       0x3e,       0x3d,
+	      0x3c,       0x3b,       0x39,       0x38,
+	      0x37,       0x36,       0x35,       0x34,
+	      0x33,       0x31,       0x30,       0x2f,
+	      0x2e,       0x2c,       0x2b,       0x2b,
+	      0x29,       0x28,       0x27,       0x26,
+	      0x25,       0x24,       0x22,       0x21,
+	      0x20,       0x1e,       0x1c,       0x19,
+	      0x18,       0x18,       0x17,       0x16,
+	      0x15,       0x14,       0x13,       0x12,
+	      0x11,       0x10,        0xf,        0xe,
+	       0xd,        0xb,        0xa,        0x9,
+	       0x8,        0x6,        0x4,        0x2,
+	       0x1
+};
+
+struct tas_gain_t tas3001c_gain = {
+  .master  = tas3001c_master_tab,
+  .treble  = tas3001c_treble_tab,
+  .bass    = tas3001c_bass_tab,
+  .mixer   = tas3001c_mixer_tab
+};
+
+struct tas_eq_pref_t *tas3001c_eq_prefs[]={
+  &eqp_0e_2_1,
+  &eqp_10_1_0,
+  &eqp_15_2_1,
+  &eqp_15_1_0,
+  &eqp_0f_2_1,
+  &eqp_0f_1_0,
+  NULL
+};
diff --git a/sound/oss/dmasound/tas3004.c b/sound/oss/dmasound/tas3004.c
new file mode 100644
index 0000000..82eaaca
--- /dev/null
+++ b/sound/oss/dmasound/tas3004.c
@@ -0,0 +1,1140 @@
+/*
+ * Driver for the i2c/i2s based TA3004 sound chip used
+ * on some Apple hardware. Also known as "snapper".
+ *
+ * Tobias Sargeant <tobias.sargeant@bigpond.com>
+ * Based upon tas3001c.c by Christopher C. Chimelis <chris@debian.org>:
+ *
+ * Input support by Renzo Davoli <renzo@cs.unibo.it>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/ioport.h>
+#include <linux/sysctl.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/soundcard.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+
+#include <asm/uaccess.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+
+#include "dmasound.h"
+#include "tas_common.h"
+#include "tas3004.h"
+
+#include "tas_ioctl.h"
+
+/* #define DEBUG_DRCE */
+
+#define TAS3004_BIQUAD_FILTER_COUNT  7
+#define TAS3004_BIQUAD_CHANNEL_COUNT 2
+
+#define VOL_DEFAULT	(100 * 4 / 5)
+#define INPUT_DEFAULT	(100 * 4 / 5)
+#define BASS_DEFAULT	(100 / 2)
+#define TREBLE_DEFAULT	(100 / 2)
+
+struct tas3004_data_t {
+	struct tas_data_t super;
+	int device_id;
+	int output_id;
+	int speaker_id;
+	struct tas_drce_t drce_state;
+};
+
+#define MAKE_TIME(sec,usec) (((sec)<<12) + (50000+(usec/10)*(1<<12))/100000)
+
+#define MAKE_RATIO(i,f) (((i)<<8) + ((500+(f)*(1<<8))/1000))
+
+
+static const union tas_biquad_t tas3004_eq_unity = {
+	.buf		 = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 },
+};
+
+
+static const struct tas_drce_t tas3004_drce_min = {
+	.enable		= 1,
+	.above		= { .val = MAKE_RATIO(16,0), .expand = 0 },
+	.below		= { .val = MAKE_RATIO(2,0), .expand = 0 },
+	.threshold	= -0x59a0,
+	.energy		= MAKE_TIME(0,  1700),
+	.attack		= MAKE_TIME(0,  1700),
+	.decay		= MAKE_TIME(0,  1700),
+};
+
+
+static const struct tas_drce_t tas3004_drce_max = {
+	.enable		= 1,
+	.above		= { .val = MAKE_RATIO(1,500), .expand = 1 },
+	.below		= { .val = MAKE_RATIO(2,0), .expand = 1 },
+	.threshold	= -0x0,
+	.energy		= MAKE_TIME(2,400000),
+	.attack		= MAKE_TIME(2,400000),
+	.decay		= MAKE_TIME(2,400000),
+};
+
+
+static const unsigned short time_constants[]={
+	MAKE_TIME(0,  1700),
+	MAKE_TIME(0,  3500),
+	MAKE_TIME(0,  6700),
+	MAKE_TIME(0, 13000),
+	MAKE_TIME(0, 26000),
+	MAKE_TIME(0, 53000),
+	MAKE_TIME(0,106000),
+	MAKE_TIME(0,212000),
+	MAKE_TIME(0,425000),
+	MAKE_TIME(0,850000),
+	MAKE_TIME(1,700000),
+	MAKE_TIME(2,400000),
+};
+
+static const unsigned short above_threshold_compression_ratio[]={
+	MAKE_RATIO( 1, 70),
+	MAKE_RATIO( 1,140),
+	MAKE_RATIO( 1,230),
+	MAKE_RATIO( 1,330),
+	MAKE_RATIO( 1,450),
+	MAKE_RATIO( 1,600),
+	MAKE_RATIO( 1,780),
+	MAKE_RATIO( 2,  0),
+	MAKE_RATIO( 2,290),
+	MAKE_RATIO( 2,670),
+	MAKE_RATIO( 3,200),
+	MAKE_RATIO( 4,  0),
+	MAKE_RATIO( 5,330),
+	MAKE_RATIO( 8,  0),
+	MAKE_RATIO(16,  0),
+};
+
+static const unsigned short above_threshold_expansion_ratio[]={
+	MAKE_RATIO(1, 60),
+	MAKE_RATIO(1,130),
+	MAKE_RATIO(1,190),
+	MAKE_RATIO(1,250),
+	MAKE_RATIO(1,310),
+	MAKE_RATIO(1,380),
+	MAKE_RATIO(1,440),
+	MAKE_RATIO(1,500)
+};
+
+static const unsigned short below_threshold_compression_ratio[]={
+	MAKE_RATIO(1, 70),
+	MAKE_RATIO(1,140),
+	MAKE_RATIO(1,230),
+	MAKE_RATIO(1,330),
+	MAKE_RATIO(1,450),
+	MAKE_RATIO(1,600),
+	MAKE_RATIO(1,780),
+	MAKE_RATIO(2,  0)
+};
+
+static const unsigned short below_threshold_expansion_ratio[]={
+	MAKE_RATIO(1, 60),
+	MAKE_RATIO(1,130),
+	MAKE_RATIO(1,190),
+	MAKE_RATIO(1,250),
+	MAKE_RATIO(1,310),
+	MAKE_RATIO(1,380),
+	MAKE_RATIO(1,440),
+	MAKE_RATIO(1,500),
+	MAKE_RATIO(1,560),
+	MAKE_RATIO(1,630),
+	MAKE_RATIO(1,690),
+	MAKE_RATIO(1,750),
+	MAKE_RATIO(1,810),
+	MAKE_RATIO(1,880),
+	MAKE_RATIO(1,940),
+	MAKE_RATIO(2,  0)
+};
+
+static inline int
+search(	unsigned short val,
+	const unsigned short *arr,
+	const int arrsize) {
+	/*
+	 * This could be a binary search, but for small tables,
+	 * a linear search is likely to be faster
+	 */
+
+	int i;
+
+	for (i=0; i < arrsize; i++)
+		if (arr[i] >= val)
+			goto _1;
+	return arrsize-1;
+ _1:
+	if (i == 0)
+		return 0;
+	return (arr[i]-val < val-arr[i-1]) ? i : i-1;
+}
+
+#define SEARCH(a, b) search(a, b, ARRAY_SIZE(b))
+
+static inline int
+time_index(unsigned short time)
+{
+	return SEARCH(time, time_constants);
+}
+
+
+static inline int
+above_threshold_compression_index(unsigned short ratio)
+{
+	return SEARCH(ratio, above_threshold_compression_ratio);
+}
+
+
+static inline int
+above_threshold_expansion_index(unsigned short ratio)
+{
+	return SEARCH(ratio, above_threshold_expansion_ratio);
+}
+
+
+static inline int
+below_threshold_compression_index(unsigned short ratio)
+{
+	return SEARCH(ratio, below_threshold_compression_ratio);
+}
+
+
+static inline int
+below_threshold_expansion_index(unsigned short ratio)
+{
+	return SEARCH(ratio, below_threshold_expansion_ratio);
+}
+
+static inline unsigned char db_to_regval(short db) {
+	int r=0;
+
+	r=(db+0x59a0) / 0x60;
+
+	if (r < 0x91) return 0x91;
+	if (r > 0xef) return 0xef;
+	return r;
+}
+
+static inline short quantize_db(short db)
+{
+	return db_to_regval(db) * 0x60 - 0x59a0;
+}
+
+static inline int
+register_width(enum tas3004_reg_t r)
+{
+	switch(r) {
+	case TAS3004_REG_MCR:
+ 	case TAS3004_REG_TREBLE:
+	case TAS3004_REG_BASS:
+	case TAS3004_REG_ANALOG_CTRL:
+	case TAS3004_REG_TEST1:
+	case TAS3004_REG_TEST2:
+	case TAS3004_REG_MCR2:
+		return 1;
+
+	case TAS3004_REG_LEFT_LOUD_BIQUAD_GAIN:
+	case TAS3004_REG_RIGHT_LOUD_BIQUAD_GAIN:
+		return 3;
+
+	case TAS3004_REG_DRC:
+	case TAS3004_REG_VOLUME:
+		return 6;
+
+	case TAS3004_REG_LEFT_MIXER:
+	case TAS3004_REG_RIGHT_MIXER:
+		return 9;
+
+	case TAS3004_REG_TEST:
+		return 10;
+
+	case TAS3004_REG_LEFT_BIQUAD0:
+	case TAS3004_REG_LEFT_BIQUAD1:
+	case TAS3004_REG_LEFT_BIQUAD2:
+	case TAS3004_REG_LEFT_BIQUAD3:
+	case TAS3004_REG_LEFT_BIQUAD4:
+	case TAS3004_REG_LEFT_BIQUAD5:
+	case TAS3004_REG_LEFT_BIQUAD6:
+
+	case TAS3004_REG_RIGHT_BIQUAD0:
+	case TAS3004_REG_RIGHT_BIQUAD1:
+	case TAS3004_REG_RIGHT_BIQUAD2:
+	case TAS3004_REG_RIGHT_BIQUAD3:
+	case TAS3004_REG_RIGHT_BIQUAD4:
+	case TAS3004_REG_RIGHT_BIQUAD5:
+	case TAS3004_REG_RIGHT_BIQUAD6:
+
+	case TAS3004_REG_LEFT_LOUD_BIQUAD:
+	case TAS3004_REG_RIGHT_LOUD_BIQUAD:
+		return 15;
+
+	default:
+		return 0;
+	}
+}
+
+static int
+tas3004_write_register(	struct tas3004_data_t *self,
+			enum tas3004_reg_t reg_num,
+			char *data,
+			uint write_mode)
+{
+	if (reg_num==TAS3004_REG_MCR ||
+	    reg_num==TAS3004_REG_BASS ||
+	    reg_num==TAS3004_REG_TREBLE ||
+	    reg_num==TAS3004_REG_ANALOG_CTRL) {
+		return tas_write_byte_register(&self->super,
+					       (uint)reg_num,
+					       *data,
+					       write_mode);
+	} else {
+		return tas_write_register(&self->super,
+					  (uint)reg_num,
+					  register_width(reg_num),
+					  data,
+					  write_mode);
+	}
+}
+
+static int
+tas3004_sync_register(	struct tas3004_data_t *self,
+			enum tas3004_reg_t reg_num)
+{
+	if (reg_num==TAS3004_REG_MCR ||
+	    reg_num==TAS3004_REG_BASS ||
+	    reg_num==TAS3004_REG_TREBLE ||
+	    reg_num==TAS3004_REG_ANALOG_CTRL) {
+		return tas_sync_byte_register(&self->super,
+					      (uint)reg_num,
+					      register_width(reg_num));
+	} else {
+		return tas_sync_register(&self->super,
+					 (uint)reg_num,
+					 register_width(reg_num));
+	}
+}
+
+static int
+tas3004_read_register(	struct tas3004_data_t *self,
+			enum tas3004_reg_t reg_num,
+			char *data,
+			uint write_mode)
+{
+	return tas_read_register(&self->super,
+				 (uint)reg_num,
+				 register_width(reg_num),
+				 data);
+}
+
+static inline int
+tas3004_fast_load(struct tas3004_data_t *self, int fast)
+{
+	if (fast)
+		self->super.shadow[TAS3004_REG_MCR][0] |= 0x80;
+	else
+		self->super.shadow[TAS3004_REG_MCR][0] &= 0x7f;
+	return tas3004_sync_register(self,TAS3004_REG_MCR);
+}
+
+static uint
+tas3004_supported_mixers(struct tas3004_data_t *self)
+{
+	return SOUND_MASK_VOLUME |
+		SOUND_MASK_PCM |
+		SOUND_MASK_ALTPCM |
+		SOUND_MASK_IMIX |
+		SOUND_MASK_TREBLE |
+		SOUND_MASK_BASS |
+		SOUND_MASK_MIC |
+		SOUND_MASK_LINE;
+}
+
+static int
+tas3004_mixer_is_stereo(struct tas3004_data_t *self, int mixer)
+{
+	switch(mixer) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_PCM:
+	case SOUND_MIXER_ALTPCM:
+	case SOUND_MIXER_IMIX:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static uint
+tas3004_stereo_mixers(struct tas3004_data_t *self)
+{
+	uint r = tas3004_supported_mixers(self);
+	uint i;
+	
+	for (i=1; i<SOUND_MIXER_NRDEVICES; i++)
+		if (r&(1<<i) && !tas3004_mixer_is_stereo(self,i))
+			r &= ~(1<<i);
+	return r;
+}
+
+static int
+tas3004_get_mixer_level(struct tas3004_data_t *self, int mixer, uint *level)
+{
+	if (!self)
+		return -1;
+
+	*level = self->super.mixer[mixer];
+
+	return 0;
+}
+
+static int
+tas3004_set_mixer_level(struct tas3004_data_t *self, int mixer, uint level)
+{
+	int rc;
+	tas_shadow_t *shadow;
+	uint temp;
+	uint offset=0;
+
+	if (!self)
+		return -1;
+
+	shadow = self->super.shadow;
+
+	if (!tas3004_mixer_is_stereo(self,mixer))
+		level = tas_mono_to_stereo(level);
+	switch(mixer) {
+	case SOUND_MIXER_VOLUME:
+		temp = tas3004_gain.master[level&0xff];
+		SET_4_20(shadow[TAS3004_REG_VOLUME], 0, temp);
+		temp = tas3004_gain.master[(level>>8)&0xff];
+		SET_4_20(shadow[TAS3004_REG_VOLUME], 3, temp);
+		rc = tas3004_sync_register(self,TAS3004_REG_VOLUME);
+		break;
+	case SOUND_MIXER_IMIX:
+		offset += 3;
+	case SOUND_MIXER_ALTPCM:
+		offset += 3;
+	case SOUND_MIXER_PCM:
+		/*
+		 * Don't load these in fast mode. The documentation
+		 * says it can be done in either mode, but testing it
+		 * shows that fast mode produces ugly clicking.
+		*/
+		/* tas3004_fast_load(self,1); */
+		temp = tas3004_gain.mixer[level&0xff];
+		SET_4_20(shadow[TAS3004_REG_LEFT_MIXER], offset, temp);
+		temp = tas3004_gain.mixer[(level>>8)&0xff];
+		SET_4_20(shadow[TAS3004_REG_RIGHT_MIXER], offset, temp);
+		rc = tas3004_sync_register(self,TAS3004_REG_LEFT_MIXER);
+		if (rc == 0)
+			rc=tas3004_sync_register(self,TAS3004_REG_RIGHT_MIXER);
+		/* tas3004_fast_load(self,0); */
+		break;
+	case SOUND_MIXER_TREBLE:
+		temp = tas3004_gain.treble[level&0xff];
+		shadow[TAS3004_REG_TREBLE][0]=temp&0xff;
+		rc = tas3004_sync_register(self,TAS3004_REG_TREBLE);
+		break;
+	case SOUND_MIXER_BASS:
+		temp = tas3004_gain.bass[level&0xff];
+		shadow[TAS3004_REG_BASS][0]=temp&0xff;
+		rc = tas3004_sync_register(self,TAS3004_REG_BASS);
+		break;
+	case SOUND_MIXER_MIC:
+		if ((level&0xff)>0) {
+			software_input_volume = SW_INPUT_VOLUME_SCALE * (level&0xff);
+			if (self->super.mixer[mixer] == 0) {
+				self->super.mixer[SOUND_MIXER_LINE] = 0;
+				shadow[TAS3004_REG_ANALOG_CTRL][0]=0xc2;
+				rc = tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL);
+			} else rc=0;
+		} else {
+			self->super.mixer[SOUND_MIXER_LINE] = SW_INPUT_VOLUME_DEFAULT;
+			software_input_volume = SW_INPUT_VOLUME_SCALE *
+				(self->super.mixer[SOUND_MIXER_LINE]&0xff);
+			shadow[TAS3004_REG_ANALOG_CTRL][0]=0x00;
+			rc = tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL);
+		}
+		break;
+	case SOUND_MIXER_LINE:
+		if (self->super.mixer[SOUND_MIXER_MIC] == 0) {
+			software_input_volume = SW_INPUT_VOLUME_SCALE * (level&0xff);
+			rc=0;
+		}
+		break;
+	default:
+		rc = -1;
+		break;
+	}
+	if (rc < 0)
+		return rc;
+	self->super.mixer[mixer] = level;
+	
+	return 0;
+}
+
+static int
+tas3004_leave_sleep(struct tas3004_data_t *self)
+{
+	unsigned char mcr = (1<<6)+(2<<4)+(2<<2);
+
+	if (!self)
+		return -1;
+
+	/* Make sure something answers on the i2c bus */
+	if (tas3004_write_register(self, TAS3004_REG_MCR, &mcr,
+	    WRITE_NORMAL | FORCE_WRITE) < 0)
+		return -1;
+
+	tas3004_fast_load(self, 1);
+
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD0);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD1);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD2);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD3);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD4);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD5);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD6);
+
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD0);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD1);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD2);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD3);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD4);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD5);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD6);
+
+	tas3004_fast_load(self, 0);
+
+	(void)tas3004_sync_register(self,TAS3004_REG_VOLUME);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_MIXER);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_MIXER);
+	(void)tas3004_sync_register(self,TAS3004_REG_TREBLE);
+	(void)tas3004_sync_register(self,TAS3004_REG_BASS);
+	(void)tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL);
+
+	return 0;
+}
+
+static int
+tas3004_enter_sleep(struct tas3004_data_t *self)
+{
+	if (!self)
+		return -1; 
+	return 0;
+}
+
+static int
+tas3004_sync_biquad(	struct tas3004_data_t *self,
+			u_int channel,
+			u_int filter)
+{
+	enum tas3004_reg_t reg;
+
+	if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_REG_LEFT_BIQUAD0 ) + filter;
+
+	return tas3004_sync_register(self,reg);
+}
+
+static int
+tas3004_write_biquad_shadow(	struct tas3004_data_t *self,
+				u_int channel,
+				u_int filter,
+				const union tas_biquad_t *biquad)
+{
+	tas_shadow_t *shadow=self->super.shadow;
+	enum tas3004_reg_t reg;
+
+	if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_REG_LEFT_BIQUAD0 ) + filter;
+
+	SET_4_20(shadow[reg], 0,biquad->coeff.b0);
+	SET_4_20(shadow[reg], 3,biquad->coeff.b1);
+	SET_4_20(shadow[reg], 6,biquad->coeff.b2);
+	SET_4_20(shadow[reg], 9,biquad->coeff.a1);
+	SET_4_20(shadow[reg],12,biquad->coeff.a2);
+
+	return 0;
+}
+
+static int
+tas3004_write_biquad(	struct tas3004_data_t *self,
+			u_int channel,
+			u_int filter,
+			const union tas_biquad_t *biquad)
+{
+	int rc;
+
+	rc=tas3004_write_biquad_shadow(self, channel, filter, biquad);
+	if (rc < 0) return rc;
+
+	return tas3004_sync_biquad(self, channel, filter);
+}
+
+static int
+tas3004_write_biquad_list(	struct tas3004_data_t *self,
+				u_int filter_count,
+				u_int flags,
+				struct tas_biquad_ctrl_t *biquads)
+{
+	int i;
+	int rc;
+
+	if (flags & TAS_BIQUAD_FAST_LOAD) tas3004_fast_load(self,1);
+
+	for (i=0; i<filter_count; i++) {
+		rc=tas3004_write_biquad(self,
+					biquads[i].channel,
+					biquads[i].filter,
+					&biquads[i].data);
+		if (rc < 0) break;
+	}
+
+	if (flags & TAS_BIQUAD_FAST_LOAD) tas3004_fast_load(self,0);
+
+	return rc;
+}
+
+static int
+tas3004_read_biquad(	struct tas3004_data_t *self,
+			u_int channel,
+			u_int filter,
+			union tas_biquad_t *biquad)
+{
+	tas_shadow_t *shadow=self->super.shadow;
+	enum tas3004_reg_t reg;
+
+	if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_REG_LEFT_BIQUAD0 ) + filter;
+
+	biquad->coeff.b0=GET_4_20(shadow[reg], 0);
+	biquad->coeff.b1=GET_4_20(shadow[reg], 3);
+	biquad->coeff.b2=GET_4_20(shadow[reg], 6);
+	biquad->coeff.a1=GET_4_20(shadow[reg], 9);
+	biquad->coeff.a2=GET_4_20(shadow[reg],12);
+	
+	return 0;	
+}
+
+static int
+tas3004_eq_rw(	struct tas3004_data_t *self,
+		u_int cmd,
+		u_long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int rc;
+	struct tas_biquad_ctrl_t biquad;
+
+	if (copy_from_user((void *)&biquad, argp, sizeof(struct tas_biquad_ctrl_t))) {
+		return -EFAULT;
+	}
+
+	if (cmd & SIOC_IN) {
+		rc=tas3004_write_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+		if (rc != 0) return rc;
+	}
+
+	if (cmd & SIOC_OUT) {
+		rc=tas3004_read_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+		if (rc != 0) return rc;
+
+		if (copy_to_user(argp, &biquad, sizeof(struct tas_biquad_ctrl_t))) {
+			return -EFAULT;
+		}
+
+	}
+	return 0;
+}
+
+static int
+tas3004_eq_list_rw(	struct tas3004_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	int rc = 0;
+	int filter_count;
+	int flags;
+	int i,j;
+	char sync_required[TAS3004_BIQUAD_CHANNEL_COUNT][TAS3004_BIQUAD_FILTER_COUNT];
+	struct tas_biquad_ctrl_t biquad;
+	struct tas_biquad_ctrl_list_t __user *argp = (void __user *)arg;
+
+	memset(sync_required,0,sizeof(sync_required));
+
+	if (copy_from_user(&filter_count, &argp->filter_count, sizeof(int)))
+		return -EFAULT;
+
+	if (copy_from_user(&flags, &argp->flags, sizeof(int)))
+		return -EFAULT;
+
+	if (cmd & SIOC_IN) {
+	}
+
+	for (i=0; i < filter_count; i++) {
+		if (copy_from_user(&biquad, &argp->biquads[i],
+				   sizeof(struct tas_biquad_ctrl_t))) {
+			return -EFAULT;
+		}
+
+		if (cmd & SIOC_IN) {
+			sync_required[biquad.channel][biquad.filter]=1;
+			rc=tas3004_write_biquad_shadow(self, biquad.channel, biquad.filter, &biquad.data);
+			if (rc != 0) return rc;
+		}
+
+		if (cmd & SIOC_OUT) {
+			rc=tas3004_read_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+			if (rc != 0) return rc;
+
+			if (copy_to_user(&argp->biquads[i], &biquad,
+					 sizeof(struct tas_biquad_ctrl_t))) {
+				return -EFAULT;
+			}
+		}
+	}
+
+	if (cmd & SIOC_IN) {
+		/*
+		 * This is OK for the tas3004. For the
+		 * tas3001c, going into fast load mode causes
+		 * the treble and bass to be reset to 0dB, and
+		 * volume controls to be muted.
+		 */
+		if (flags & TAS_BIQUAD_FAST_LOAD) tas3004_fast_load(self,1);
+		for (i=0; i<TAS3004_BIQUAD_CHANNEL_COUNT; i++) {
+			for (j=0; j<TAS3004_BIQUAD_FILTER_COUNT; j++) {
+				if (sync_required[i][j]) {
+					rc=tas3004_sync_biquad(self, i, j);
+					if (rc < 0) goto out;
+				}
+			}
+		}
+	out:
+		if (flags & TAS_BIQUAD_FAST_LOAD)
+			tas3004_fast_load(self,0);
+	}
+
+	return rc;
+}
+
+static int
+tas3004_update_drce(	struct tas3004_data_t *self,
+			int flags,
+			struct tas_drce_t *drce)
+{
+	tas_shadow_t *shadow;
+	int i;
+	shadow=self->super.shadow;
+
+	if (flags & TAS_DRCE_ABOVE_RATIO) {
+		self->drce_state.above.expand = drce->above.expand;
+		if (drce->above.val == (1<<8)) {
+			self->drce_state.above.val = 1<<8;
+			shadow[TAS3004_REG_DRC][0] = 0x02;
+					
+		} else if (drce->above.expand) {
+			i=above_threshold_expansion_index(drce->above.val);
+			self->drce_state.above.val=above_threshold_expansion_ratio[i];
+			shadow[TAS3004_REG_DRC][0] = 0x0a + (i<<3);
+		} else {
+			i=above_threshold_compression_index(drce->above.val);
+			self->drce_state.above.val=above_threshold_compression_ratio[i];
+			shadow[TAS3004_REG_DRC][0] = 0x08 + (i<<3);
+		}
+	}
+
+	if (flags & TAS_DRCE_BELOW_RATIO) {
+		self->drce_state.below.expand = drce->below.expand;
+		if (drce->below.val == (1<<8)) {
+			self->drce_state.below.val = 1<<8;
+			shadow[TAS3004_REG_DRC][1] = 0x02;
+					
+		} else if (drce->below.expand) {
+			i=below_threshold_expansion_index(drce->below.val);
+			self->drce_state.below.val=below_threshold_expansion_ratio[i];
+			shadow[TAS3004_REG_DRC][1] = 0x08 + (i<<3);
+		} else {
+			i=below_threshold_compression_index(drce->below.val);
+			self->drce_state.below.val=below_threshold_compression_ratio[i];
+			shadow[TAS3004_REG_DRC][1] = 0x0a + (i<<3);
+		}
+	}
+
+	if (flags & TAS_DRCE_THRESHOLD) {
+		self->drce_state.threshold=quantize_db(drce->threshold);
+		shadow[TAS3004_REG_DRC][2] = db_to_regval(self->drce_state.threshold);
+	}
+
+	if (flags & TAS_DRCE_ENERGY) {
+		i=time_index(drce->energy);
+		self->drce_state.energy=time_constants[i];
+		shadow[TAS3004_REG_DRC][3] = 0x40 + (i<<4);
+	}
+
+	if (flags & TAS_DRCE_ATTACK) {
+		i=time_index(drce->attack);
+		self->drce_state.attack=time_constants[i];
+		shadow[TAS3004_REG_DRC][4] = 0x40 + (i<<4);
+	}
+
+	if (flags & TAS_DRCE_DECAY) {
+		i=time_index(drce->decay);
+		self->drce_state.decay=time_constants[i];
+		shadow[TAS3004_REG_DRC][5] = 0x40 + (i<<4);
+	}
+
+	if (flags & TAS_DRCE_ENABLE) {
+		self->drce_state.enable = drce->enable;
+	}
+
+	if (!self->drce_state.enable) {
+		shadow[TAS3004_REG_DRC][0] |= 0x01;
+	}
+
+#ifdef DEBUG_DRCE
+	printk("DRCE: set [ ENABLE:%x ABOVE:%x/%x BELOW:%x/%x THRESH:%x ENERGY:%x ATTACK:%x DECAY:%x\n",
+	       self->drce_state.enable,
+	       self->drce_state.above.expand,self->drce_state.above.val,
+	       self->drce_state.below.expand,self->drce_state.below.val,
+	       self->drce_state.threshold,
+	       self->drce_state.energy,
+	       self->drce_state.attack,
+	       self->drce_state.decay);
+
+	printk("DRCE: reg [ %02x %02x %02x %02x %02x %02x ]\n",
+	       (unsigned char)shadow[TAS3004_REG_DRC][0],
+	       (unsigned char)shadow[TAS3004_REG_DRC][1],
+	       (unsigned char)shadow[TAS3004_REG_DRC][2],
+	       (unsigned char)shadow[TAS3004_REG_DRC][3],
+	       (unsigned char)shadow[TAS3004_REG_DRC][4],
+	       (unsigned char)shadow[TAS3004_REG_DRC][5]);
+#endif
+
+	return tas3004_sync_register(self, TAS3004_REG_DRC);
+}
+
+static int
+tas3004_drce_rw(	struct tas3004_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	int rc;
+	struct tas_drce_ctrl_t drce_ctrl;
+	void __user *argp = (void __user *)arg;
+
+	if (copy_from_user(&drce_ctrl, argp, sizeof(struct tas_drce_ctrl_t)))
+		return -EFAULT;
+
+#ifdef DEBUG_DRCE
+	printk("DRCE: input [ FLAGS:%x ENABLE:%x ABOVE:%x/%x BELOW:%x/%x THRESH:%x ENERGY:%x ATTACK:%x DECAY:%x\n",
+	       drce_ctrl.flags,
+	       drce_ctrl.data.enable,
+	       drce_ctrl.data.above.expand,drce_ctrl.data.above.val,
+	       drce_ctrl.data.below.expand,drce_ctrl.data.below.val,
+	       drce_ctrl.data.threshold,
+	       drce_ctrl.data.energy,
+	       drce_ctrl.data.attack,
+	       drce_ctrl.data.decay);
+#endif
+
+	if (cmd & SIOC_IN) {
+		rc = tas3004_update_drce(self, drce_ctrl.flags, &drce_ctrl.data);
+		if (rc < 0) return rc;
+	}
+
+	if (cmd & SIOC_OUT) {
+		if (drce_ctrl.flags & TAS_DRCE_ENABLE)
+			drce_ctrl.data.enable = self->drce_state.enable;
+		if (drce_ctrl.flags & TAS_DRCE_ABOVE_RATIO)
+			drce_ctrl.data.above = self->drce_state.above;
+		if (drce_ctrl.flags & TAS_DRCE_BELOW_RATIO)
+			drce_ctrl.data.below = self->drce_state.below;
+		if (drce_ctrl.flags & TAS_DRCE_THRESHOLD)
+			drce_ctrl.data.threshold = self->drce_state.threshold;
+		if (drce_ctrl.flags & TAS_DRCE_ENERGY)
+			drce_ctrl.data.energy = self->drce_state.energy;
+		if (drce_ctrl.flags & TAS_DRCE_ATTACK)
+			drce_ctrl.data.attack = self->drce_state.attack;
+		if (drce_ctrl.flags & TAS_DRCE_DECAY)
+			drce_ctrl.data.decay = self->drce_state.decay;
+
+		if (copy_to_user(argp, &drce_ctrl,
+				 sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+	}
+
+	return 0;
+}
+
+static void
+tas3004_update_device_parameters(struct tas3004_data_t *self)
+{
+	char data;
+	int i;
+
+	if (!self) return;
+
+	if (self->output_id == TAS_OUTPUT_HEADPHONES) {
+		/* turn on allPass when headphones are plugged in */
+		data = 0x02;
+	} else {
+		data = 0x00;
+	}
+
+	tas3004_write_register(self, TAS3004_REG_MCR2, &data, WRITE_NORMAL | FORCE_WRITE);
+
+	for (i=0; tas3004_eq_prefs[i]; i++) {
+		struct tas_eq_pref_t *eq = tas3004_eq_prefs[i];
+
+		if (eq->device_id == self->device_id &&
+		    (eq->output_id == 0 || eq->output_id == self->output_id) &&
+		    (eq->speaker_id == 0 || eq->speaker_id == self->speaker_id)) {
+
+			tas3004_update_drce(self, TAS_DRCE_ALL, eq->drce);
+			tas3004_write_biquad_list(self, eq->filter_count, TAS_BIQUAD_FAST_LOAD, eq->biquads);
+
+			break;
+		}
+	}
+}
+
+static void
+tas3004_device_change_handler(void *self)
+{
+	if (!self) return;
+
+	tas3004_update_device_parameters((struct tas3004_data_t *)self);
+}
+
+static struct work_struct device_change;
+
+static int
+tas3004_output_device_change(	struct tas3004_data_t *self,
+				int device_id,
+				int output_id,
+				int speaker_id)
+{
+	self->device_id=device_id;
+	self->output_id=output_id;
+	self->speaker_id=speaker_id;
+
+	schedule_work(&device_change);
+
+	return 0;
+}
+
+static int
+tas3004_device_ioctl(	struct tas3004_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	uint __user *argp = (void __user *)arg;
+	switch (cmd) {
+	case TAS_READ_EQ:
+	case TAS_WRITE_EQ:
+		return tas3004_eq_rw(self, cmd, arg);
+
+	case TAS_READ_EQ_LIST:
+	case TAS_WRITE_EQ_LIST:
+		return tas3004_eq_list_rw(self, cmd, arg);
+
+	case TAS_READ_EQ_FILTER_COUNT:
+		put_user(TAS3004_BIQUAD_FILTER_COUNT, argp);
+		return 0;
+
+	case TAS_READ_EQ_CHANNEL_COUNT:
+		put_user(TAS3004_BIQUAD_CHANNEL_COUNT, argp);
+		return 0;
+
+	case TAS_READ_DRCE:
+	case TAS_WRITE_DRCE:
+		return tas3004_drce_rw(self, cmd, arg);
+
+	case TAS_READ_DRCE_CAPS:
+		put_user(TAS_DRCE_ENABLE         |
+			 TAS_DRCE_ABOVE_RATIO    |
+			 TAS_DRCE_BELOW_RATIO    |
+			 TAS_DRCE_THRESHOLD      |
+			 TAS_DRCE_ENERGY         |
+			 TAS_DRCE_ATTACK         |
+			 TAS_DRCE_DECAY,
+			 argp);
+		return 0;
+
+	case TAS_READ_DRCE_MIN:
+	case TAS_READ_DRCE_MAX: {
+		struct tas_drce_ctrl_t drce_ctrl;
+		const struct tas_drce_t *drce_copy;
+
+		if (copy_from_user(&drce_ctrl, argp,
+				   sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+
+		if (cmd == TAS_READ_DRCE_MIN) {
+			drce_copy=&tas3004_drce_min;
+		} else {
+			drce_copy=&tas3004_drce_max;
+		}
+
+		if (drce_ctrl.flags & TAS_DRCE_ABOVE_RATIO) {
+			drce_ctrl.data.above=drce_copy->above;
+		}
+		if (drce_ctrl.flags & TAS_DRCE_BELOW_RATIO) {
+			drce_ctrl.data.below=drce_copy->below;
+		}
+		if (drce_ctrl.flags & TAS_DRCE_THRESHOLD) {
+			drce_ctrl.data.threshold=drce_copy->threshold;
+		}
+		if (drce_ctrl.flags & TAS_DRCE_ENERGY) {
+			drce_ctrl.data.energy=drce_copy->energy;
+		}
+		if (drce_ctrl.flags & TAS_DRCE_ATTACK) {
+			drce_ctrl.data.attack=drce_copy->attack;
+		}
+		if (drce_ctrl.flags & TAS_DRCE_DECAY) {
+			drce_ctrl.data.decay=drce_copy->decay;
+		}
+
+		if (copy_to_user(argp, &drce_ctrl,
+				 sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+	}
+	}
+
+	return -EINVAL;
+}
+
+static int
+tas3004_init_mixer(struct tas3004_data_t *self)
+{
+	unsigned char mcr = (1<<6)+(2<<4)+(2<<2);
+
+	/* Make sure something answers on the i2c bus */
+	if (tas3004_write_register(self, TAS3004_REG_MCR, &mcr,
+	    WRITE_NORMAL | FORCE_WRITE) < 0)
+		return -1;
+
+	tas3004_fast_load(self, 1);
+
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD0);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD1);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD2);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD3);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD4);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD5);
+	(void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD6);
+
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD0);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD1);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD2);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD3);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD4);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD5);
+	(void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD6);
+
+	tas3004_sync_register(self, TAS3004_REG_DRC);
+
+	tas3004_sync_register(self, TAS3004_REG_MCR2);
+
+	tas3004_fast_load(self, 0);
+
+	tas3004_set_mixer_level(self, SOUND_MIXER_VOLUME, VOL_DEFAULT<<8 | VOL_DEFAULT);
+	tas3004_set_mixer_level(self, SOUND_MIXER_PCM, INPUT_DEFAULT<<8 | INPUT_DEFAULT);
+	tas3004_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0);
+	tas3004_set_mixer_level(self, SOUND_MIXER_IMIX, 0);
+
+	tas3004_set_mixer_level(self, SOUND_MIXER_BASS, BASS_DEFAULT);
+	tas3004_set_mixer_level(self, SOUND_MIXER_TREBLE, TREBLE_DEFAULT);
+
+	tas3004_set_mixer_level(self, SOUND_MIXER_LINE,SW_INPUT_VOLUME_DEFAULT);
+
+	return 0;
+}
+
+static int
+tas3004_uninit_mixer(struct tas3004_data_t *self)
+{
+	tas3004_set_mixer_level(self, SOUND_MIXER_VOLUME, 0);
+	tas3004_set_mixer_level(self, SOUND_MIXER_PCM, 0);
+	tas3004_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0);
+	tas3004_set_mixer_level(self, SOUND_MIXER_IMIX, 0);
+
+	tas3004_set_mixer_level(self, SOUND_MIXER_BASS, 0);
+	tas3004_set_mixer_level(self, SOUND_MIXER_TREBLE, 0);
+
+	tas3004_set_mixer_level(self, SOUND_MIXER_LINE, 0);
+
+	return 0;
+}
+
+static int
+tas3004_init(struct i2c_client *client)
+{
+	struct tas3004_data_t *self;
+	size_t sz = sizeof(*self) + (TAS3004_REG_MAX*sizeof(tas_shadow_t));
+	char drce_init[] = { 0x69, 0x22, 0x9f, 0xb0, 0x60, 0xa0 };
+	char mcr2 = 0;
+	int i, j;
+
+	self = kmalloc(sz, GFP_KERNEL);
+	if (!self)
+		return -ENOMEM;
+	memset(self, 0, sz);
+
+	self->super.client = client;
+	self->super.shadow = (tas_shadow_t *)(self+1);
+	self->output_id = TAS_OUTPUT_HEADPHONES;
+
+	dev_set_drvdata(&client->dev, self);
+
+	for (i = 0; i < TAS3004_BIQUAD_CHANNEL_COUNT; i++)
+		for (j = 0; j<TAS3004_BIQUAD_FILTER_COUNT; j++)
+			tas3004_write_biquad_shadow(self, i, j,
+					&tas3004_eq_unity);
+
+	tas3004_write_register(self, TAS3004_REG_MCR2, &mcr2, WRITE_SHADOW);
+	tas3004_write_register(self, TAS3004_REG_DRC, drce_init, WRITE_SHADOW);
+
+	INIT_WORK(&device_change, tas3004_device_change_handler, self);
+	return 0;
+}
+
+static void 
+tas3004_uninit(struct tas3004_data_t *self)
+{
+	tas3004_uninit_mixer(self);
+	kfree(self);
+}
+
+
+struct tas_driver_hooks_t tas3004_hooks = {
+	.init			= (tas_hook_init_t)tas3004_init,
+	.post_init		= (tas_hook_post_init_t)tas3004_init_mixer,
+	.uninit			= (tas_hook_uninit_t)tas3004_uninit,
+	.get_mixer_level	= (tas_hook_get_mixer_level_t)tas3004_get_mixer_level,
+	.set_mixer_level	= (tas_hook_set_mixer_level_t)tas3004_set_mixer_level,
+	.enter_sleep		= (tas_hook_enter_sleep_t)tas3004_enter_sleep,
+	.leave_sleep		= (tas_hook_leave_sleep_t)tas3004_leave_sleep,
+	.supported_mixers	= (tas_hook_supported_mixers_t)tas3004_supported_mixers,
+	.mixer_is_stereo	= (tas_hook_mixer_is_stereo_t)tas3004_mixer_is_stereo,
+	.stereo_mixers		= (tas_hook_stereo_mixers_t)tas3004_stereo_mixers,
+	.output_device_change	= (tas_hook_output_device_change_t)tas3004_output_device_change,
+	.device_ioctl		= (tas_hook_device_ioctl_t)tas3004_device_ioctl
+};
diff --git a/sound/oss/dmasound/tas3004.h b/sound/oss/dmasound/tas3004.h
new file mode 100644
index 0000000..c6d584b
--- /dev/null
+++ b/sound/oss/dmasound/tas3004.h
@@ -0,0 +1,77 @@
+/*
+ * Header file for the i2c/i2s based TA3004 sound chip used
+ * on some Apple hardware. Also known as "tumbler".
+ *
+ *  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.
+ *
+ * Written by Christopher C. Chimelis <chris@debian.org>
+ */
+
+#ifndef _TAS3004_H_
+#define _TAS3004_H_
+
+#include <linux/types.h>
+
+#include "tas_common.h"
+#include "tas_eq_prefs.h"
+
+/*
+ * Macros that correspond to the registers that we write to
+ * when setting the various values.
+ */
+
+#define TAS3004_VERSION	        "0.3"
+#define TAS3004_DATE	        "20011214"
+
+#define I2C_DRIVERNAME_TAS3004 "TAS3004 driver V " TAS3004_VERSION
+#define I2C_DRIVERID_TAS3004    (I2C_DRIVERID_TAS_BASE+1)
+
+extern  struct tas_driver_hooks_t tas3004_hooks;
+extern struct tas_gain_t tas3004_gain;
+extern struct tas_eq_pref_t *tas3004_eq_prefs[];
+
+enum tas3004_reg_t {
+  TAS3004_REG_MCR                    = 0x01,
+  TAS3004_REG_DRC                    = 0x02,
+
+  TAS3004_REG_VOLUME                 = 0x04,
+  TAS3004_REG_TREBLE                 = 0x05,
+  TAS3004_REG_BASS                   = 0x06,
+  TAS3004_REG_LEFT_MIXER             = 0x07,
+  TAS3004_REG_RIGHT_MIXER            = 0x08,
+
+  TAS3004_REG_LEFT_BIQUAD0           = 0x0a,
+  TAS3004_REG_LEFT_BIQUAD1           = 0x0b,
+  TAS3004_REG_LEFT_BIQUAD2           = 0x0c,
+  TAS3004_REG_LEFT_BIQUAD3           = 0x0d,
+  TAS3004_REG_LEFT_BIQUAD4           = 0x0e,
+  TAS3004_REG_LEFT_BIQUAD5           = 0x0f,
+  TAS3004_REG_LEFT_BIQUAD6           = 0x10,
+  
+  TAS3004_REG_RIGHT_BIQUAD0          = 0x13,
+  TAS3004_REG_RIGHT_BIQUAD1          = 0x14,
+  TAS3004_REG_RIGHT_BIQUAD2          = 0x15,
+  TAS3004_REG_RIGHT_BIQUAD3          = 0x16,
+  TAS3004_REG_RIGHT_BIQUAD4          = 0x17,
+  TAS3004_REG_RIGHT_BIQUAD5          = 0x18,
+  TAS3004_REG_RIGHT_BIQUAD6          = 0x19,
+
+  TAS3004_REG_LEFT_LOUD_BIQUAD       = 0x21,
+  TAS3004_REG_RIGHT_LOUD_BIQUAD      = 0x22,
+
+  TAS3004_REG_LEFT_LOUD_BIQUAD_GAIN  = 0x23,
+  TAS3004_REG_RIGHT_LOUD_BIQUAD_GAIN = 0x24,
+
+  TAS3004_REG_TEST                   = 0x29,
+
+  TAS3004_REG_ANALOG_CTRL            = 0x40,
+  TAS3004_REG_TEST1                  = 0x41,
+  TAS3004_REG_TEST2                  = 0x42,
+  TAS3004_REG_MCR2                   = 0x43,
+
+  TAS3004_REG_MAX                    = 0x44
+};
+
+#endif /* _TAS3004_H_ */
diff --git a/sound/oss/dmasound/tas3004_tables.c b/sound/oss/dmasound/tas3004_tables.c
new file mode 100644
index 0000000..b910e0a
--- /dev/null
+++ b/sound/oss/dmasound/tas3004_tables.c
@@ -0,0 +1,301 @@
+#include "tas3004.h"
+#include "tas_eq_prefs.h"
+
+static struct tas_drce_t eqp_17_1_0_drce={
+    .enable     = 1,
+    .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+    .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+    .threshold  = -19.12  * (1<<8),
+    .energy     = 2.4     * (1<<12),
+    .attack     = 0.013   * (1<<12),
+    .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_17_1_0_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0fd0d4, 0xe05e56, 0x0fd0d4, 0xe05ee1, 0x0fa234 } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x0910d7, 0x088e1a, 0x030651, 0x01dcb1, 0x02c892 } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0ff895, 0xe0970b, 0x0f7f00, 0xe0970b, 0x0f7795 } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0fd1c4, 0xe1ac22, 0x0ec8cf, 0xe1ac22, 0x0e9a94 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x0f7c1c, 0xe3cc03, 0x0df786, 0xe3cc03, 0x0d73a2 } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x11fb92, 0xf5a1a0, 0x073cd2, 0xf5a1a0, 0x093865 } } },
+  { .channel = 0, .filter = 6, .data = { .coeff = { 0x0e17a9, 0x068b6c, 0x08a0e5, 0x068b6c, 0x06b88e } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0fd0d4, 0xe05e56, 0x0fd0d4, 0xe05ee1, 0x0fa234 } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x0910d7, 0x088e1a, 0x030651, 0x01dcb1, 0x02c892 } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0ff895, 0xe0970b, 0x0f7f00, 0xe0970b, 0x0f7795 } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0fd1c4, 0xe1ac22, 0x0ec8cf, 0xe1ac22, 0x0e9a94 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x0f7c1c, 0xe3cc03, 0x0df786, 0xe3cc03, 0x0d73a2 } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x11fb92, 0xf5a1a0, 0x073cd2, 0xf5a1a0, 0x093865 } } },
+  { .channel = 1, .filter = 6, .data = { .coeff = { 0x0e17a9, 0x068b6c, 0x08a0e5, 0x068b6c, 0x06b88e } } }
+};
+
+static struct tas_eq_pref_t eqp_17_1_0 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x17,
+  .output_id     = TAS_OUTPUT_INTERNAL_SPKR,
+  .speaker_id    = 0x00,
+
+  .drce          = &eqp_17_1_0_drce,
+
+  .filter_count  = 14,
+  .biquads       = eqp_17_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_18_1_0_drce={
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = -13.14  * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_18_1_0_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0f5514, 0xe155d7, 0x0f5514, 0xe15cfa, 0x0eb14b } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x06ec33, 0x02abe3, 0x015eef, 0xf764d9, 0x03922d } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0ef5f2, 0xe67d1f, 0x0bcf37, 0xe67d1f, 0x0ac529 } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0db050, 0xe5be4d, 0x0d0c78, 0xe5be4d, 0x0abcc8 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x0f1298, 0xe64ec6, 0x0cc03e, 0xe64ec6, 0x0bd2d7 } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x0c641a, 0x06537a, 0x08d155, 0x06537a, 0x053570 } } },
+  { .channel = 0, .filter = 6, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0f5514, 0xe155d7, 0x0f5514, 0xe15cfa, 0x0eb14b } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x06ec33, 0x02abe3, 0x015eef, 0xf764d9, 0x03922d } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0ef5f2, 0xe67d1f, 0x0bcf37, 0xe67d1f, 0x0ac529 } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0db050, 0xe5be4d, 0x0d0c78, 0xe5be4d, 0x0abcc8 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x0f1298, 0xe64ec6, 0x0cc03e, 0xe64ec6, 0x0bd2d7 } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x0c641a, 0x06537a, 0x08d155, 0x06537a, 0x053570 } } },
+  { .channel = 1, .filter = 6, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } }
+};
+
+static struct tas_eq_pref_t eqp_18_1_0 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x18,
+  .output_id     = TAS_OUTPUT_INTERNAL_SPKR,
+  .speaker_id    = 0x00,
+
+  .drce          = &eqp_18_1_0_drce,
+
+  .filter_count  = 14,
+  .biquads       = eqp_18_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_1a_1_0_drce={
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = -10.75  * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_1a_1_0_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0fb8fd, 0xe08e04, 0x0fb8fd, 0xe08f40, 0x0f7336 } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x06371d, 0x0c6e3a, 0x06371d, 0x05bfd3, 0x031ca2 } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0fa1c0, 0xe18692, 0x0f030e, 0xe18692, 0x0ea4ce } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0fe495, 0xe17eff, 0x0f0452, 0xe17eff, 0x0ee8e7 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x100857, 0xe7e71c, 0x0e9599, 0xe7e71c, 0x0e9df1 } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x0fb26e, 0x06a82c, 0x0db2b4, 0x06a82c, 0x0d6522 } } },
+  { .channel = 0, .filter = 6, .data = { .coeff = { 0x11419d, 0xf06cbf, 0x0a4f6e, 0xf06cbf, 0x0b910c } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0fb8fd, 0xe08e04, 0x0fb8fd, 0xe08f40, 0x0f7336 } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x06371d, 0x0c6e3a, 0x06371d, 0x05bfd3, 0x031ca2 } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0fa1c0, 0xe18692, 0x0f030e, 0xe18692, 0x0ea4ce } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0fe495, 0xe17eff, 0x0f0452, 0xe17eff, 0x0ee8e7 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x100857, 0xe7e71c, 0x0e9599, 0xe7e71c, 0x0e9df1 } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x0fb26e, 0x06a82c, 0x0db2b4, 0x06a82c, 0x0d6522 } } },
+  { .channel = 1, .filter = 6, .data = { .coeff = { 0x11419d, 0xf06cbf, 0x0a4f6e, 0xf06cbf, 0x0b910c } } }
+};
+
+static struct tas_eq_pref_t eqp_1a_1_0 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x1a,
+  .output_id     = TAS_OUTPUT_INTERNAL_SPKR,
+  .speaker_id    = 0x00,
+
+  .drce          = &eqp_1a_1_0_drce,
+
+  .filter_count  = 14,
+  .biquads       = eqp_1a_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_1c_1_0_drce={
+  .enable     = 1,
+  .above      = { .val = 3.0 * (1<<8), .expand = 0 },
+  .below      = { .val = 1.0 * (1<<8), .expand = 0 },
+  .threshold  = -14.34  * (1<<8),
+  .energy     = 2.4     * (1<<12),
+  .attack     = 0.013   * (1<<12),
+  .decay      = 0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_1c_1_0_biquads[]={
+  { .channel = 0, .filter = 0, .data = { .coeff = { 0x0f4f95, 0xe160d4, 0x0f4f95, 0xe1686e, 0x0ea6c5 } } },
+  { .channel = 0, .filter = 1, .data = { .coeff = { 0x066b92, 0x0290d4, 0x0148a0, 0xf6853f, 0x03bfc7 } } },
+  { .channel = 0, .filter = 2, .data = { .coeff = { 0x0f57dc, 0xe51c91, 0x0dd1cb, 0xe51c91, 0x0d29a8 } } },
+  { .channel = 0, .filter = 3, .data = { .coeff = { 0x0df1cb, 0xe4fa84, 0x0d7cdc, 0xe4fa84, 0x0b6ea7 } } },
+  { .channel = 0, .filter = 4, .data = { .coeff = { 0x0eba36, 0xe6aa48, 0x0b9f52, 0xe6aa48, 0x0a5989 } } },
+  { .channel = 0, .filter = 5, .data = { .coeff = { 0x0caf02, 0x05ef9d, 0x084beb, 0x05ef9d, 0x04faee } } },
+  { .channel = 0, .filter = 6, .data = { .coeff = { 0x0fc686, 0xe22947, 0x0e4b5d, 0xe22947, 0x0e11e4 } } },
+
+  { .channel = 1, .filter = 0, .data = { .coeff = { 0x0f4f95, 0xe160d4, 0x0f4f95, 0xe1686e, 0x0ea6c5 } } },
+  { .channel = 1, .filter = 1, .data = { .coeff = { 0x066b92, 0x0290d4, 0x0148a0, 0xf6853f, 0x03bfc7 } } },
+  { .channel = 1, .filter = 2, .data = { .coeff = { 0x0f57dc, 0xe51c91, 0x0dd1cb, 0xe51c91, 0x0d29a8 } } },
+  { .channel = 1, .filter = 3, .data = { .coeff = { 0x0df1cb, 0xe4fa84, 0x0d7cdc, 0xe4fa84, 0x0b6ea7 } } },
+  { .channel = 1, .filter = 4, .data = { .coeff = { 0x0eba36, 0xe6aa48, 0x0b9f52, 0xe6aa48, 0x0a5989 } } },
+  { .channel = 1, .filter = 5, .data = { .coeff = { 0x0caf02, 0x05ef9d, 0x084beb, 0x05ef9d, 0x04faee } } },
+  { .channel = 1, .filter = 6, .data = { .coeff = { 0x0fc686, 0xe22947, 0x0e4b5d, 0xe22947, 0x0e11e4 } } }
+};
+
+static struct tas_eq_pref_t eqp_1c_1_0 = {
+  .sample_rate   = 44100,
+  .device_id     = 0x1c,
+  .output_id     = TAS_OUTPUT_INTERNAL_SPKR,
+  .speaker_id    = 0x00,
+
+  .drce          = &eqp_1c_1_0_drce,
+
+  .filter_count  = 14,
+  .biquads       = eqp_1c_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static uint tas3004_master_tab[]={
+	       0x0,       0x75,       0x9c,       0xbb,
+	      0xdb,       0xfb,      0x11e,      0x143,
+	     0x16b,      0x196,      0x1c3,      0x1f5,
+	     0x229,      0x263,      0x29f,      0x2e1,
+	     0x328,      0x373,      0x3c5,      0x41b,
+	     0x478,      0x4dc,      0x547,      0x5b8,
+	     0x633,      0x6b5,      0x740,      0x7d5,
+	     0x873,      0x91c,      0x9d2,      0xa92,
+	     0xb5e,      0xc39,      0xd22,      0xe19,
+	     0xf20,     0x1037,     0x1161,     0x129e,
+	    0x13ed,     0x1551,     0x16ca,     0x185d,
+	    0x1a08,     0x1bcc,     0x1dac,     0x1fa7,
+	    0x21c1,     0x23fa,     0x2655,     0x28d6,
+	    0x2b7c,     0x2e4a,     0x3141,     0x3464,
+	    0x37b4,     0x3b35,     0x3ee9,     0x42d3,
+	    0x46f6,     0x4b53,     0x4ff0,     0x54ce,
+	    0x59f2,     0x5f5f,     0x6519,     0x6b24,
+	    0x7183,     0x783c,     0x7f53,     0x86cc,
+	    0x8ead,     0x96fa,     0x9fba,     0xa8f2,
+	    0xb2a7,     0xbce1,     0xc7a5,     0xd2fa,
+	    0xdee8,     0xeb75,     0xf8aa,    0x1068e,
+	   0x1152a,    0x12487,    0x134ad,    0x145a5,
+	   0x1577b,    0x16a37,    0x17df5,    0x192bd,
+	   0x1a890,    0x1bf7b,    0x1d78d,    0x1f0d1,
+	   0x20b55,    0x22727,    0x24456,    0x262f2,
+	   0x2830b
+};
+
+static uint tas3004_mixer_tab[]={
+	       0x0,      0x748,      0x9be,      0xbaf,
+	     0xda4,      0xfb1,     0x11de,     0x1431,
+	    0x16ad,     0x1959,     0x1c37,     0x1f4b,
+	    0x2298,     0x2628,     0x29fb,     0x2e12,
+	    0x327d,     0x3734,     0x3c47,     0x41b4,
+	    0x4787,     0x4dbe,     0x546d,     0x5b86,
+	    0x632e,     0x6b52,     0x7400,     0x7d54,
+	    0x873b,     0x91c6,     0x9d1a,     0xa920,
+	    0xb5e5,     0xc38c,     0xd21b,     0xe18f,
+	    0xf1f5,    0x1036a,    0x1160f,    0x129d6,
+	   0x13ed0,    0x1550c,    0x16ca0,    0x185c9,
+	   0x1a07b,    0x1bcc3,    0x1dab9,    0x1fa75,
+	   0x21c0f,    0x23fa3,    0x26552,    0x28d64,
+	   0x2b7c9,    0x2e4a2,    0x31411,    0x3463b,
+	   0x37b44,    0x3b353,    0x3ee94,    0x42d30,
+	   0x46f55,    0x4b533,    0x4fefc,    0x54ce5,
+	   0x59f25,    0x5f5f6,    0x65193,    0x6b23c,
+	   0x71835,    0x783c3,    0x7f52c,    0x86cc0,
+	   0x8eacc,    0x96fa5,    0x9fba0,    0xa8f1a,
+	   0xb2a71,    0xbce0a,    0xc7a4a,    0xd2fa0,
+	   0xdee7b,    0xeb752,    0xf8a9f,   0x1068e4,
+	  0x1152a3,   0x12486a,   0x134ac8,   0x145a55,
+	  0x1577ac,   0x16a370,   0x17df51,   0x192bc2,
+	  0x1a88f8,   0x1bf7b7,   0x1d78c9,   0x1f0d04,
+	  0x20b542,   0x227268,   0x244564,   0x262f26,
+	  0x2830af
+};
+
+static uint tas3004_treble_tab[]={
+	      0x96,       0x95,       0x95,       0x94,
+	      0x93,       0x92,       0x92,       0x91,
+	      0x90,       0x90,       0x8f,       0x8e,
+	      0x8d,       0x8d,       0x8c,       0x8b,
+	      0x8a,       0x8a,       0x89,       0x88,
+	      0x88,       0x87,       0x86,       0x85,
+	      0x85,       0x84,       0x83,       0x83,
+	      0x82,       0x81,       0x80,       0x80,
+	      0x7f,       0x7e,       0x7e,       0x7d,
+	      0x7c,       0x7b,       0x7b,       0x7a,
+	      0x79,       0x78,       0x78,       0x77,
+	      0x76,       0x76,       0x75,       0x74,
+	      0x73,       0x73,       0x72,       0x71,
+	      0x71,       0x68,       0x45,       0x5b,
+	      0x6d,       0x6c,       0x6b,       0x6a,
+	      0x69,       0x68,       0x67,       0x66,
+	      0x65,       0x63,       0x62,       0x62,
+	      0x60,       0x5e,       0x5c,       0x5b,
+	      0x59,       0x57,       0x55,       0x53,
+	      0x52,       0x4f,       0x4d,       0x4a,
+	      0x48,       0x46,       0x43,       0x40,
+	      0x3d,       0x3a,       0x36,       0x33,
+	      0x2f,       0x2c,       0x27,       0x23,
+	      0x1f,       0x1a,       0x15,        0xf,
+	       0x8,        0x5,        0x2,        0x1,
+	       0x1
+};
+
+static uint tas3004_bass_tab[]={
+	      0x96,       0x95,       0x95,       0x94,
+	      0x93,       0x92,       0x92,       0x91,
+	      0x90,       0x90,       0x8f,       0x8e,
+	      0x8d,       0x8d,       0x8c,       0x8b,
+	      0x8a,       0x8a,       0x89,       0x88,
+	      0x88,       0x87,       0x86,       0x85,
+	      0x85,       0x84,       0x83,       0x83,
+	      0x82,       0x81,       0x80,       0x80,
+	      0x7f,       0x7e,       0x7e,       0x7d,
+	      0x7c,       0x7b,       0x7b,       0x7a,
+	      0x79,       0x78,       0x78,       0x77,
+	      0x76,       0x76,       0x75,       0x74,
+	      0x73,       0x73,       0x72,       0x71,
+	      0x70,       0x6f,       0x6e,       0x6d,
+	      0x6c,       0x6b,       0x6a,       0x6a,
+	      0x69,       0x67,       0x66,       0x66,
+	      0x65,       0x63,       0x62,       0x62,
+	      0x61,       0x60,       0x5e,       0x5d,
+	      0x5b,       0x59,       0x57,       0x55,
+	      0x53,       0x51,       0x4f,       0x4c,
+	      0x4a,       0x48,       0x46,       0x44,
+	      0x41,       0x3e,       0x3b,       0x38,
+	      0x36,       0x33,       0x2f,       0x2b,
+	      0x28,       0x24,       0x20,       0x1c,
+	      0x17,       0x12,        0xd,        0x7,
+	       0x1
+};
+
+struct tas_gain_t tas3004_gain={
+  .master  = tas3004_master_tab,
+  .treble  = tas3004_treble_tab,
+  .bass    = tas3004_bass_tab,
+  .mixer   = tas3004_mixer_tab
+};
+
+struct tas_eq_pref_t *tas3004_eq_prefs[]={
+  &eqp_17_1_0,
+  &eqp_18_1_0,
+  &eqp_1a_1_0,
+  &eqp_1c_1_0,
+  NULL
+};
diff --git a/sound/oss/dmasound/tas_common.c b/sound/oss/dmasound/tas_common.c
new file mode 100644
index 0000000..d36a1fe
--- /dev/null
+++ b/sound/oss/dmasound/tas_common.c
@@ -0,0 +1,214 @@
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/ioport.h>
+#include <linux/sysctl.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/soundcard.h>
+#include <asm/uaccess.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+
+#include "tas_common.h"
+
+#define CALL0(proc)								\
+	do {									\
+		struct tas_data_t *self;					\
+		if (!tas_client || driver_hooks == NULL)			\
+			return -1;						\
+		self = dev_get_drvdata(&tas_client->dev);			\
+		if (driver_hooks->proc)						\
+			return driver_hooks->proc(self);			\
+		else								\
+			return -EINVAL;						\
+	} while (0)
+
+#define CALL(proc,arg...)							\
+	do {									\
+		struct tas_data_t *self;					\
+		if (!tas_client || driver_hooks == NULL)			\
+			return -1;						\
+		self = dev_get_drvdata(&tas_client->dev);			\
+		if (driver_hooks->proc)						\
+			return driver_hooks->proc(self, ## arg);		\
+		else								\
+			return -EINVAL;						\
+	} while (0)
+
+
+static u8 tas_i2c_address = 0x34;
+static struct i2c_client *tas_client;
+static struct device_node* tas_node;
+
+static int tas_attach_adapter(struct i2c_adapter *);
+static int tas_detach_client(struct i2c_client *);
+
+struct i2c_driver tas_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "tas",
+	.flags		= I2C_DF_NOTIFY,
+	.attach_adapter	= tas_attach_adapter,
+	.detach_client	= tas_detach_client,
+};
+
+struct tas_driver_hooks_t *driver_hooks;
+
+int
+tas_register_driver(struct tas_driver_hooks_t *hooks)
+{
+	driver_hooks = hooks;
+	return 0;
+}
+
+int
+tas_get_mixer_level(int mixer, uint *level)
+{
+	CALL(get_mixer_level,mixer,level);
+}
+
+int
+tas_set_mixer_level(int mixer,uint level)
+{
+	CALL(set_mixer_level,mixer,level);
+}
+
+int
+tas_enter_sleep(void)
+{
+	CALL0(enter_sleep);
+}
+
+int
+tas_leave_sleep(void)
+{
+	CALL0(leave_sleep);
+}
+
+int
+tas_supported_mixers(void)
+{
+	CALL0(supported_mixers);
+}
+
+int
+tas_mixer_is_stereo(int mixer)
+{
+	CALL(mixer_is_stereo,mixer);
+}
+
+int
+tas_stereo_mixers(void)
+{
+	CALL0(stereo_mixers);
+}
+
+int
+tas_output_device_change(int device_id,int layout_id,int speaker_id)
+{
+	CALL(output_device_change,device_id,layout_id,speaker_id);
+}
+
+int
+tas_device_ioctl(u_int cmd, u_long arg)
+{
+	CALL(device_ioctl,cmd,arg);
+}
+
+int
+tas_post_init(void)
+{
+	CALL0(post_init);
+}
+
+static int
+tas_detect_client(struct i2c_adapter *adapter, int address)
+{
+	static const char *client_name = "tas Digital Equalizer";
+	struct i2c_client *new_client;
+	int rc = -ENODEV;
+
+	if (!driver_hooks) {
+		printk(KERN_ERR "tas_detect_client called with no hooks !\n");
+		return -ENODEV;
+	}
+	
+	new_client = kmalloc(sizeof(*new_client), GFP_KERNEL);
+	if (!new_client)
+		return -ENOMEM;
+	memset(new_client, 0, sizeof(*new_client));
+
+	new_client->addr = address;
+	new_client->adapter = adapter;
+	new_client->driver = &tas_driver;
+	strlcpy(new_client->name, client_name, DEVICE_NAME_SIZE);
+
+        if (driver_hooks->init(new_client))
+		goto bail;
+
+	/* Tell the i2c layer a new client has arrived */
+	if (i2c_attach_client(new_client)) {
+		driver_hooks->uninit(dev_get_drvdata(&new_client->dev));
+		goto bail;
+	}
+
+	tas_client = new_client;
+	return 0;
+ bail:
+	tas_client = NULL;
+	kfree(new_client);
+	return rc;
+}
+
+static int
+tas_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!strncmp(adapter->name, "mac-io", 6))
+		return tas_detect_client(adapter, tas_i2c_address);
+	return 0;
+}
+
+static int
+tas_detach_client(struct i2c_client *client)
+{
+	if (client == tas_client) {
+		driver_hooks->uninit(dev_get_drvdata(&client->dev));
+
+		i2c_detach_client(client);
+		kfree(client);
+	}
+	return 0;
+}
+
+void
+tas_cleanup(void)
+{
+	i2c_del_driver(&tas_driver);
+}
+
+int __init
+tas_init(int driver_id, const char *driver_name)
+{
+	u32* paddr;
+
+	printk(KERN_INFO "tas driver [%s])\n", driver_name);
+
+#ifndef CONFIG_I2C_KEYWEST
+	request_module("i2c-keywest");
+#endif
+	tas_node = find_devices("deq");
+	if (tas_node == NULL)
+		return -ENODEV;
+	paddr = (u32 *)get_property(tas_node, "i2c-address", NULL);
+	if (paddr) {
+		tas_i2c_address = (*paddr) >> 1;
+		printk(KERN_INFO "using i2c address: 0x%x from device-tree\n",
+				tas_i2c_address);
+	} else    
+		printk(KERN_INFO "using i2c address: 0x%x (default)\n",
+				tas_i2c_address);
+
+	return i2c_add_driver(&tas_driver);
+}
diff --git a/sound/oss/dmasound/tas_common.h b/sound/oss/dmasound/tas_common.h
new file mode 100644
index 0000000..3a6d486
--- /dev/null
+++ b/sound/oss/dmasound/tas_common.h
@@ -0,0 +1,284 @@
+#ifndef _TAS_COMMON_H_
+#define _TAS_COMMON_H_
+
+#include <linux/i2c.h>
+#include <linux/soundcard.h>
+#include <asm/string.h>
+
+#define I2C_DRIVERID_TAS_BASE   (0xFEBA)
+
+#define SET_4_20(shadow, offset, val)                        \
+	do {                                                 \
+		(shadow)[(offset)+0] = ((val) >> 16) & 0xff; \
+		(shadow)[(offset)+1] = ((val) >> 8)  & 0xff; \
+		(shadow)[(offset)+2] = ((val) >> 0)  & 0xff; \
+	} while (0)
+
+#define GET_4_20(shadow, offset)                             \
+	(((u_int)((shadow)[(offset)+0]) << 16) |             \
+	 ((u_int)((shadow)[(offset)+1]) <<  8) |             \
+	 ((u_int)((shadow)[(offset)+2]) <<  0))
+
+
+#define TAS_BIQUAD_FAST_LOAD 0x01
+
+#define TAS_DRCE_ENABLE           0x01
+#define TAS_DRCE_ABOVE_RATIO      0x02
+#define TAS_DRCE_BELOW_RATIO      0x04
+#define TAS_DRCE_THRESHOLD        0x08
+#define TAS_DRCE_ENERGY           0x10
+#define TAS_DRCE_ATTACK           0x20
+#define TAS_DRCE_DECAY            0x40
+
+#define TAS_DRCE_ALL              0x7f
+
+
+#define TAS_OUTPUT_HEADPHONES     0x00
+#define TAS_OUTPUT_INTERNAL_SPKR  0x01
+#define TAS_OUTPUT_EXTERNAL_SPKR  0x02
+
+
+union tas_biquad_t {
+	struct {
+		int b0,b1,b2,a1,a2;
+	} coeff;
+	int buf[5];
+};
+
+struct tas_biquad_ctrl_t {
+	u_int channel:4;
+	u_int filter:4;
+
+	union tas_biquad_t data;
+};
+
+struct tas_biquad_ctrl_list_t {
+	int flags;
+	int filter_count;
+	struct tas_biquad_ctrl_t biquads[0];
+};
+
+struct tas_ratio_t {
+	unsigned short val;    /* 8.8                        */
+	unsigned short expand; /* 0 = compress, !0 = expand. */
+};
+
+struct tas_drce_t {
+	unsigned short enable;
+	struct tas_ratio_t above;
+	struct tas_ratio_t below;
+	short threshold;       /* dB,       8.8 signed    */
+	unsigned short energy; /* seconds,  4.12 unsigned */
+	unsigned short attack; /* seconds,  4.12 unsigned */
+	unsigned short decay;  /* seconds,  4.12 unsigned */
+};
+
+struct tas_drce_ctrl_t {
+	uint flags;
+
+	struct tas_drce_t data;
+};
+
+struct tas_gain_t
+{
+  unsigned int *master;
+  unsigned int *treble;
+  unsigned int *bass;
+  unsigned int *mixer;
+};
+
+typedef char tas_shadow_t[0x45];
+
+struct tas_data_t
+{
+	struct i2c_client *client;
+	tas_shadow_t *shadow;
+	uint mixer[SOUND_MIXER_NRDEVICES];
+};
+
+typedef int (*tas_hook_init_t)(struct i2c_client *);
+typedef int (*tas_hook_post_init_t)(struct tas_data_t *);
+typedef void (*tas_hook_uninit_t)(struct tas_data_t *);
+
+typedef int (*tas_hook_get_mixer_level_t)(struct tas_data_t *,int,uint *);
+typedef int (*tas_hook_set_mixer_level_t)(struct tas_data_t *,int,uint);
+
+typedef int (*tas_hook_enter_sleep_t)(struct tas_data_t *);
+typedef int (*tas_hook_leave_sleep_t)(struct tas_data_t *);
+
+typedef int (*tas_hook_supported_mixers_t)(struct tas_data_t *);
+typedef int (*tas_hook_mixer_is_stereo_t)(struct tas_data_t *,int);
+typedef int (*tas_hook_stereo_mixers_t)(struct tas_data_t *);
+
+typedef int (*tas_hook_output_device_change_t)(struct tas_data_t *,int,int,int);
+typedef int (*tas_hook_device_ioctl_t)(struct tas_data_t *,u_int,u_long);
+
+struct tas_driver_hooks_t {
+	/*
+	 * All hardware initialisation must be performed in
+	 * post_init(), as tas_dmasound_init() does a hardware reset.
+	 *
+	 * init() is called before tas_dmasound_init() so that
+	 * ouput_device_change() is always called after i2c driver
+	 * initialisation. The implication is that
+	 * output_device_change() must cope with the fact that it
+	 * may be called before post_init().
+	 */
+
+	tas_hook_init_t                   init;
+	tas_hook_post_init_t              post_init;
+	tas_hook_uninit_t                 uninit;
+
+	tas_hook_get_mixer_level_t        get_mixer_level;
+	tas_hook_set_mixer_level_t        set_mixer_level;
+
+	tas_hook_enter_sleep_t            enter_sleep;
+	tas_hook_leave_sleep_t            leave_sleep;
+
+	tas_hook_supported_mixers_t       supported_mixers;
+	tas_hook_mixer_is_stereo_t        mixer_is_stereo;
+	tas_hook_stereo_mixers_t          stereo_mixers;
+
+	tas_hook_output_device_change_t   output_device_change;
+	tas_hook_device_ioctl_t           device_ioctl;
+};
+
+enum tas_write_mode_t {
+	WRITE_HW     = 0x01,
+	WRITE_SHADOW = 0x02,
+	WRITE_NORMAL = 0x03,
+	FORCE_WRITE  = 0x04
+};
+
+static inline uint
+tas_mono_to_stereo(uint mono)
+{
+	mono &=0xff;
+	return mono | (mono<<8);
+}
+
+/*
+ * Todo: make these functions a bit more efficient !
+ */
+static inline int
+tas_write_register(	struct tas_data_t *self,
+			uint reg_num,
+			uint reg_width,
+			char *data,
+			uint write_mode)
+{
+	int rc;
+
+	if (reg_width==0 || data==NULL || self==NULL)
+		return -EINVAL;
+	if (!(write_mode & FORCE_WRITE) &&
+	    !memcmp(data,self->shadow[reg_num],reg_width))
+	    	return 0;
+
+	if (write_mode & WRITE_SHADOW)
+		memcpy(self->shadow[reg_num],data,reg_width);
+	if (write_mode & WRITE_HW) {
+		rc=i2c_smbus_write_block_data(self->client,
+					      reg_num,
+					      reg_width,
+					      data);
+		if (rc < 0) {
+			printk("tas: I2C block write failed \n");  
+			return rc; 
+		}
+	}
+	return 0;
+}
+
+static inline int
+tas_sync_register(	struct tas_data_t *self,
+			uint reg_num,
+			uint reg_width)
+{
+	int rc;
+
+	if (reg_width==0 || self==NULL)
+		return -EINVAL;
+	rc=i2c_smbus_write_block_data(self->client,
+				      reg_num,
+				      reg_width,
+				      self->shadow[reg_num]);
+	if (rc < 0) {
+		printk("tas: I2C block write failed \n");
+		return rc;
+	}
+	return 0;
+}
+
+static inline int
+tas_write_byte_register(	struct tas_data_t *self,
+				uint reg_num,
+				char data,
+				uint write_mode)
+{
+	if (self==NULL)
+		return -1;
+	if (!(write_mode & FORCE_WRITE) && data != self->shadow[reg_num][0])
+		return 0;
+	if (write_mode & WRITE_SHADOW)
+		self->shadow[reg_num][0]=data;
+	if (write_mode & WRITE_HW) {
+		if (i2c_smbus_write_byte_data(self->client, reg_num, data) < 0) {
+			printk("tas: I2C byte write failed \n");  
+			return -1; 
+		}
+	}
+	return 0;
+}
+
+static inline int
+tas_sync_byte_register(	struct tas_data_t *self,
+			uint reg_num,
+			uint reg_width)
+{
+	if (reg_width==0 || self==NULL)
+		return -1;
+	if (i2c_smbus_write_byte_data(
+	    self->client, reg_num, self->shadow[reg_num][0]) < 0) {
+		printk("tas: I2C byte write failed \n");
+		return -1;
+	}
+	return 0;
+}
+
+static inline int
+tas_read_register(	struct tas_data_t *self,
+			uint reg_num,
+			uint reg_width,
+			char *data)
+{
+	if (reg_width==0 || data==NULL || self==NULL)
+		return -1;
+	memcpy(data,self->shadow[reg_num],reg_width);
+	return 0;
+}
+
+extern int tas_register_driver(struct tas_driver_hooks_t *hooks);
+
+extern int tas_get_mixer_level(int mixer,uint *level);
+extern int tas_set_mixer_level(int mixer,uint level);
+extern int tas_enter_sleep(void);
+extern int tas_leave_sleep(void);
+extern int tas_supported_mixers(void);
+extern int tas_mixer_is_stereo(int mixer);
+extern int tas_stereo_mixers(void);
+extern int tas_output_device_change(int,int,int);
+extern int tas_device_ioctl(u_int, u_long);
+
+extern void tas_cleanup(void);
+extern int tas_init(int driver_id,const char *driver_name);
+extern int tas_post_init(void);
+
+#endif /* _TAS_COMMON_H_ */
+/*
+ * Local Variables:
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/sound/oss/dmasound/tas_eq_prefs.h b/sound/oss/dmasound/tas_eq_prefs.h
new file mode 100644
index 0000000..3a994ed
--- /dev/null
+++ b/sound/oss/dmasound/tas_eq_prefs.h
@@ -0,0 +1,24 @@
+#ifndef _TAS_EQ_PREFS_H_
+#define _TAS_EQ_PREFS_H_
+
+struct tas_eq_pref_t {
+	u_int sample_rate;
+	u_int device_id;
+	u_int output_id;
+	u_int speaker_id;
+
+	struct tas_drce_t *drce;
+
+	u_int filter_count;
+	struct tas_biquad_ctrl_t *biquads;
+};
+
+#endif /* _TAS_EQ_PREFS_H_ */
+
+/*
+ * Local Variables:
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/sound/oss/dmasound/tas_ioctl.h b/sound/oss/dmasound/tas_ioctl.h
new file mode 100644
index 0000000..dccae3a
--- /dev/null
+++ b/sound/oss/dmasound/tas_ioctl.h
@@ -0,0 +1,24 @@
+#ifndef _TAS_IOCTL_H_
+#define _TAS_IOCTL_H_
+
+#include <linux/i2c.h>
+#include <linux/soundcard.h>
+
+
+#define TAS_READ_EQ              _SIOR('t',0,struct tas_biquad_ctrl_t)
+#define TAS_WRITE_EQ             _SIOW('t',0,struct tas_biquad_ctrl_t)
+
+#define TAS_READ_EQ_LIST         _SIOR('t',1,struct tas_biquad_ctrl_t)
+#define TAS_WRITE_EQ_LIST        _SIOW('t',1,struct tas_biquad_ctrl_t)
+
+#define TAS_READ_EQ_FILTER_COUNT  _SIOR('t',2,int)
+#define TAS_READ_EQ_CHANNEL_COUNT _SIOR('t',3,int)
+
+#define TAS_READ_DRCE            _SIOR('t',4,struct tas_drce_ctrl_t)
+#define TAS_WRITE_DRCE           _SIOW('t',4,struct tas_drce_ctrl_t)
+
+#define TAS_READ_DRCE_CAPS       _SIOR('t',5,int)
+#define TAS_READ_DRCE_MIN        _SIOR('t',6,int)
+#define TAS_READ_DRCE_MAX        _SIOR('t',7,int)
+
+#endif
diff --git a/sound/oss/dmasound/trans_16.c b/sound/oss/dmasound/trans_16.c
new file mode 100644
index 0000000..23562e9
--- /dev/null
+++ b/sound/oss/dmasound/trans_16.c
@@ -0,0 +1,897 @@
+/*
+ *  linux/sound/oss/dmasound/trans_16.c
+ *
+ *  16 bit translation routines.  Only used by Power mac at present.
+ *
+ *  See linux/sound/oss/dmasound/dmasound_core.c for copyright and
+ *  history prior to 08/02/2001.
+ *
+ *  08/02/2001 Iain Sandoe
+ *		split from dmasound_awacs.c
+ *  11/29/2003 Renzo Davoli (King Enzo)
+ *  	- input resampling (for soft rate < hard rate)
+ *  	- software line in gain control
+ */
+
+#include <linux/soundcard.h>
+#include <asm/uaccess.h>
+#include "dmasound.h"
+
+static short dmasound_alaw2dma16[] ;
+static short dmasound_ulaw2dma16[] ;
+
+static ssize_t pmac_ct_law(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+static ssize_t pmac_ct_s8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft);
+static ssize_t pmac_ct_u8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft);
+static ssize_t pmac_ct_s16(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+static ssize_t pmac_ct_u16(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+
+static ssize_t pmac_ctx_law(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t pmac_ctx_s8(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+static ssize_t pmac_ctx_u8(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+static ssize_t pmac_ctx_s16(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t pmac_ctx_u16(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+
+static ssize_t pmac_ct_s16_read(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+static ssize_t pmac_ct_u16_read(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+
+/*** Translations ************************************************************/
+
+static int expand_data;	/* Data for expanding */
+
+static ssize_t pmac_ct_law(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	short *table = dmasound.soft.format == AFMT_MU_LAW
+		? dmasound_ulaw2dma16 : dmasound_alaw2dma16;
+	ssize_t count, used;
+	short *p = (short *) &frame[*frameUsed];
+	int val, stereo = dmasound.soft.stereo;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	while (count > 0) {
+		u_char data;
+		if (get_user(data, userPtr++))
+			return -EFAULT;
+		val = table[data];
+		*p++ = val;
+		if (stereo) {
+			if (get_user(data, userPtr++))
+				return -EFAULT;
+			val = table[data];
+		}
+		*p++ = val;
+		count--;
+	}
+	*frameUsed += used * 4;
+	return stereo? used * 2: used;
+}
+
+
+static ssize_t pmac_ct_s8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	ssize_t count, used;
+	short *p = (short *) &frame[*frameUsed];
+	int val, stereo = dmasound.soft.stereo;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	while (count > 0) {
+		u_char data;
+		if (get_user(data, userPtr++))
+			return -EFAULT;
+		val = data << 8;
+		*p++ = val;
+		if (stereo) {
+			if (get_user(data, userPtr++))
+				return -EFAULT;
+			val = data << 8;
+		}
+		*p++ = val;
+		count--;
+	}
+	*frameUsed += used * 4;
+	return stereo? used * 2: used;
+}
+
+
+static ssize_t pmac_ct_u8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	ssize_t count, used;
+	short *p = (short *) &frame[*frameUsed];
+	int val, stereo = dmasound.soft.stereo;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	while (count > 0) {
+		u_char data;
+		if (get_user(data, userPtr++))
+			return -EFAULT;
+		val = (data ^ 0x80) << 8;
+		*p++ = val;
+		if (stereo) {
+			if (get_user(data, userPtr++))
+				return -EFAULT;
+			val = (data ^ 0x80) << 8;
+		}
+		*p++ = val;
+		count--;
+	}
+	*frameUsed += used * 4;
+	return stereo? used * 2: used;
+}
+
+
+static ssize_t pmac_ct_s16(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	ssize_t count, used;
+	int stereo = dmasound.soft.stereo;
+	short *fp = (short *) &frame[*frameUsed];
+
+	frameLeft >>= 2;
+	userCount >>= (stereo? 2: 1);
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	if (!stereo) {
+		short __user *up = (short __user *) userPtr;
+		while (count > 0) {
+			short data;
+			if (get_user(data, up++))
+				return -EFAULT;
+			*fp++ = data;
+			*fp++ = data;
+			count--;
+		}
+	} else {
+		if (copy_from_user(fp, userPtr, count * 4))
+			return -EFAULT;
+	}
+	*frameUsed += used * 4;
+	return stereo? used * 4: used * 2;
+}
+
+static ssize_t pmac_ct_u16(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	ssize_t count, used;
+	int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
+	int stereo = dmasound.soft.stereo;
+	short *fp = (short *) &frame[*frameUsed];
+	short __user *up = (short __user *) userPtr;
+
+	frameLeft >>= 2;
+	userCount >>= (stereo? 2: 1);
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	while (count > 0) {
+		short data;
+		if (get_user(data, up++))
+			return -EFAULT;
+		data ^= mask;
+		*fp++ = data;
+		if (stereo) {
+			if (get_user(data, up++))
+				return -EFAULT;
+			data ^= mask;
+		}
+		*fp++ = data;
+		count--;
+	}
+	*frameUsed += used * 4;
+	return stereo? used * 4: used * 2;
+}
+
+
+static ssize_t pmac_ctx_law(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	unsigned short *table = (unsigned short *)
+		(dmasound.soft.format == AFMT_MU_LAW
+		 ? dmasound_ulaw2dma16 : dmasound_alaw2dma16);
+	unsigned int data = expand_data;
+	unsigned int *p = (unsigned int *) &frame[*frameUsed];
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+	int stereo = dmasound.soft.stereo;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	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];
+			if (stereo) {
+				if (get_user(c, userPtr++))
+					return -EFAULT;
+				data = (data << 16) + table[c];
+			} else
+				data = (data << 16) + data;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 2: utotal;
+}
+
+static ssize_t pmac_ctx_s8(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	unsigned int *p = (unsigned int *) &frame[*frameUsed];
+	unsigned int data = expand_data;
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int stereo = dmasound.soft.stereo;
+	int utotal, ftotal;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	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 << 8;
+			if (stereo) {
+				if (get_user(c, userPtr++))
+					return -EFAULT;
+				data = (data << 16) + (c << 8);
+			} else
+				data = (data << 16) + data;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 2: utotal;
+}
+
+
+static ssize_t pmac_ctx_u8(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	unsigned int *p = (unsigned int *) &frame[*frameUsed];
+	unsigned int data = expand_data;
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int stereo = dmasound.soft.stereo;
+	int utotal, ftotal;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	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 ^ 0x80) << 8;
+			if (stereo) {
+				if (get_user(c, userPtr++))
+					return -EFAULT;
+				data = (data << 16) + ((c ^ 0x80) << 8);
+			} else
+				data = (data << 16) + data;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 2: utotal;
+}
+
+
+static ssize_t pmac_ctx_s16(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	unsigned int *p = (unsigned int *) &frame[*frameUsed];
+	unsigned int data = expand_data;
+	unsigned short __user *up = (unsigned short __user *) userPtr;
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int stereo = dmasound.soft.stereo;
+	int utotal, ftotal;
+
+	frameLeft >>= 2;
+	userCount >>= (stereo? 2: 1);
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		unsigned short c;
+		if (bal < 0) {
+			if (userCount == 0)
+				break;
+			if (get_user(data, up++))
+				return -EFAULT;
+			if (stereo) {
+				if (get_user(c, up++))
+					return -EFAULT;
+				data = (data << 16) + c;
+			} else
+				data = (data << 16) + data;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 4: utotal * 2;
+}
+
+
+static ssize_t pmac_ctx_u16(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
+	unsigned int *p = (unsigned int *) &frame[*frameUsed];
+	unsigned int data = expand_data;
+	unsigned short __user *up = (unsigned short __user *) userPtr;
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int stereo = dmasound.soft.stereo;
+	int utotal, ftotal;
+
+	frameLeft >>= 2;
+	userCount >>= (stereo? 2: 1);
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		unsigned short c;
+		if (bal < 0) {
+			if (userCount == 0)
+				break;
+			if (get_user(data, up++))
+				return -EFAULT;
+			data ^= mask;
+			if (stereo) {
+				if (get_user(c, up++))
+					return -EFAULT;
+				data = (data << 16) + (c ^ mask);
+			} else
+				data = (data << 16) + data;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 4: utotal * 2;
+}
+
+/* data in routines... */
+
+static ssize_t pmac_ct_s8_read(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	ssize_t count, used;
+	short *p = (short *) &frame[*frameUsed];
+	int val, stereo = dmasound.soft.stereo;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	while (count > 0) {
+		u_char data;
+
+		val = *p++;
+		val = (val * software_input_volume) >> 7;
+		data = val >> 8;
+		if (put_user(data, (u_char __user *)userPtr++))
+			return -EFAULT;
+		if (stereo) {
+			val = *p;
+			val = (val * software_input_volume) >> 7;
+			data = val >> 8;
+			if (put_user(data, (u_char __user *)userPtr++))
+				return -EFAULT;
+		}
+		p++;
+		count--;
+	}
+	*frameUsed += used * 4;
+	return stereo? used * 2: used;
+}
+
+
+static ssize_t pmac_ct_u8_read(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	ssize_t count, used;
+	short *p = (short *) &frame[*frameUsed];
+	int val, stereo = dmasound.soft.stereo;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	while (count > 0) {
+		u_char data;
+
+		val = *p++;
+		val = (val * software_input_volume) >> 7;
+		data = (val >> 8) ^ 0x80;
+		if (put_user(data, (u_char __user *)userPtr++))
+			return -EFAULT;
+		if (stereo) {
+			val = *p;
+			val = (val * software_input_volume) >> 7;
+			data = (val >> 8) ^ 0x80;
+			if (put_user(data, (u_char __user *)userPtr++))
+				return -EFAULT;
+		}
+		p++;
+		count--;
+	}
+	*frameUsed += used * 4;
+	return stereo? used * 2: used;
+}
+
+static ssize_t pmac_ct_s16_read(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	ssize_t count, used;
+	int stereo = dmasound.soft.stereo;
+	short *fp = (short *) &frame[*frameUsed];
+	short __user *up = (short __user *) userPtr;
+
+	frameLeft >>= 2;
+	userCount >>= (stereo? 2: 1);
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	while (count > 0) {
+		short data;
+
+		data = *fp++;
+		data = (data * software_input_volume) >> 7;
+		if (put_user(data, up++))
+			return -EFAULT;
+		if (stereo) {
+			data = *fp;
+			data = (data * software_input_volume) >> 7;
+			if (put_user(data, up++))
+				return -EFAULT;
+		}
+		fp++;
+		count--;
+ 	}
+	*frameUsed += used * 4;
+	return stereo? used * 4: used * 2;
+}
+
+static ssize_t pmac_ct_u16_read(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	ssize_t count, used;
+	int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
+	int stereo = dmasound.soft.stereo;
+	short *fp = (short *) &frame[*frameUsed];
+	short __user *up = (short __user *) userPtr;
+
+	frameLeft >>= 2;
+	userCount >>= (stereo? 2: 1);
+	used = count = min_t(unsigned long, userCount, frameLeft);
+	while (count > 0) {
+		int data;
+
+		data = *fp++;
+		data = (data * software_input_volume) >> 7;
+		data ^= mask;
+		if (put_user(data, up++))
+			return -EFAULT;
+		if (stereo) {
+			data = *fp;
+			data = (data * software_input_volume) >> 7;
+			data ^= mask;
+			if (put_user(data, up++))
+				return -EFAULT;
+		}
+		fp++;
+		count--;
+	}
+	*frameUsed += used * 4;
+	return stereo? used * 4: used * 2;
+}
+
+/* data in routines (reducing speed)... */
+
+static ssize_t pmac_ctx_s8_read(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	short *p = (short *) &frame[*frameUsed];
+	int bal = expand_read_bal;
+	int vall,valr, stereo = dmasound.soft.stereo;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		u_char data;
+
+		if (bal<0 && userCount == 0)
+			break;
+		vall = *p++;
+		vall = (vall * software_input_volume) >> 7;
+		if (stereo) {
+			valr = *p;
+			valr = (valr * software_input_volume) >> 7;
+		}
+		p++;
+		if (bal < 0) {
+			data = vall >> 8;
+			if (put_user(data, (u_char __user *)userPtr++))
+				return -EFAULT;
+			if (stereo) {
+				data = valr >> 8;
+				if (put_user(data, (u_char __user *)userPtr++))
+					return -EFAULT;
+			}
+			userCount--;
+			bal += hSpeed;
+		}
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_read_bal=bal;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 2: utotal;
+}
+
+
+static ssize_t pmac_ctx_u8_read(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	short *p = (short *) &frame[*frameUsed];
+	int bal = expand_read_bal;
+	int vall,valr, stereo = dmasound.soft.stereo;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+	frameLeft >>= 2;
+	if (stereo)
+		userCount >>= 1;
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		u_char data;
+
+		if (bal<0 && userCount == 0)
+			break;
+
+		vall = *p++;
+		vall = (vall * software_input_volume) >> 7;
+		if (stereo) {
+			valr = *p;
+			valr = (valr * software_input_volume) >> 7;
+		}
+		p++;
+		if (bal < 0) {
+			data = (vall >> 8) ^ 0x80;
+			if (put_user(data, (u_char __user *)userPtr++))
+				return -EFAULT;
+			if (stereo) {
+				data = (valr >> 8) ^ 0x80;
+				if (put_user(data, (u_char __user *)userPtr++))
+					return -EFAULT;
+			}
+			userCount--;
+			bal += hSpeed;
+		}
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_read_bal=bal;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 2: utotal;
+}
+
+static ssize_t pmac_ctx_s16_read(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	int bal = expand_read_bal;
+	short *fp = (short *) &frame[*frameUsed];
+	short __user *up = (short __user *) userPtr;
+	int stereo = dmasound.soft.stereo;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+	frameLeft >>= 2;
+	userCount >>= (stereo? 2: 1);
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		int datal,datar;
+
+		if (bal<0 && userCount == 0)
+			break;
+
+		datal = *fp++;
+		datal = (datal * software_input_volume) >> 7;
+		if (stereo) {
+			datar = *fp;
+			datar = (datar * software_input_volume) >> 7;
+		}
+		fp++;
+		if (bal < 0) {
+			if (put_user(datal, up++))
+				return -EFAULT;
+			if (stereo) {
+				if (put_user(datar, up++))
+					return -EFAULT;
+			}
+			userCount--;
+			bal += hSpeed;
+		}
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_read_bal=bal;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 4: utotal * 2;
+}
+
+static ssize_t pmac_ctx_u16_read(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	int bal = expand_read_bal;
+	int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
+	short *fp = (short *) &frame[*frameUsed];
+	short __user *up = (short __user *) userPtr;
+	int stereo = dmasound.soft.stereo;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+	frameLeft >>= 2;
+	userCount >>= (stereo? 2: 1);
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		int datal,datar;
+
+		if (bal<0 && userCount == 0)
+			break;
+
+		datal = *fp++;
+		datal = (datal * software_input_volume) >> 7;
+		datal ^= mask;
+		if (stereo) {
+			datar = *fp;
+			datar = (datar * software_input_volume) >> 7;
+			datar ^= mask;
+		}
+		fp++;
+		if (bal < 0) {
+			if (put_user(datal, up++))
+				return -EFAULT;
+			if (stereo) {
+				if (put_user(datar, up++))
+					return -EFAULT;
+			}
+			userCount--;
+			bal += hSpeed;
+		}
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_read_bal=bal;
+	*frameUsed += (ftotal - frameLeft) * 4;
+	utotal -= userCount;
+	return stereo? utotal * 4: utotal * 2;
+}
+
+
+TRANS transAwacsNormal = {
+	.ct_ulaw=	pmac_ct_law,
+	.ct_alaw=	pmac_ct_law,
+	.ct_s8=		pmac_ct_s8,
+	.ct_u8=		pmac_ct_u8,
+	.ct_s16be=	pmac_ct_s16,
+	.ct_u16be=	pmac_ct_u16,
+	.ct_s16le=	pmac_ct_s16,
+	.ct_u16le=	pmac_ct_u16,
+};
+
+TRANS transAwacsExpand = {
+	.ct_ulaw=	pmac_ctx_law,
+	.ct_alaw=	pmac_ctx_law,
+	.ct_s8=		pmac_ctx_s8,
+	.ct_u8=		pmac_ctx_u8,
+	.ct_s16be=	pmac_ctx_s16,
+	.ct_u16be=	pmac_ctx_u16,
+	.ct_s16le=	pmac_ctx_s16,
+	.ct_u16le=	pmac_ctx_u16,
+};
+
+TRANS transAwacsNormalRead = {
+	.ct_s8=		pmac_ct_s8_read,
+	.ct_u8=		pmac_ct_u8_read,
+	.ct_s16be=	pmac_ct_s16_read,
+	.ct_u16be=	pmac_ct_u16_read,
+	.ct_s16le=	pmac_ct_s16_read,
+	.ct_u16le=	pmac_ct_u16_read,
+};
+
+TRANS transAwacsExpandRead = {
+	.ct_s8=		pmac_ctx_s8_read,
+	.ct_u8=		pmac_ctx_u8_read,
+	.ct_s16be=	pmac_ctx_s16_read,
+	.ct_u16be=	pmac_ctx_u16_read,
+	.ct_s16le=	pmac_ctx_s16_read,
+	.ct_u16le=	pmac_ctx_u16_read,
+};
+
+/* translation tables */
+/* 16 bit mu-law */
+
+static short dmasound_ulaw2dma16[] = {
+	-32124,	-31100,	-30076,	-29052,	-28028,	-27004,	-25980,	-24956,
+	-23932,	-22908,	-21884,	-20860,	-19836,	-18812,	-17788,	-16764,
+	-15996,	-15484,	-14972,	-14460,	-13948,	-13436,	-12924,	-12412,
+	-11900,	-11388,	-10876,	-10364,	-9852,	-9340,	-8828,	-8316,
+	-7932,	-7676,	-7420,	-7164,	-6908,	-6652,	-6396,	-6140,
+	-5884,	-5628,	-5372,	-5116,	-4860,	-4604,	-4348,	-4092,
+	-3900,	-3772,	-3644,	-3516,	-3388,	-3260,	-3132,	-3004,
+	-2876,	-2748,	-2620,	-2492,	-2364,	-2236,	-2108,	-1980,
+	-1884,	-1820,	-1756,	-1692,	-1628,	-1564,	-1500,	-1436,
+	-1372,	-1308,	-1244,	-1180,	-1116,	-1052,	-988,	-924,
+	-876,	-844,	-812,	-780,	-748,	-716,	-684,	-652,
+	-620,	-588,	-556,	-524,	-492,	-460,	-428,	-396,
+	-372,	-356,	-340,	-324,	-308,	-292,	-276,	-260,
+	-244,	-228,	-212,	-196,	-180,	-164,	-148,	-132,
+	-120,	-112,	-104,	-96,	-88,	-80,	-72,	-64,
+	-56,	-48,	-40,	-32,	-24,	-16,	-8,	0,
+	32124,	31100,	30076,	29052,	28028,	27004,	25980,	24956,
+	23932,	22908,	21884,	20860,	19836,	18812,	17788,	16764,
+	15996,	15484,	14972,	14460,	13948,	13436,	12924,	12412,
+	11900,	11388,	10876,	10364,	9852,	9340,	8828,	8316,
+	7932,	7676,	7420,	7164,	6908,	6652,	6396,	6140,
+	5884,	5628,	5372,	5116,	4860,	4604,	4348,	4092,
+	3900,	3772,	3644,	3516,	3388,	3260,	3132,	3004,
+	2876,	2748,	2620,	2492,	2364,	2236,	2108,	1980,
+	1884,	1820,	1756,	1692,	1628,	1564,	1500,	1436,
+	1372,	1308,	1244,	1180,	1116,	1052,	988,	924,
+	876,	844,	812,	780,	748,	716,	684,	652,
+	620,	588,	556,	524,	492,	460,	428,	396,
+	372,	356,	340,	324,	308,	292,	276,	260,
+	244,	228,	212,	196,	180,	164,	148,	132,
+	120,	112,	104,	96,	88,	80,	72,	64,
+	56,	48,	40,	32,	24,	16,	8,	0,
+};
+
+/* 16 bit A-law */
+
+static short dmasound_alaw2dma16[] = {
+	-5504,	-5248,	-6016,	-5760,	-4480,	-4224,	-4992,	-4736,
+	-7552,	-7296,	-8064,	-7808,	-6528,	-6272,	-7040,	-6784,
+	-2752,	-2624,	-3008,	-2880,	-2240,	-2112,	-2496,	-2368,
+	-3776,	-3648,	-4032,	-3904,	-3264,	-3136,	-3520,	-3392,
+	-22016,	-20992,	-24064,	-23040,	-17920,	-16896,	-19968,	-18944,
+	-30208,	-29184,	-32256,	-31232,	-26112,	-25088,	-28160,	-27136,
+	-11008,	-10496,	-12032,	-11520,	-8960,	-8448,	-9984,	-9472,
+	-15104,	-14592,	-16128,	-15616,	-13056,	-12544,	-14080,	-13568,
+	-344,	-328,	-376,	-360,	-280,	-264,	-312,	-296,
+	-472,	-456,	-504,	-488,	-408,	-392,	-440,	-424,
+	-88,	-72,	-120,	-104,	-24,	-8,	-56,	-40,
+	-216,	-200,	-248,	-232,	-152,	-136,	-184,	-168,
+	-1376,	-1312,	-1504,	-1440,	-1120,	-1056,	-1248,	-1184,
+	-1888,	-1824,	-2016,	-1952,	-1632,	-1568,	-1760,	-1696,
+	-688,	-656,	-752,	-720,	-560,	-528,	-624,	-592,
+	-944,	-912,	-1008,	-976,	-816,	-784,	-880,	-848,
+	5504,	5248,	6016,	5760,	4480,	4224,	4992,	4736,
+	7552,	7296,	8064,	7808,	6528,	6272,	7040,	6784,
+	2752,	2624,	3008,	2880,	2240,	2112,	2496,	2368,
+	3776,	3648,	4032,	3904,	3264,	3136,	3520,	3392,
+	22016,	20992,	24064,	23040,	17920,	16896,	19968,	18944,
+	30208,	29184,	32256,	31232,	26112,	25088,	28160,	27136,
+	11008,	10496,	12032,	11520,	8960,	8448,	9984,	9472,
+	15104,	14592,	16128,	15616,	13056,	12544,	14080,	13568,
+	344,	328,	376,	360,	280,	264,	312,	296,
+	472,	456,	504,	488,	408,	392,	440,	424,
+	88,	72,	120,	104,	24,	8,	56,	40,
+	216,	200,	248,	232,	152,	136,	184,	168,
+	1376,	1312,	1504,	1440,	1120,	1056,	1248,	1184,
+	1888,	1824,	2016,	1952,	1632,	1568,	1760,	1696,
+	688,	656,	752,	720,	560,	528,	624,	592,
+	944,	912,	1008,	976,	816,	784,	880,	848,
+};