summary refs log tree commit diff
path: root/sound/ppc
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/ppc
downloadlinux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz
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!
Diffstat (limited to 'sound/ppc')
-rw-r--r--sound/ppc/Kconfig23
-rw-r--r--sound/ppc/Makefile9
-rw-r--r--sound/ppc/awacs.c903
-rw-r--r--sound/ppc/awacs.h192
-rw-r--r--sound/ppc/beep.c262
-rw-r--r--sound/ppc/burgundy.c439
-rw-r--r--sound/ppc/burgundy.h95
-rw-r--r--sound/ppc/daca.c283
-rw-r--r--sound/ppc/keywest.c143
-rw-r--r--sound/ppc/pmac.c1328
-rw-r--r--sound/ppc/pmac.h214
-rw-r--r--sound/ppc/powermac.c159
-rw-r--r--sound/ppc/tumbler.c1175
-rw-r--r--sound/ppc/tumbler_volume.h250
14 files changed, 5475 insertions, 0 deletions
diff --git a/sound/ppc/Kconfig b/sound/ppc/Kconfig
new file mode 100644
index 000000000000..b0a9ebf8bf3b
--- /dev/null
+++ b/sound/ppc/Kconfig
@@ -0,0 +1,23 @@
+# ALSA PowerMac drivers
+
+menu "ALSA PowerMac devices"
+	depends on SND!=n && PPC
+
+comment "ALSA PowerMac requires I2C"
+	depends on SND && I2C=n
+
+comment "ALSA PowerMac requires INPUT"
+	depends on SND && INPUT=n
+
+config SND_POWERMAC
+	tristate "PowerMac (AWACS, DACA, Burgundy, Tumbler, Keywest)"
+	depends on SND && I2C && INPUT
+	select SND_PCM
+	help
+	  Say Y here to include support for the integrated sound device.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-powermac.
+
+endmenu
+
diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile
new file mode 100644
index 000000000000..4d95c652c8ca
--- /dev/null
+++ b/sound/ppc/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o
diff --git a/sound/ppc/awacs.c b/sound/ppc/awacs.c
new file mode 100644
index 000000000000..e052bd071e5b
--- /dev/null
+++ b/sound/ppc/awacs.c
@@ -0,0 +1,903 @@
+/*
+ * PMac AWACS lowlevel functions
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ * code based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <asm/nvram.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "pmac.h"
+
+
+#ifdef CONFIG_ADB_CUDA
+#define PMAC_AMP_AVAIL
+#endif
+
+#ifdef PMAC_AMP_AVAIL
+typedef struct awacs_amp {
+	unsigned char amp_master;
+	unsigned char amp_vol[2][2];
+	unsigned char amp_tone[2];
+} awacs_amp_t;
+
+#define CHECK_CUDA_AMP() (sys_ctrler == SYS_CTRLER_CUDA)
+
+#endif /* PMAC_AMP_AVAIL */
+
+
+static void snd_pmac_screamer_wait(pmac_t *chip)
+{
+	long timeout = 2000;
+	while (!(in_le32(&chip->awacs->codec_stat) & MASK_VALID)) {
+		mdelay(1);
+		if (! --timeout) {
+			snd_printd("snd_pmac_screamer_wait timeout\n");
+			break;
+		}
+	}
+}
+
+/*
+ * write AWACS register
+ */
+static void
+snd_pmac_awacs_write(pmac_t *chip, int val)
+{
+	long timeout = 5000000;
+
+	if (chip->model == PMAC_SCREAMER)
+		snd_pmac_screamer_wait(chip);
+	out_le32(&chip->awacs->codec_ctrl, val | (chip->subframe << 22));
+	while (in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) {
+		if (! --timeout) {
+			snd_printd("snd_pmac_awacs_write timeout\n");
+			break;
+		}
+	}
+}
+
+static void
+snd_pmac_awacs_write_reg(pmac_t *chip, int reg, int val)
+{
+	snd_pmac_awacs_write(chip, val | (reg << 12));
+	chip->awacs_reg[reg] = val;
+}
+
+static void
+snd_pmac_awacs_write_noreg(pmac_t *chip, int reg, int val)
+{
+	snd_pmac_awacs_write(chip, val | (reg << 12));
+}
+
+#ifdef CONFIG_PMAC_PBOOK
+/* Recalibrate chip */
+static void screamer_recalibrate(pmac_t *chip)
+{
+	if (chip->model != PMAC_SCREAMER)
+		return;
+
+	/* Sorry for the horrible delays... I hope to get that improved
+	 * by making the whole PM process asynchronous in a future version
+	 */
+	snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]);
+	if (chip->manufacturer == 0x1)
+		/* delay for broken crystal part */
+		big_mdelay(750);
+	snd_pmac_awacs_write_noreg(chip, 1,
+				   chip->awacs_reg[1] | MASK_RECALIBRATE | MASK_CMUTE | MASK_AMUTE);
+	snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]);
+	snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]);
+}
+
+#else
+#define screamer_recalibrate(chip) /* NOP */
+#endif
+
+
+/*
+ * additional callback to set the pcm format
+ */
+static void snd_pmac_awacs_set_format(pmac_t *chip)
+{
+	chip->awacs_reg[1] &= ~MASK_SAMPLERATE;
+	chip->awacs_reg[1] |= chip->rate_index << 3;
+	snd_pmac_awacs_write_reg(chip, 1, chip->awacs_reg[1]);
+}
+
+
+/*
+ * AWACS volume callbacks
+ */
+/*
+ * volumes: 0-15 stereo
+ */
+static int snd_pmac_awacs_info_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 15;
+	return 0;
+}
+ 
+static int snd_pmac_awacs_get_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int lshift = (kcontrol->private_value >> 8) & 0xff;
+	int inverted = (kcontrol->private_value >> 16) & 1;
+	unsigned long flags;
+	int vol[2];
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	vol[0] = (chip->awacs_reg[reg] >> lshift) & 0xf;
+	vol[1] = chip->awacs_reg[reg] & 0xf;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (inverted) {
+		vol[0] = 0x0f - vol[0];
+		vol[1] = 0x0f - vol[1];
+	}
+	ucontrol->value.integer.value[0] = vol[0];
+	ucontrol->value.integer.value[1] = vol[1];
+	return 0;
+}
+
+static int snd_pmac_awacs_put_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int lshift = (kcontrol->private_value >> 8) & 0xff;
+	int inverted = (kcontrol->private_value >> 16) & 1;
+	int val, oldval;
+	unsigned long flags;
+	int vol[2];
+
+	vol[0] = ucontrol->value.integer.value[0];
+	vol[1] = ucontrol->value.integer.value[1];
+	if (inverted) {
+		vol[0] = 0x0f - vol[0];
+		vol[1] = 0x0f - vol[1];
+	}
+	vol[0] &= 0x0f;
+	vol[1] &= 0x0f;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oldval = chip->awacs_reg[reg];
+	val = oldval & ~(0xf | (0xf << lshift));
+	val |= vol[0] << lshift;
+	val |= vol[1];
+	if (oldval != val)
+		snd_pmac_awacs_write_reg(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return oldval != reg;
+}
+
+
+#define AWACS_VOLUME(xname, xreg, xshift, xinverted) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \
+  .info = snd_pmac_awacs_info_volume, \
+  .get = snd_pmac_awacs_get_volume, \
+  .put = snd_pmac_awacs_put_volume, \
+  .private_value = (xreg) | ((xshift) << 8) | ((xinverted) << 16) }
+
+/*
+ * mute master/ogain for AWACS: mono
+ */
+static int snd_pmac_awacs_get_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int invert = (kcontrol->private_value >> 16) & 1;
+	int val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->awacs_reg[reg] >> shift) & 1;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		val = 1 - val;
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int snd_pmac_awacs_put_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int invert = (kcontrol->private_value >> 16) & 1;
+	int mask = 1 << shift;
+	int val, changed;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = chip->awacs_reg[reg] & ~mask;
+	if (ucontrol->value.integer.value[0] != invert)
+		val |= mask;
+	changed = chip->awacs_reg[reg] != val;
+	if (changed)
+		snd_pmac_awacs_write_reg(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return changed;
+}
+
+#define AWACS_SWITCH(xname, xreg, xshift, xinvert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \
+  .info = snd_pmac_boolean_mono_info, \
+  .get = snd_pmac_awacs_get_switch, \
+  .put = snd_pmac_awacs_put_switch, \
+  .private_value = (xreg) | ((xshift) << 8) | ((xinvert) << 16) }
+
+
+#ifdef PMAC_AMP_AVAIL
+/*
+ * controls for perch/whisper extension cards, e.g. G3 desktop
+ *
+ * TDA7433 connected via i2c address 0x45 (= 0x8a),
+ * accessed through cuda
+ */
+static void awacs_set_cuda(int reg, int val)
+{
+	struct adb_request req;
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, 0x8a, reg, val);
+	while (! req.complete)
+		cuda_poll();
+}
+
+/*
+ * level = 0 - 14, 7 = 0 dB
+ */
+static void awacs_amp_set_tone(awacs_amp_t *amp, int bass, int treble)
+{
+	amp->amp_tone[0] = bass;
+	amp->amp_tone[1] = treble;
+	if (bass > 7)
+		bass = (14 - bass) + 8;
+	if (treble > 7)
+		treble = (14 - treble) + 8;
+	awacs_set_cuda(2, (bass << 4) | treble);
+}
+
+/*
+ * vol = 0 - 31 (attenuation), 32 = mute bit, stereo
+ */
+static int awacs_amp_set_vol(awacs_amp_t *amp, int index, int lvol, int rvol, int do_check)
+{
+	if (do_check && amp->amp_vol[index][0] == lvol &&
+	    amp->amp_vol[index][1] == rvol)
+		return 0;
+	awacs_set_cuda(3 + index, lvol);
+	awacs_set_cuda(5 + index, rvol);
+	amp->amp_vol[index][0] = lvol;
+	amp->amp_vol[index][1] = rvol;
+	return 1;
+}
+
+/*
+ * 0 = -79 dB, 79 = 0 dB, 99 = +20 dB
+ */
+static void awacs_amp_set_master(awacs_amp_t *amp, int vol)
+{
+	amp->amp_master = vol;
+	if (vol <= 79)
+		vol = 32 + (79 - vol);
+	else
+		vol = 32 - (vol - 79);
+	awacs_set_cuda(1, vol);
+}
+
+static void awacs_amp_free(pmac_t *chip)
+{
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return);
+	kfree(amp);
+	chip->mixer_data = NULL;
+	chip->mixer_free = NULL;
+}
+
+
+/*
+ * mixer controls
+ */
+static int snd_pmac_awacs_info_volume_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 31;
+	return 0;
+}
+ 
+static int snd_pmac_awacs_get_volume_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return -EINVAL);
+	snd_assert(index >= 0 && index <= 1, return -EINVAL);
+	ucontrol->value.integer.value[0] = 31 - (amp->amp_vol[index][0] & 31);
+	ucontrol->value.integer.value[1] = 31 - (amp->amp_vol[index][1] & 31);
+	return 0;
+}
+
+static int snd_pmac_awacs_put_volume_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	int vol[2];
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return -EINVAL);
+	snd_assert(index >= 0 && index <= 1, return -EINVAL);
+
+	vol[0] = (31 - (ucontrol->value.integer.value[0] & 31)) | (amp->amp_vol[index][0] & 32);
+	vol[1] = (31 - (ucontrol->value.integer.value[1] & 31)) | (amp->amp_vol[index][1] & 32);
+	return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1);
+}
+
+static int snd_pmac_awacs_get_switch_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return -EINVAL);
+	snd_assert(index >= 0 && index <= 1, return -EINVAL);
+	ucontrol->value.integer.value[0] = (amp->amp_vol[index][0] & 32) ? 0 : 1;
+	ucontrol->value.integer.value[1] = (amp->amp_vol[index][1] & 32) ? 0 : 1;
+	return 0;
+}
+
+static int snd_pmac_awacs_put_switch_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	int vol[2];
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return -EINVAL);
+	snd_assert(index >= 0 && index <= 1, return -EINVAL);
+
+	vol[0] = (ucontrol->value.integer.value[0] ? 0 : 32) | (amp->amp_vol[index][0] & 31);
+	vol[1] = (ucontrol->value.integer.value[1] ? 0 : 32) | (amp->amp_vol[index][1] & 31);
+	return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1);
+}
+
+static int snd_pmac_awacs_info_tone_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 14;
+	return 0;
+}
+ 
+static int snd_pmac_awacs_get_tone_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return -EINVAL);
+	snd_assert(index >= 0 && index <= 1, return -EINVAL);
+	ucontrol->value.integer.value[0] = amp->amp_tone[index];
+	return 0;
+}
+
+static int snd_pmac_awacs_put_tone_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return -EINVAL);
+	snd_assert(index >= 0 && index <= 1, return -EINVAL);
+	if (ucontrol->value.integer.value[0] != amp->amp_tone[index]) {
+		amp->amp_tone[index] = ucontrol->value.integer.value[0];
+		awacs_amp_set_tone(amp, amp->amp_tone[0], amp->amp_tone[1]);
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_pmac_awacs_info_master_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 99;
+	return 0;
+}
+ 
+static int snd_pmac_awacs_get_master_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return -EINVAL);
+	ucontrol->value.integer.value[0] = amp->amp_master;
+	return 0;
+}
+
+static int snd_pmac_awacs_put_master_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	awacs_amp_t *amp = chip->mixer_data;
+	snd_assert(amp, return -EINVAL);
+	if (ucontrol->value.integer.value[0] != amp->amp_master) {
+		amp->amp_master = ucontrol->value.integer.value[0];
+		awacs_amp_set_master(amp, amp->amp_master);
+		return 1;
+	}
+	return 0;
+}
+
+#define AMP_CH_SPK	0
+#define AMP_CH_HD	1
+
+static snd_kcontrol_new_t snd_pmac_awacs_amp_vol[] __initdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "PC Speaker Playback Volume",
+	  .info = snd_pmac_awacs_info_volume_amp,
+	  .get = snd_pmac_awacs_get_volume_amp,
+	  .put = snd_pmac_awacs_put_volume_amp,
+	  .private_value = AMP_CH_SPK,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Headphone Playback Volume",
+	  .info = snd_pmac_awacs_info_volume_amp,
+	  .get = snd_pmac_awacs_get_volume_amp,
+	  .put = snd_pmac_awacs_put_volume_amp,
+	  .private_value = AMP_CH_HD,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Tone Control - Bass",
+	  .info = snd_pmac_awacs_info_tone_amp,
+	  .get = snd_pmac_awacs_get_tone_amp,
+	  .put = snd_pmac_awacs_put_tone_amp,
+	  .private_value = 0,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Tone Control - Treble",
+	  .info = snd_pmac_awacs_info_tone_amp,
+	  .get = snd_pmac_awacs_get_tone_amp,
+	  .put = snd_pmac_awacs_put_tone_amp,
+	  .private_value = 1,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Amp Master Playback Volume",
+	  .info = snd_pmac_awacs_info_master_amp,
+	  .get = snd_pmac_awacs_get_master_amp,
+	  .put = snd_pmac_awacs_put_master_amp,
+	},
+};
+
+static snd_kcontrol_new_t snd_pmac_awacs_amp_hp_sw __initdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Headphone Playback Switch",
+	.info = snd_pmac_boolean_stereo_info,
+	.get = snd_pmac_awacs_get_switch_amp,
+	.put = snd_pmac_awacs_put_switch_amp,
+	.private_value = AMP_CH_HD,
+};
+
+static snd_kcontrol_new_t snd_pmac_awacs_amp_spk_sw __initdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PC Speaker Playback Switch",
+	.info = snd_pmac_boolean_stereo_info,
+	.get = snd_pmac_awacs_get_switch_amp,
+	.put = snd_pmac_awacs_put_switch_amp,
+	.private_value = AMP_CH_SPK,
+};
+
+#endif /* PMAC_AMP_AVAIL */
+
+
+/*
+ * mic boost for screamer
+ */
+static int snd_pmac_screamer_mic_boost_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 2;
+	return 0;
+}
+
+static int snd_pmac_screamer_mic_boost_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->awacs_reg[6] & MASK_MIC_BOOST)
+		val = 2;
+	else if (chip->awacs_reg[0] & MASK_GAINLINE)
+		val = 1;
+	else
+		val = 0;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int snd_pmac_screamer_mic_boost_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int val0, val6;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val0 = chip->awacs_reg[0] & ~MASK_GAINLINE;
+	val6 = chip->awacs_reg[6] & ~MASK_MIC_BOOST;
+	if (ucontrol->value.integer.value[0] > 0) {
+		val0 |= MASK_GAINLINE;
+		if (ucontrol->value.integer.value[0] > 1)
+			val6 |= MASK_MIC_BOOST;
+	}
+	if (val0 != chip->awacs_reg[0]) {
+		snd_pmac_awacs_write_reg(chip, 0, val0);
+		changed = 1;
+	}
+	if (val6 != chip->awacs_reg[6]) {
+		snd_pmac_awacs_write_reg(chip, 6, val6);
+		changed = 1;
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return changed;
+}
+
+/*
+ * lists of mixer elements
+ */
+static snd_kcontrol_new_t snd_pmac_awacs_mixers[] __initdata = {
+	AWACS_VOLUME("Master Playback Volume", 2, 6, 1),
+	AWACS_SWITCH("Master Capture Switch", 1, SHIFT_LOOPTHRU, 0),
+	AWACS_VOLUME("Capture Volume", 0, 4, 0),
+	AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0),
+};
+
+/* FIXME: is this correct order?
+ * screamer (powerbook G3 pismo) seems to have different bits...
+ */
+static snd_kcontrol_new_t snd_pmac_awacs_mixers2[] __initdata = {
+	AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_LINE, 0),
+	AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_MIC, 0),
+};
+
+static snd_kcontrol_new_t snd_pmac_screamer_mixers2[] __initdata = {
+	AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0),
+	AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_LINE, 0),
+};
+
+static snd_kcontrol_new_t snd_pmac_awacs_master_sw __initdata =
+AWACS_SWITCH("Master Playback Switch", 1, SHIFT_HDMUTE, 1);
+
+static snd_kcontrol_new_t snd_pmac_awacs_mic_boost[] __initdata = {
+	AWACS_SWITCH("Mic Boost", 0, SHIFT_GAINLINE, 0),
+};
+
+static snd_kcontrol_new_t snd_pmac_screamer_mic_boost[] __initdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Mic Boost",
+	  .info = snd_pmac_screamer_mic_boost_info,
+	  .get = snd_pmac_screamer_mic_boost_get,
+	  .put = snd_pmac_screamer_mic_boost_put,
+	},
+};
+
+static snd_kcontrol_new_t snd_pmac_awacs_speaker_vol[] __initdata = {
+	AWACS_VOLUME("PC Speaker Playback Volume", 4, 6, 1),
+};
+static snd_kcontrol_new_t snd_pmac_awacs_speaker_sw __initdata =
+AWACS_SWITCH("PC Speaker Playback Switch", 1, SHIFT_SPKMUTE, 1);
+
+
+/*
+ * add new mixer elements to the card
+ */
+static int build_mixers(pmac_t *chip, int nums, snd_kcontrol_new_t *mixers)
+{
+	int i, err;
+
+	for (i = 0; i < nums; i++) {
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mixers[i], chip))) < 0)
+			return err;
+	}
+	return 0;
+}
+
+
+/*
+ * restore all registers
+ */
+static void awacs_restore_all_regs(pmac_t *chip)
+{
+	snd_pmac_awacs_write_noreg(chip, 0, chip->awacs_reg[0]);
+	snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]);
+	snd_pmac_awacs_write_noreg(chip, 2, chip->awacs_reg[2]);
+	snd_pmac_awacs_write_noreg(chip, 4, chip->awacs_reg[4]);
+	if (chip->model == PMAC_SCREAMER) {
+		snd_pmac_awacs_write_noreg(chip, 5, chip->awacs_reg[5]);
+		snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]);
+		snd_pmac_awacs_write_noreg(chip, 7, chip->awacs_reg[7]);
+	}
+}
+
+#ifdef CONFIG_PMAC_PBOOK
+static void snd_pmac_awacs_suspend(pmac_t *chip)
+{
+	snd_pmac_awacs_write_noreg(chip, 1, (chip->awacs_reg[1]
+					     | MASK_AMUTE | MASK_CMUTE));
+}
+
+static void snd_pmac_awacs_resume(pmac_t *chip)
+{
+	if (machine_is_compatible("PowerBook3,1")
+	    || machine_is_compatible("PowerBook3,2")) {
+		big_mdelay(100);
+		snd_pmac_awacs_write_reg(chip, 1,
+			chip->awacs_reg[1] & ~MASK_PAROUT);
+		big_mdelay(300);
+	}
+
+	awacs_restore_all_regs(chip);
+	if (chip->model == PMAC_SCREAMER) {
+		/* reset power bits in reg 6 */
+		mdelay(5);
+		snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]);
+	}
+	screamer_recalibrate(chip);
+#ifdef PMAC_AMP_AVAIL
+	if (chip->mixer_data) {
+		awacs_amp_t *amp = chip->mixer_data;
+		awacs_amp_set_vol(amp, 0, amp->amp_vol[0][0], amp->amp_vol[0][1], 0);
+		awacs_amp_set_vol(amp, 1, amp->amp_vol[1][0], amp->amp_vol[1][1], 0);
+		awacs_amp_set_tone(amp, amp->amp_tone[0], amp->amp_tone[1]);
+		awacs_amp_set_master(amp, amp->amp_master);
+	}
+#endif
+}
+#endif /* CONFIG_PMAC_PBOOK */
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+/*
+ * auto-mute stuffs
+ */
+static int snd_pmac_awacs_detect_headphone(pmac_t *chip)
+{
+	return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0;
+}
+
+#ifdef PMAC_AMP_AVAIL
+static int toggle_amp_mute(awacs_amp_t *amp, int index, int mute)
+{
+	int vol[2];
+	vol[0] = amp->amp_vol[index][0] & 31;
+	vol[1] = amp->amp_vol[index][1] & 31;
+	if (mute) {
+		vol[0] |= 32;
+		vol[1] |= 32;
+	}
+	return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1);
+}
+#endif
+
+static void snd_pmac_awacs_update_automute(pmac_t *chip, int do_notify)
+{
+	if (chip->auto_mute) {
+#ifdef PMAC_AMP_AVAIL
+		if (chip->mixer_data) {
+			awacs_amp_t *amp = chip->mixer_data;
+			int changed;
+			if (snd_pmac_awacs_detect_headphone(chip)) {
+				changed = toggle_amp_mute(amp, AMP_CH_HD, 0);
+				changed |= toggle_amp_mute(amp, AMP_CH_SPK, 1);
+			} else {
+				changed = toggle_amp_mute(amp, AMP_CH_HD, 1);
+				changed |= toggle_amp_mute(amp, AMP_CH_SPK, 0);
+			}
+			if (do_notify && ! changed)
+				return;
+		} else
+#endif
+		{
+			int reg = chip->awacs_reg[1] | (MASK_HDMUTE|MASK_SPKMUTE);
+			if (snd_pmac_awacs_detect_headphone(chip))
+				reg &= ~MASK_HDMUTE;
+			else
+				reg &= ~MASK_SPKMUTE;
+			if (do_notify && reg == chip->awacs_reg[1])
+				return;
+			snd_pmac_awacs_write_reg(chip, 1, reg);
+		}
+		if (do_notify) {
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->master_sw_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->speaker_sw_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->hp_detect_ctl->id);
+		}
+	}
+}
+#endif /* PMAC_SUPPORT_AUTOMUTE */
+
+
+/*
+ * initialize chip
+ */
+int __init
+snd_pmac_awacs_init(pmac_t *chip)
+{
+	int err, vol;
+
+	/* looks like MASK_GAINLINE triggers something, so we set here
+	 * as start-up
+	 */
+	chip->awacs_reg[0] = MASK_MUX_CD | 0xff | MASK_GAINLINE;
+	chip->awacs_reg[1] = MASK_CMUTE | MASK_AMUTE;
+	/* FIXME: Only machines with external SRS module need MASK_PAROUT */
+	if (chip->has_iic || chip->device_id == 0x5 ||
+	    /*chip->_device_id == 0x8 || */
+	    chip->device_id == 0xb)
+		chip->awacs_reg[1] |= MASK_PAROUT;
+	/* get default volume from nvram */
+	// vol = (~nvram_read_byte(0x1308) & 7) << 1;
+	// vol = ((pmac_xpram_read( 8 ) & 7 ) << 1 );
+	vol = 0x0f; /* no, on alsa, muted as default */
+	vol = vol + (vol << 6);
+	chip->awacs_reg[2] = vol;
+	chip->awacs_reg[4] = vol;
+	if (chip->model == PMAC_SCREAMER) {
+		chip->awacs_reg[5] = vol; /* FIXME: screamer has loopthru vol control */
+		chip->awacs_reg[6] = MASK_MIC_BOOST; /* FIXME: maybe should be vol << 3 for PCMCIA speaker */
+		chip->awacs_reg[7] = 0;
+	}
+
+	awacs_restore_all_regs(chip);
+	chip->manufacturer = (in_le32(&chip->awacs->codec_stat) >> 8) & 0xf;
+	screamer_recalibrate(chip);
+
+	chip->revision = (in_le32(&chip->awacs->codec_stat) >> 12) & 0xf;
+#ifdef PMAC_AMP_AVAIL
+	if (chip->revision == 3 && chip->has_iic && CHECK_CUDA_AMP()) {
+		awacs_amp_t *amp = kmalloc(sizeof(*amp), GFP_KERNEL);
+		if (! amp)
+			return -ENOMEM;
+		chip->mixer_data = amp;
+		memset(amp, 0, sizeof(*amp));
+		chip->mixer_free = awacs_amp_free;
+		awacs_amp_set_vol(amp, 0, 63, 63, 0); /* mute and zero vol */
+		awacs_amp_set_vol(amp, 1, 63, 63, 0);
+		awacs_amp_set_tone(amp, 7, 7); /* 0 dB */
+		awacs_amp_set_master(amp, 79); /* 0 dB */
+	}
+#endif /* PMAC_AMP_AVAIL */
+
+	if (chip->hp_stat_mask == 0) {
+		/* set headphone-jack detection bit */
+		switch (chip->model) {
+		case PMAC_AWACS:
+			chip->hp_stat_mask = 0x04;
+			break;
+		case PMAC_SCREAMER:
+			switch (chip->device_id) {
+			case 0x08:
+				/* 1 = side jack, 2 = front jack */
+				chip->hp_stat_mask = 0x03;
+				break;
+			case 0x00:
+			case 0x05:
+				chip->hp_stat_mask = 0x04;
+				break;
+			default:
+				chip->hp_stat_mask = 0x08;
+				break;
+			}
+			break;
+		default:
+			snd_BUG();
+			break;
+		}
+	}
+
+	/*
+	 * build mixers
+	 */
+	strcpy(chip->card->mixername, "PowerMac AWACS");
+
+	if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers),
+				snd_pmac_awacs_mixers)) < 0)
+		return err;
+	if (chip->model == PMAC_SCREAMER)
+		err = build_mixers(chip, ARRAY_SIZE(snd_pmac_screamer_mixers2),
+				   snd_pmac_screamer_mixers2);
+	else
+		err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers2),
+				   snd_pmac_awacs_mixers2);
+	if (err < 0)
+		return err;
+	chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_master_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0)
+		return err;
+#ifdef PMAC_AMP_AVAIL
+	if (chip->mixer_data) {
+		/* use amplifier.  the signal is connected from route A
+		 * to the amp.  the amp has its headphone and speaker
+		 * volumes and mute switches, so we use them instead of
+		 * screamer registers.
+		 * in this case, it seems the route C is not used.
+		 */
+		if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_amp_vol),
+					snd_pmac_awacs_amp_vol)) < 0)
+			return err;
+		/* overwrite */
+		chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_hp_sw, chip);
+		if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0)
+			return err;
+		chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_spk_sw, chip);
+		if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0)
+			return err;
+	} else
+#endif /* PMAC_AMP_AVAIL */
+	{
+		/* route A = headphone, route C = speaker */
+		if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_speaker_vol),
+					snd_pmac_awacs_speaker_vol)) < 0)
+			return err;
+		chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_speaker_sw, chip);
+		if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0)
+			return err;
+	}
+
+	if (chip->model == PMAC_SCREAMER) {
+		if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_screamer_mic_boost),
+					snd_pmac_screamer_mic_boost)) < 0)
+			return err;
+	} else {
+		if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mic_boost),
+					snd_pmac_awacs_mic_boost)) < 0)
+			return err;
+	}
+
+	/*
+	 * set lowlevel callbacks
+	 */
+	chip->set_format = snd_pmac_awacs_set_format;
+#ifdef CONFIG_PMAC_PBOOK
+	chip->suspend = snd_pmac_awacs_suspend;
+	chip->resume = snd_pmac_awacs_resume;
+#endif
+#ifdef PMAC_SUPPORT_AUTOMUTE
+	if ((err = snd_pmac_add_automute(chip)) < 0)
+		return err;
+	chip->detect_headphone = snd_pmac_awacs_detect_headphone;
+	chip->update_automute = snd_pmac_awacs_update_automute;
+	snd_pmac_awacs_update_automute(chip, 0); /* update the status only */
+#endif
+	if (chip->model == PMAC_SCREAMER) {
+		snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]);
+		snd_pmac_awacs_write_noreg(chip, 0, chip->awacs_reg[0]);
+	}
+
+	return 0;
+}
diff --git a/sound/ppc/awacs.h b/sound/ppc/awacs.h
new file mode 100644
index 000000000000..1b2cc44eda57
--- /dev/null
+++ b/sound/ppc/awacs.h
@@ -0,0 +1,192 @@
+/*
+ * Driver for PowerMac AWACS onboard soundchips
+ * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de>
+ *   based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#ifndef __AWACS_H
+#define __AWACS_H
+
+/*******************************/
+/* AWACs Audio Register Layout */
+/*******************************/
+
+struct awacs_regs {
+    unsigned	control;	/* Audio control register */
+    unsigned	pad0[3];
+    unsigned	codec_ctrl;	/* Codec control register */
+    unsigned	pad1[3];
+    unsigned	codec_stat;	/* Codec status register */
+    unsigned	pad2[3];
+    unsigned	clip_count;	/* Clipping count register */
+    unsigned	pad3[3];
+    unsigned	byteswap;	/* Data is little-endian if 1 */
+};
+
+/*******************/
+/* Audio Bit Masks */
+/*******************/
+
+/* Audio Control Reg Bit Masks */
+/* ----- ------- --- --- ----- */
+#define MASK_ISFSEL	(0xf)		/* Input SubFrame Select */
+#define MASK_OSFSEL	(0xf << 4)	/* Output SubFrame Select */
+#define MASK_RATE	(0x7 << 8)	/* Sound Rate */
+#define MASK_CNTLERR	(0x1 << 11)	/* Error */
+#define MASK_PORTCHG	(0x1 << 12)	/* Port Change */
+#define MASK_IEE	(0x1 << 13)	/* Enable Interrupt on Error */
+#define MASK_IEPC	(0x1 << 14)	/* Enable Interrupt on Port Change */
+#define MASK_SSFSEL	(0x3 << 15)	/* Status SubFrame Select */
+
+/* Audio Codec Control Reg Bit Masks */
+/* ----- ----- ------- --- --- ----- */
+#define MASK_NEWECMD	(0x1 << 24)	/* Lock: don't write to reg when 1 */
+#define MASK_EMODESEL	(0x3 << 22)	/* Send info out on which frame? */
+#define MASK_EXMODEADDR	(0x3ff << 12)	/* Extended Mode Address -- 10 bits */
+#define MASK_EXMODEDATA	(0xfff)		/* Extended Mode Data -- 12 bits */
+
+/* Audio Codec Control Address Values / Masks */
+/* ----- ----- ------- ------- ------ - ----- */
+#define MASK_ADDR0	(0x0 << 12)	/* Expanded Data Mode Address 0 */
+#define MASK_ADDR_MUX	MASK_ADDR0	/* Mux Control */
+#define MASK_ADDR_GAIN	MASK_ADDR0
+
+#define MASK_ADDR1	(0x1 << 12)	/* Expanded Data Mode Address 1 */
+#define MASK_ADDR_MUTE	MASK_ADDR1
+#define MASK_ADDR_RATE	MASK_ADDR1
+
+#define MASK_ADDR2	(0x2 << 12)	/* Expanded Data Mode Address 2 */
+#define MASK_ADDR_VOLA	MASK_ADDR2	/* Volume Control A -- Headphones */
+#define MASK_ADDR_VOLHD MASK_ADDR2
+
+#define MASK_ADDR4	(0x4 << 12)	/* Expanded Data Mode Address 4 */
+#define MASK_ADDR_VOLC	MASK_ADDR4	/* Volume Control C -- Speaker */
+#define MASK_ADDR_VOLSPK MASK_ADDR4
+
+/* additional registers of screamer */
+#define MASK_ADDR5	(0x5 << 12)	/* Expanded Data Mode Address 5 */
+#define MASK_ADDR6	(0x6 << 12)	/* Expanded Data Mode Address 6 */
+#define MASK_ADDR7	(0x7 << 12)	/* Expanded Data Mode Address 7 */
+
+/* Address 0 Bit Masks & Macros */
+/* ------- - --- ----- - ------ */
+#define MASK_GAINRIGHT	(0xf)		/* Gain Right Mask */
+#define MASK_GAINLEFT	(0xf << 4)	/* Gain Left Mask */
+#define MASK_GAINLINE	(0x1 << 8)	/* Disable Mic preamp */
+#define MASK_GAINMIC	(0x0 << 8)	/* Enable Mic preamp */
+#define MASK_MUX_CD	(0x1 << 9)	/* Select CD in MUX */
+#define MASK_MUX_MIC	(0x1 << 10)	/* Select Mic in MUX */
+#define MASK_MUX_AUDIN	(0x1 << 11)	/* Select Audio In in MUX */
+#define MASK_MUX_LINE	MASK_MUX_AUDIN
+#define SHIFT_GAINLINE	8
+#define SHIFT_MUX_CD	9
+#define SHIFT_MUX_MIC	10
+#define SHIFT_MUX_LINE	11
+
+#define GAINRIGHT(x)	((x) & MASK_GAINRIGHT)
+#define GAINLEFT(x)	(((x) << 4) & MASK_GAINLEFT)
+
+/* Address 1 Bit Masks */
+/* ------- - --- ----- */
+#define MASK_ADDR1RES1	(0x3)		/* Reserved */
+#define MASK_RECALIBRATE (0x1 << 2)	/* Recalibrate */
+#define MASK_SAMPLERATE	(0x7 << 3)	/* Sample Rate: */
+#define MASK_LOOPTHRU	(0x1 << 6)	/* Loopthrough Enable */
+#define SHIFT_LOOPTHRU	6
+#define MASK_CMUTE	(0x1 << 7)	/* Output C (Speaker) Mute when 1 */
+#define MASK_SPKMUTE	MASK_CMUTE
+#define SHIFT_SPKMUTE	7
+#define MASK_ADDR1RES2	(0x1 << 8)	/* Reserved */
+#define MASK_AMUTE	(0x1 << 9)	/* Output A (Headphone) Mute when 1 */
+#define MASK_HDMUTE	MASK_AMUTE
+#define SHIFT_HDMUTE	9
+#define MASK_PAROUT	(0x3 << 10)	/* Parallel Out (???) */
+
+#define SAMPLERATE_48000	(0x0 << 3)	/* 48 or 44.1 kHz */
+#define SAMPLERATE_32000	(0x1 << 3)	/* 32 or 29.4 kHz */
+#define SAMPLERATE_24000	(0x2 << 3)	/* 24 or 22.05 kHz */
+#define SAMPLERATE_19200	(0x3 << 3)	/* 19.2 or 17.64 kHz */
+#define SAMPLERATE_16000	(0x4 << 3)	/* 16 or 14.7 kHz */
+#define SAMPLERATE_12000	(0x5 << 3)	/* 12 or 11.025 kHz */
+#define SAMPLERATE_9600		(0x6 << 3)	/* 9.6 or 8.82 kHz */
+#define SAMPLERATE_8000		(0x7 << 3)	/* 8 or 7.35 kHz */
+
+/* Address 2 & 4 Bit Masks & Macros */
+/* ------- - - - --- ----- - ------ */
+#define MASK_OUTVOLRIGHT (0xf)		/* Output Right Volume */
+#define MASK_ADDR2RES1	(0x2 << 4)	/* Reserved */
+#define MASK_ADDR4RES1	MASK_ADDR2RES1
+#define MASK_OUTVOLLEFT	(0xf << 6)	/* Output Left Volume */
+#define MASK_ADDR2RES2	(0x2 << 10)	/* Reserved */
+#define MASK_ADDR4RES2	MASK_ADDR2RES2
+
+#define VOLRIGHT(x)	(((~(x)) & MASK_OUTVOLRIGHT))
+#define VOLLEFT(x)	(((~(x)) << 6) & MASK_OUTVOLLEFT)
+
+/* address 6 */
+#define MASK_MIC_BOOST  (0x4)           /* screamer mic boost */
+#define SHIFT_MIC_BOOST	2
+
+/* Audio Codec Status Reg Bit Masks */
+/* ----- ----- ------ --- --- ----- */
+#define MASK_EXTEND	(0x1 << 23)	/* Extend */
+#define MASK_VALID	(0x1 << 22)	/* Valid Data? */
+#define MASK_OFLEFT	(0x1 << 21)	/* Overflow Left */
+#define MASK_OFRIGHT	(0x1 << 20)	/* Overflow Right */
+#define MASK_ERRCODE	(0xf << 16)	/* Error Code */
+#define MASK_REVISION	(0xf << 12)	/* Revision Number */
+#define MASK_MFGID	(0xf << 8)	/* Mfg. ID */
+#define MASK_CODSTATRES	(0xf << 4)	/* bits 4 - 7 reserved */
+#define MASK_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. */
+
+
+#endif /* __AWACS_H */
diff --git a/sound/ppc/beep.c b/sound/ppc/beep.c
new file mode 100644
index 000000000000..c23f601a37f9
--- /dev/null
+++ b/sound/ppc/beep.c
@@ -0,0 +1,262 @@
+/*
+ * Beep using pcm
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include "pmac.h"
+
+struct snd_pmac_beep {
+	int running;	/* boolean */
+	int volume;	/* mixer volume: 0-100 */
+	int volume_play;	/* currently playing volume */
+	int hz;
+	int nsamples;
+	short *buf;		/* allocated wave buffer */
+	unsigned long addr;	/* physical address of buffer */
+	struct input_dev dev;
+};
+
+/*
+ * stop beep if running
+ */
+void snd_pmac_beep_stop(pmac_t *chip)
+{
+	pmac_beep_t *beep = chip->beep;
+	if (beep && beep->running) {
+		beep->running = 0;
+		snd_pmac_beep_dma_stop(chip);
+	}
+}
+
+/*
+ * Stuff for outputting a beep.  The values range from -327 to +327
+ * so we can multiply by an amplitude in the range 0..100 to get a
+ * signed short value to put in the output buffer.
+ */
+static short beep_wform[256] = {
+	0,	40,	79,	117,	153,	187,	218,	245,
+	269,	288,	304,	316,	323,	327,	327,	324,
+	318,	310,	299,	288,	275,	262,	249,	236,
+	224,	213,	204,	196,	190,	186,	183,	182,
+	182,	183,	186,	189,	192,	196,	200,	203,
+	206,	208,	209,	209,	209,	207,	204,	201,
+	197,	193,	188,	183,	179,	174,	170,	166,
+	163,	161,	160,	159,	159,	160,	161,	162,
+	164,	166,	168,	169,	171,	171,	171,	170,
+	169,	167,	163,	159,	155,	150,	144,	139,
+	133,	128,	122,	117,	113,	110,	107,	105,
+	103,	103,	103,	103,	104,	104,	105,	105,
+	105,	103,	101,	97,	92,	86,	78,	68,
+	58,	45,	32,	18,	3,	-11,	-26,	-41,
+	-55,	-68,	-79,	-88,	-95,	-100,	-102,	-102,
+	-99,	-93,	-85,	-75,	-62,	-48,	-33,	-16,
+	0,	16,	33,	48,	62,	75,	85,	93,
+	99,	102,	102,	100,	95,	88,	79,	68,
+	55,	41,	26,	11,	-3,	-18,	-32,	-45,
+	-58,	-68,	-78,	-86,	-92,	-97,	-101,	-103,
+	-105,	-105,	-105,	-104,	-104,	-103,	-103,	-103,
+	-103,	-105,	-107,	-110,	-113,	-117,	-122,	-128,
+	-133,	-139,	-144,	-150,	-155,	-159,	-163,	-167,
+	-169,	-170,	-171,	-171,	-171,	-169,	-168,	-166,
+	-164,	-162,	-161,	-160,	-159,	-159,	-160,	-161,
+	-163,	-166,	-170,	-174,	-179,	-183,	-188,	-193,
+	-197,	-201,	-204,	-207,	-209,	-209,	-209,	-208,
+	-206,	-203,	-200,	-196,	-192,	-189,	-186,	-183,
+	-182,	-182,	-183,	-186,	-190,	-196,	-204,	-213,
+	-224,	-236,	-249,	-262,	-275,	-288,	-299,	-310,
+	-318,	-324,	-327,	-327,	-323,	-316,	-304,	-288,
+	-269,	-245,	-218,	-187,	-153,	-117,	-79,	-40,
+};
+
+#define BEEP_SRATE	22050	/* 22050 Hz sample rate */
+#define BEEP_BUFLEN	512
+#define BEEP_VOLUME	15	/* 0 - 100 */
+
+static int snd_pmac_beep_event(struct input_dev *dev, unsigned int type, unsigned int code, int hz)
+{
+	pmac_t *chip;
+	pmac_beep_t *beep;
+	unsigned long flags;
+	int beep_speed = 0;
+	int srate;
+	int period, ncycles, nsamples;
+	int i, j, f;
+	short *p;
+
+	if (type != EV_SND)
+		return -1;
+
+	switch (code) {
+	case SND_BELL: if (hz) hz = 1000;
+	case SND_TONE: break;
+	default: return -1;
+	}
+
+	chip = dev->private;
+	if (! chip || (beep = chip->beep) == NULL)
+		return -1;
+
+	if (! hz) {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if (beep->running)
+			snd_pmac_beep_stop(chip);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return 0;
+	}
+
+	beep_speed = snd_pmac_rate_index(chip, &chip->playback, BEEP_SRATE);
+	srate = chip->freq_table[beep_speed];
+
+	if (hz <= srate / BEEP_BUFLEN || hz > srate / 2)
+		hz = 1000;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->playback.running || chip->capture.running || beep->running) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return 0;
+	}
+	beep->running = 1;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	if (hz == beep->hz && beep->volume == beep->volume_play) {
+		nsamples = beep->nsamples;
+	} else {
+		period = srate * 256 / hz;	/* fixed point */
+		ncycles = BEEP_BUFLEN * 256 / period;
+		nsamples = (period * ncycles) >> 8;
+		f = ncycles * 65536 / nsamples;
+		j = 0;
+		p = beep->buf;
+		for (i = 0; i < nsamples; ++i, p += 2) {
+			p[0] = p[1] = beep_wform[j >> 8] * beep->volume;
+			j = (j + f) & 0xffff;
+		}
+		beep->hz = hz;
+		beep->volume_play = beep->volume;
+		beep->nsamples = nsamples;
+	}
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_pmac_beep_dma_start(chip, beep->nsamples * 4, beep->addr, beep_speed);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+/*
+ * beep volume mixer
+ */
+
+#define chip_t pmac_t
+
+static int snd_pmac_info_beep(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+
+static int snd_pmac_get_beep(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	snd_assert(chip->beep, return -ENXIO);
+	ucontrol->value.integer.value[0] = chip->beep->volume;
+	return 0;
+}
+
+static int snd_pmac_put_beep(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int oval;
+	snd_assert(chip->beep, return -ENXIO);
+	oval = chip->beep->volume;
+	chip->beep->volume = ucontrol->value.integer.value[0];
+	return oval != chip->beep->volume;
+}
+
+static snd_kcontrol_new_t snd_pmac_beep_mixer = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Beep Playback Volume",
+	.info = snd_pmac_info_beep,
+	.get = snd_pmac_get_beep,
+	.put = snd_pmac_put_beep,
+};
+
+/* Initialize beep stuff */
+int __init snd_pmac_attach_beep(pmac_t *chip)
+{
+	pmac_beep_t *beep;
+	int err;
+
+	beep = kmalloc(sizeof(*beep), GFP_KERNEL);
+	if (! beep)
+		return -ENOMEM;
+
+	memset(beep, 0, sizeof(*beep));
+	beep->buf = (short *) kmalloc(BEEP_BUFLEN * 4, GFP_KERNEL);
+	if (! beep->buf) {
+		kfree(beep);
+		return -ENOMEM;
+	}
+	beep->addr = virt_to_bus(beep->buf);
+
+	beep->dev.evbit[0] = BIT(EV_SND);
+	beep->dev.sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE);
+	beep->dev.event = snd_pmac_beep_event;
+	beep->dev.private = chip;
+
+	/* FIXME: set more better values */
+	beep->dev.name = "PowerMac Beep";
+	beep->dev.phys = "powermac/beep";
+	beep->dev.id.bustype = BUS_ADB;
+	beep->dev.id.vendor = 0x001f;
+	beep->dev.id.product = 0x0001;
+	beep->dev.id.version = 0x0100;
+
+	beep->volume = BEEP_VOLUME;
+	beep->running = 0;
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_pmac_beep_mixer, chip))) < 0) {
+		kfree(beep->buf);
+		kfree(beep);
+		return err;
+	}
+
+	chip->beep = beep;
+	input_register_device(&beep->dev);
+
+	return 0;
+}
+
+void snd_pmac_detach_beep(pmac_t *chip)
+{
+	if (chip->beep) {
+		input_unregister_device(&chip->beep->dev);
+		kfree(chip->beep->buf);
+		kfree(chip->beep);
+		chip->beep = NULL;
+	}
+}
diff --git a/sound/ppc/burgundy.c b/sound/ppc/burgundy.c
new file mode 100644
index 000000000000..3f837d9f3eb1
--- /dev/null
+++ b/sound/ppc/burgundy.c
@@ -0,0 +1,439 @@
+/*
+ * PMac Burgundy lowlevel functions
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ * code based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include "pmac.h"
+#include "burgundy.h"
+
+
+/* Waits for busy flag to clear */
+inline static void
+snd_pmac_burgundy_busy_wait(pmac_t *chip)
+{
+	int timeout = 50;
+	while ((in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) && timeout--)
+		udelay(1);
+	if (! timeout)
+		printk(KERN_DEBUG "burgundy_busy_wait: timeout\n");
+}
+
+inline static void
+snd_pmac_burgundy_extend_wait(pmac_t *chip)
+{
+	int timeout;
+	timeout = 50;
+	while (!(in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--)
+		udelay(1);
+	if (! timeout)
+		printk(KERN_DEBUG "burgundy_extend_wait: timeout #1\n");
+	timeout = 50;
+	while ((in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--)
+		udelay(1);
+	if (! timeout)
+		printk(KERN_DEBUG "burgundy_extend_wait: timeout #2\n");
+}
+
+static void
+snd_pmac_burgundy_wcw(pmac_t *chip, unsigned addr, unsigned val)
+{
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x200c00 + (val & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x200d00 +((val>>8) & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x200e00 +((val>>16) & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x200f00 +((val>>24) & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+}
+
+static unsigned
+snd_pmac_burgundy_rcw(pmac_t *chip, unsigned addr)
+{
+	unsigned val = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100000);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += (in_le32(&chip->awacs->codec_stat) >> 4) & 0xff;
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100100);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<8;
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100200);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<16;
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100300);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<24;
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return val;
+}
+
+static void
+snd_pmac_burgundy_wcb(pmac_t *chip, unsigned int addr, unsigned int val)
+{
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x300000 + (val & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+}
+
+static unsigned
+snd_pmac_burgundy_rcb(pmac_t *chip, unsigned int addr)
+{
+	unsigned val = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100000);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += (in_le32(&chip->awacs->codec_stat) >> 4) & 0xff;
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return val;
+}
+
+/*
+ * Burgundy volume: 0 - 100, stereo
+ */
+static void
+snd_pmac_burgundy_write_volume(pmac_t *chip, unsigned int address, long *volume, int shift)
+{
+	int hardvolume, lvolume, rvolume;
+
+	lvolume = volume[0] ? volume[0] + BURGUNDY_VOLUME_OFFSET : 0;
+	rvolume = volume[1] ? volume[1] + BURGUNDY_VOLUME_OFFSET : 0;
+
+	hardvolume = lvolume + (rvolume << shift);
+	if (shift == 8)
+		hardvolume |= hardvolume << 16;
+
+	snd_pmac_burgundy_wcw(chip, address, hardvolume);
+}
+
+static void
+snd_pmac_burgundy_read_volume(pmac_t *chip, unsigned int address, long *volume, int shift)
+{
+	int wvolume;
+
+	wvolume = snd_pmac_burgundy_rcw(chip, address);
+
+	volume[0] = wvolume & 0xff;
+	if (volume[0] >= BURGUNDY_VOLUME_OFFSET)
+		volume[0] -= BURGUNDY_VOLUME_OFFSET;
+	else
+		volume[0] = 0;
+	volume[1] = (wvolume >> shift) & 0xff;
+	if (volume[1] >= BURGUNDY_VOLUME_OFFSET)
+		volume[1] -= BURGUNDY_VOLUME_OFFSET;
+	else
+		volume[1] = 0;
+}
+
+
+/*
+ */
+
+#define BASE2ADDR(base)	((base) << 12)
+#define ADDR2BASE(addr)	((addr) >> 12)
+
+static int snd_pmac_burgundy_info_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+
+static int snd_pmac_burgundy_get_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	snd_pmac_burgundy_read_volume(chip, addr, ucontrol->value.integer.value, shift);
+	return 0;
+}
+
+static int snd_pmac_burgundy_put_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	long nvoices[2];
+
+	snd_pmac_burgundy_write_volume(chip, addr, ucontrol->value.integer.value, shift);
+	snd_pmac_burgundy_read_volume(chip, addr, nvoices, shift);
+	return (nvoices[0] != ucontrol->value.integer.value[0] ||
+		nvoices[1] != ucontrol->value.integer.value[1]);
+}
+
+#define BURGUNDY_VOLUME(xname, xindex, addr, shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+  .info = snd_pmac_burgundy_info_volume,\
+  .get = snd_pmac_burgundy_get_volume,\
+  .put = snd_pmac_burgundy_put_volume,\
+  .private_value = ((ADDR2BASE(addr) & 0xff) | ((shift) << 8)) }
+
+/* lineout/speaker */
+
+static int snd_pmac_burgundy_info_switch_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = stereo + 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_pmac_burgundy_get_switch_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int lmask = kcontrol->private_value & 0xff;
+	int rmask = (kcontrol->private_value >> 8) & 0xff;
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int val = snd_pmac_burgundy_rcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES);
+	ucontrol->value.integer.value[0] = (val & lmask) ? 1 : 0;
+	if (stereo)
+		ucontrol->value.integer.value[1] = (val & rmask) ? 1 : 0;
+	return 0;
+}
+
+static int snd_pmac_burgundy_put_switch_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	int lmask = kcontrol->private_value & 0xff;
+	int rmask = (kcontrol->private_value >> 8) & 0xff;
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int val, oval;
+	oval = snd_pmac_burgundy_rcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES);
+	val = oval & ~(lmask | rmask);
+	if (ucontrol->value.integer.value[0])
+		val |= lmask;
+	if (stereo && ucontrol->value.integer.value[1])
+		val |= rmask;
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, val);
+	return val != oval;
+}
+
+#define BURGUNDY_OUTPUT_SWITCH(xname, xindex, lmask, rmask, stereo) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+  .info = snd_pmac_burgundy_info_switch_out,\
+  .get = snd_pmac_burgundy_get_switch_out,\
+  .put = snd_pmac_burgundy_put_switch_out,\
+  .private_value = ((lmask) | ((rmask) << 8) | ((stereo) << 24)) }
+
+/* line/speaker output volume */
+static int snd_pmac_burgundy_info_volume_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = stereo + 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 15;
+	return 0;
+}
+
+static int snd_pmac_burgundy_get_volume_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int oval;
+
+	oval = ~snd_pmac_burgundy_rcb(chip, addr) & 0xff;
+	ucontrol->value.integer.value[0] = oval & 0xf;
+	if (stereo)
+		ucontrol->value.integer.value[1] = (oval >> 4) & 0xf;
+	return 0;
+}
+
+static int snd_pmac_burgundy_put_volume_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int oval, val;
+
+	oval = ~snd_pmac_burgundy_rcb(chip, addr) & 0xff;
+	val = ucontrol->value.integer.value[0];
+	if (stereo)
+		val |= ucontrol->value.integer.value[1] << 4;
+	else
+		val |= ucontrol->value.integer.value[0] << 4;
+	val = ~val & 0xff;
+	snd_pmac_burgundy_wcb(chip, addr, val);
+	return val != oval;
+}
+
+#define BURGUNDY_OUTPUT_VOLUME(xname, xindex, addr, stereo) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+  .info = snd_pmac_burgundy_info_volume_out,\
+  .get = snd_pmac_burgundy_get_volume_out,\
+  .put = snd_pmac_burgundy_put_volume_out,\
+  .private_value = (ADDR2BASE(addr) | ((stereo) << 24)) }
+
+static snd_kcontrol_new_t snd_pmac_burgundy_mixers[] __initdata = {
+	BURGUNDY_VOLUME("Master Playback Volume", 0, MASK_ADDR_BURGUNDY_MASTER_VOLUME, 8),
+	BURGUNDY_VOLUME("Line Playback Volume", 0, MASK_ADDR_BURGUNDY_VOLLINE, 16),
+	BURGUNDY_VOLUME("CD Playback Volume", 0, MASK_ADDR_BURGUNDY_VOLCD, 16),
+	BURGUNDY_VOLUME("Mic Playback Volume", 0, MASK_ADDR_BURGUNDY_VOLMIC, 16),
+	BURGUNDY_OUTPUT_VOLUME("PC Speaker Playback Volume", 0, MASK_ADDR_BURGUNDY_ATTENHP, 0),
+	/*BURGUNDY_OUTPUT_VOLUME("PCM Playback Volume", 0, MASK_ADDR_BURGUNDY_ATTENLINEOUT, 1),*/
+	BURGUNDY_OUTPUT_VOLUME("Headphone Playback Volume", 0, MASK_ADDR_BURGUNDY_ATTENSPEAKER, 1),
+};	
+static snd_kcontrol_new_t snd_pmac_burgundy_master_sw __initdata = 
+BURGUNDY_OUTPUT_SWITCH("Headphone Playback Switch", 0, BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1);
+static snd_kcontrol_new_t snd_pmac_burgundy_speaker_sw __initdata = 
+BURGUNDY_OUTPUT_SWITCH("PC Speaker Playback Switch", 0, BURGUNDY_OUTPUT_INTERN, 0, 0);
+
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+/*
+ * auto-mute stuffs
+ */
+static int snd_pmac_burgundy_detect_headphone(pmac_t *chip)
+{
+	return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0;
+}
+
+static void snd_pmac_burgundy_update_automute(pmac_t *chip, int do_notify)
+{
+	if (chip->auto_mute) {
+		int reg, oreg;
+		reg = oreg = snd_pmac_burgundy_rcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES);
+		reg &= ~(BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT | BURGUNDY_OUTPUT_INTERN);
+		if (snd_pmac_burgundy_detect_headphone(chip))
+			reg |= BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT;
+		else
+			reg |= BURGUNDY_OUTPUT_INTERN;
+		if (do_notify && reg == oreg)
+			return;
+		snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, reg);
+		if (do_notify) {
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->master_sw_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->speaker_sw_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->hp_detect_ctl->id);
+		}
+	}
+}
+#endif /* PMAC_SUPPORT_AUTOMUTE */
+
+
+/*
+ * initialize burgundy
+ */
+int __init snd_pmac_burgundy_init(pmac_t *chip)
+{
+	int i, err;
+
+	/* Checks to see the chip is alive and kicking */
+	if ((in_le32(&chip->awacs->codec_ctrl) & MASK_ERRCODE) == 0xf0000) {
+		printk(KERN_WARNING "pmac burgundy: disabled by MacOS :-(\n");
+		return 1;
+	}
+
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_OUTPUTENABLES,
+			   DEF_BURGUNDY_OUTPUTENABLES);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+			   DEF_BURGUNDY_MORE_OUTPUTENABLES);
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_OUTPUTSELECTS,
+			   DEF_BURGUNDY_OUTPUTSELECTS);
+
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_INPSEL21,
+			   DEF_BURGUNDY_INPSEL21);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_INPSEL3,
+			   DEF_BURGUNDY_INPSEL3);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINCD,
+			   DEF_BURGUNDY_GAINCD);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINLINE,
+			   DEF_BURGUNDY_GAINLINE);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINMIC,
+			   DEF_BURGUNDY_GAINMIC);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINMODEM,
+			   DEF_BURGUNDY_GAINMODEM);
+
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENSPEAKER,
+			   DEF_BURGUNDY_ATTENSPEAKER);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENLINEOUT,
+			   DEF_BURGUNDY_ATTENLINEOUT);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENHP,
+			   DEF_BURGUNDY_ATTENHP);
+
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_MASTER_VOLUME,
+			   DEF_BURGUNDY_MASTER_VOLUME);
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLCD,
+			   DEF_BURGUNDY_VOLCD);
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLLINE,
+			   DEF_BURGUNDY_VOLLINE);
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLMIC,
+			   DEF_BURGUNDY_VOLMIC);
+
+	if (chip->hp_stat_mask == 0)
+		/* set headphone-jack detection bit */
+		chip->hp_stat_mask = 0x04;
+
+	/*
+	 * build burgundy mixers
+	 */
+	strcpy(chip->card->mixername, "PowerMac Burgundy");
+
+	for (i = 0; i < ARRAY_SIZE(snd_pmac_burgundy_mixers); i++) {
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_pmac_burgundy_mixers[i], chip))) < 0)
+			return err;
+	}
+	chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_burgundy_master_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0)
+		return err;
+	chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_burgundy_speaker_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0)
+		return err;
+#ifdef PMAC_SUPPORT_AUTOMUTE
+	if ((err = snd_pmac_add_automute(chip)) < 0)
+		return err;
+
+	chip->detect_headphone = snd_pmac_burgundy_detect_headphone;
+	chip->update_automute = snd_pmac_burgundy_update_automute;
+	snd_pmac_burgundy_update_automute(chip, 0); /* update the status only */
+#endif
+
+	return 0;
+}
diff --git a/sound/ppc/burgundy.h b/sound/ppc/burgundy.h
new file mode 100644
index 000000000000..ebb457a8342c
--- /dev/null
+++ b/sound/ppc/burgundy.h
@@ -0,0 +1,95 @@
+/*
+ * Driver for PowerMac Burgundy onboard soundchips
+ * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de>
+ *   based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#ifndef __BURGUNDY_H
+#define __BURGUNDY_H
+
+#define MASK_ADDR_BURGUNDY_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) */ /* too loud */
+#define DEF_BURGUNDY_MASTER_VOLUME (0xDDDDDDDD)
+
+#define DEF_BURGUNDY_MORE_OUTPUTENABLES (0x7E)
+
+#define DEF_BURGUNDY_ATTENSPEAKER (0x44)
+#define DEF_BURGUNDY_ATTENLINEOUT (0xCC)
+#define DEF_BURGUNDY_ATTENHP (0xCC)
+
+/* OUTPUTENABLES bits */
+#define BURGUNDY_OUTPUT_LEFT	0x02
+#define BURGUNDY_OUTPUT_RIGHT	0x04
+#define BURGUNDY_OUTPUT_INTERN	0x80
+
+/* volume offset */
+#define BURGUNDY_VOLUME_OFFSET	155
+
+#endif /* __BURGUNDY_H */
diff --git a/sound/ppc/daca.c b/sound/ppc/daca.c
new file mode 100644
index 000000000000..f24a91693616
--- /dev/null
+++ b/sound/ppc/daca.c
@@ -0,0 +1,283 @@
+/*
+ * PMac DACA lowlevel functions
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <linux/kmod.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "pmac.h"
+
+/* i2c address */
+#define DACA_I2C_ADDR	0x4d
+
+/* registers */
+#define DACA_REG_SR	0x01
+#define DACA_REG_AVOL	0x02
+#define DACA_REG_GCFG	0x03
+
+/* maximum volume value */
+#define DACA_VOL_MAX	0x38
+
+
+typedef struct pmac_daca_t {
+	pmac_keywest_t i2c;
+	int left_vol, right_vol;
+	unsigned int deemphasis : 1;
+	unsigned int amp_on : 1;
+} pmac_daca_t;
+
+
+/*
+ * initialize / detect DACA
+ */
+static int daca_init_client(pmac_keywest_t *i2c)
+{
+	unsigned short wdata = 0x00;
+	/* SR: no swap, 1bit delay, 32-48kHz */
+	/* GCFG: power amp inverted, DAC on */
+	if (i2c_smbus_write_byte_data(i2c->client, DACA_REG_SR, 0x08) < 0 ||
+	    i2c_smbus_write_byte_data(i2c->client, DACA_REG_GCFG, 0x05) < 0)
+		return -EINVAL;
+	return i2c_smbus_write_block_data(i2c->client, DACA_REG_AVOL,
+					  2, (unsigned char*)&wdata);
+}
+
+/*
+ * update volume
+ */
+static int daca_set_volume(pmac_daca_t *mix)
+{
+	unsigned char data[2];
+  
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	if (mix->left_vol > DACA_VOL_MAX)
+		data[0] = DACA_VOL_MAX;
+	else
+		data[0] = mix->left_vol;
+	if (mix->right_vol > DACA_VOL_MAX)
+		data[1] = DACA_VOL_MAX;
+	else
+		data[1] = mix->right_vol;
+	data[1] |= mix->deemphasis ? 0x40 : 0;
+	if (i2c_smbus_write_block_data(mix->i2c.client, DACA_REG_AVOL,
+				       2, data) < 0) {
+		snd_printk("failed to set volume \n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/* deemphasis switch */
+static int daca_info_deemphasis(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int daca_get_deemphasis(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_daca_t *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->deemphasis ? 1 : 0;
+	return 0;
+}
+
+static int daca_put_deemphasis(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_daca_t *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->deemphasis != ucontrol->value.integer.value[0];
+	if (change) {
+		mix->deemphasis = ucontrol->value.integer.value[0];
+		daca_set_volume(mix);
+	}
+	return change;
+}
+
+/* output volume */
+static int daca_info_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = DACA_VOL_MAX;
+	return 0;
+}
+
+static int daca_get_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_daca_t *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->left_vol;
+	ucontrol->value.integer.value[1] = mix->right_vol;
+	return 0;
+}
+
+static int daca_put_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_daca_t *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->left_vol != ucontrol->value.integer.value[0] ||
+		mix->right_vol != ucontrol->value.integer.value[1];
+	if (change) {
+		mix->left_vol = ucontrol->value.integer.value[0];
+		mix->right_vol = ucontrol->value.integer.value[1];
+		daca_set_volume(mix);
+	}
+	return change;
+}
+
+/* amplifier switch */
+#define daca_info_amp	daca_info_deemphasis
+
+static int daca_get_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_daca_t *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->amp_on ? 1 : 0;
+	return 0;
+}
+
+static int daca_put_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_daca_t *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->amp_on != ucontrol->value.integer.value[0];
+	if (change) {
+		mix->amp_on = ucontrol->value.integer.value[0];
+		i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_GCFG,
+					  mix->amp_on ? 0x05 : 0x04);
+	}
+	return change;
+}
+
+static snd_kcontrol_new_t daca_mixers[] = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Deemphasis Switch",
+	  .info = daca_info_deemphasis,
+	  .get = daca_get_deemphasis,
+	  .put = daca_put_deemphasis
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Volume",
+	  .info = daca_info_volume,
+	  .get = daca_get_volume,
+	  .put = daca_put_volume
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Power Amplifier Switch",
+	  .info = daca_info_amp,
+	  .get = daca_get_amp,
+	  .put = daca_put_amp
+	},
+};
+
+
+#ifdef CONFIG_PMAC_PBOOK
+static void daca_resume(pmac_t *chip)
+{
+	pmac_daca_t *mix = chip->mixer_data;
+	i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_SR, 0x08);
+	i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_GCFG,
+				  mix->amp_on ? 0x05 : 0x04);
+	daca_set_volume(mix);
+}
+#endif /* CONFIG_PMAC_PBOOK */
+
+
+static void daca_cleanup(pmac_t *chip)
+{
+	pmac_daca_t *mix = chip->mixer_data;
+	if (! mix)
+		return;
+	snd_pmac_keywest_cleanup(&mix->i2c);
+	kfree(mix);
+	chip->mixer_data = NULL;
+}
+
+/* exported */
+int __init snd_pmac_daca_init(pmac_t *chip)
+{
+	int i, err;
+	pmac_daca_t *mix;
+
+#ifdef CONFIG_KMOD
+	if (current->fs->root)
+		request_module("i2c-keywest");
+#endif /* CONFIG_KMOD */	
+
+	mix = kmalloc(sizeof(*mix), GFP_KERNEL);
+	if (! mix)
+		return -ENOMEM;
+	memset(mix, 0, sizeof(*mix));
+	chip->mixer_data = mix;
+	chip->mixer_free = daca_cleanup;
+	mix->amp_on = 1; /* default on */
+
+	mix->i2c.addr = DACA_I2C_ADDR;
+	mix->i2c.init_client = daca_init_client;
+	mix->i2c.name = "DACA";
+	if ((err = snd_pmac_keywest_init(&mix->i2c)) < 0)
+		return err;
+
+	/*
+	 * build mixers
+	 */
+	strcpy(chip->card->mixername, "PowerMac DACA");
+
+	for (i = 0; i < ARRAY_SIZE(daca_mixers); i++) {
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&daca_mixers[i], chip))) < 0)
+			return err;
+	}
+
+#ifdef CONFIG_PMAC_PBOOK
+	chip->resume = daca_resume;
+#endif
+
+	return 0;
+}
diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c
new file mode 100644
index 000000000000..df073a05b5d7
--- /dev/null
+++ b/sound/ppc/keywest.c
@@ -0,0 +1,143 @@
+/*
+ * common keywest i2c layer
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/i2c-dev.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "pmac.h"
+
+/*
+ * we have to keep a static variable here since i2c attach_adapter
+ * callback cannot pass a private data.
+ */
+static pmac_keywest_t *keywest_ctx;
+
+
+#define I2C_DRIVERID_KEYWEST	0xFEBA
+
+static int keywest_attach_adapter(struct i2c_adapter *adapter);
+static int keywest_detach_client(struct i2c_client *client);
+
+struct i2c_driver keywest_driver = {  
+	.name = "PMac Keywest Audio",
+	.id = I2C_DRIVERID_KEYWEST,
+	.flags = I2C_DF_NOTIFY,
+	.attach_adapter = &keywest_attach_adapter,
+	.detach_client = &keywest_detach_client,
+};
+
+
+#ifndef i2c_device_name
+#define i2c_device_name(x)	((x)->name)
+#endif
+
+static int keywest_attach_adapter(struct i2c_adapter *adapter)
+{
+	int err;
+	struct i2c_client *new_client;
+
+	if (! keywest_ctx)
+		return -EINVAL;
+
+	if (strncmp(i2c_device_name(adapter), "mac-io", 6))
+		return 0; /* ignored */
+
+	new_client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
+	if (! new_client)
+		return -ENOMEM;
+
+	memset(new_client, 0, sizeof(*new_client));
+	new_client->addr = keywest_ctx->addr;
+	i2c_set_clientdata(new_client, keywest_ctx);
+	new_client->adapter = adapter;
+	new_client->driver = &keywest_driver;
+	new_client->flags = 0;
+
+	strcpy(i2c_device_name(new_client), keywest_ctx->name);
+	keywest_ctx->client = new_client;
+	
+	/* Tell the i2c layer a new client has arrived */
+	if (i2c_attach_client(new_client)) {
+		snd_printk(KERN_ERR "tumbler: cannot attach i2c client\n");
+		err = -ENODEV;
+		goto __err;
+	}
+
+	return 0;
+
+ __err:
+	kfree(new_client);
+	keywest_ctx->client = NULL;
+	return err;
+}
+
+static int keywest_detach_client(struct i2c_client *client)
+{
+	if (! keywest_ctx)
+		return 0;
+	if (client == keywest_ctx->client)
+		keywest_ctx->client = NULL;
+
+	i2c_detach_client(client);
+	kfree(client);
+	return 0;
+}
+
+/* exported */
+void snd_pmac_keywest_cleanup(pmac_keywest_t *i2c)
+{
+	if (keywest_ctx && keywest_ctx == i2c) {
+		i2c_del_driver(&keywest_driver);
+		keywest_ctx = NULL;
+	}
+}
+
+int __init snd_pmac_tumbler_post_init(void)
+{
+	int err;
+	
+	if ((err = keywest_ctx->init_client(keywest_ctx)) < 0) {
+		snd_printk(KERN_ERR "tumbler: %i :cannot initialize the MCS\n", err);
+		return err;
+	}
+	return 0;
+}
+
+/* exported */
+int __init snd_pmac_keywest_init(pmac_keywest_t *i2c)
+{
+	int err;
+
+	if (keywest_ctx)
+		return -EBUSY;
+
+	keywest_ctx = i2c;
+
+	if ((err = i2c_add_driver(&keywest_driver))) {
+		snd_printk(KERN_ERR "cannot register keywest i2c driver\n");
+		return err;
+	}
+	return 0;
+}
diff --git a/sound/ppc/pmac.c b/sound/ppc/pmac.c
new file mode 100644
index 000000000000..6c4ed90f490e
--- /dev/null
+++ b/sound/ppc/pmac.c
@@ -0,0 +1,1328 @@
+/*
+ * PMac DBDMA lowlevel functions
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ * code based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include "pmac.h"
+#include <sound/pcm_params.h>
+#ifdef CONFIG_PPC_HAS_FEATURE_CALLS
+#include <asm/pmac_feature.h>
+#else
+#include <asm/feature.h>
+#endif
+
+
+#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK)
+static int snd_pmac_register_sleep_notifier(pmac_t *chip);
+static int snd_pmac_unregister_sleep_notifier(pmac_t *chip);
+static int snd_pmac_suspend(snd_card_t *card, pm_message_t state);
+static int snd_pmac_resume(snd_card_t *card);
+#endif
+
+
+/* fixed frequency table for awacs, screamer, burgundy, DACA (44100 max) */
+static int awacs_freqs[8] = {
+	44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350
+};
+/* fixed frequency table for tumbler */
+static int tumbler_freqs[1] = {
+	44100
+};
+
+/*
+ * allocate DBDMA command arrays
+ */
+static int snd_pmac_dbdma_alloc(pmac_dbdma_t *rec, int size)
+{
+	rec->space = kmalloc(sizeof(struct dbdma_cmd) * (size + 1), GFP_KERNEL);
+	if (rec->space == NULL)
+		return -ENOMEM;
+	rec->size = size;
+	memset(rec->space, 0, sizeof(struct dbdma_cmd) * (size + 1));
+	rec->cmds = (void __iomem *)DBDMA_ALIGN(rec->space);
+	rec->addr = virt_to_bus(rec->cmds);
+	return 0;
+}
+
+static void snd_pmac_dbdma_free(pmac_dbdma_t *rec)
+{
+	if (rec)
+		kfree(rec->space);
+}
+
+
+/*
+ * pcm stuff
+ */
+
+/*
+ * look up frequency table
+ */
+
+unsigned int snd_pmac_rate_index(pmac_t *chip, pmac_stream_t *rec, unsigned int rate)
+{
+	int i, ok, found;
+
+	ok = rec->cur_freqs;
+	if (rate > chip->freq_table[0])
+		return 0;
+	found = 0;
+	for (i = 0; i < chip->num_freqs; i++, ok >>= 1) {
+		if (! (ok & 1)) continue;
+		found = i;
+		if (rate >= chip->freq_table[i])
+			break;
+	}
+	return found;
+}
+
+/*
+ * check whether another stream is active
+ */
+static inline int another_stream(int stream)
+{
+	return (stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+}
+
+/*
+ * allocate buffers
+ */
+static int snd_pmac_pcm_hw_params(snd_pcm_substream_t *subs,
+				  snd_pcm_hw_params_t *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw_params));
+}
+
+/*
+ * release buffers
+ */
+static int snd_pmac_pcm_hw_free(snd_pcm_substream_t *subs)
+{
+	snd_pcm_lib_free_pages(subs);
+	return 0;
+}
+
+/*
+ * get a stream of the opposite direction
+ */
+static pmac_stream_t *snd_pmac_get_stream(pmac_t *chip, int stream)
+{
+	switch (stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		return &chip->playback;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		return &chip->capture;
+	default:
+		snd_BUG();
+		return NULL;
+	}
+}
+
+/*
+ * wait while run status is on
+ */
+inline static void
+snd_pmac_wait_ack(pmac_stream_t *rec)
+{
+	int timeout = 50000;
+	while ((in_le32(&rec->dma->status) & RUN) && timeout-- > 0)
+		udelay(1);
+}
+
+/*
+ * set the format and rate to the chip.
+ * call the lowlevel function if defined (e.g. for AWACS).
+ */
+static void snd_pmac_pcm_set_format(pmac_t *chip)
+{
+	/* set up frequency and format */
+	out_le32(&chip->awacs->control, chip->control_mask | (chip->rate_index << 8));
+	out_le32(&chip->awacs->byteswap, chip->format == SNDRV_PCM_FORMAT_S16_LE ? 1 : 0);
+	if (chip->set_format)
+		chip->set_format(chip);
+}
+
+/*
+ * stop the DMA transfer
+ */
+inline static void snd_pmac_dma_stop(pmac_stream_t *rec)
+{
+	out_le32(&rec->dma->control, (RUN|WAKE|FLUSH|PAUSE) << 16);
+	snd_pmac_wait_ack(rec);
+}
+
+/*
+ * set the command pointer address
+ */
+inline static void snd_pmac_dma_set_command(pmac_stream_t *rec, pmac_dbdma_t *cmd)
+{
+	out_le32(&rec->dma->cmdptr, cmd->addr);
+}
+
+/*
+ * start the DMA
+ */
+inline static void snd_pmac_dma_run(pmac_stream_t *rec, int status)
+{
+	out_le32(&rec->dma->control, status | (status << 16));
+}
+
+
+/*
+ * prepare playback/capture stream
+ */
+static int snd_pmac_pcm_prepare(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs)
+{
+	int i;
+	volatile struct dbdma_cmd __iomem *cp;
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	int rate_index;
+	long offset;
+	pmac_stream_t *astr;
+	
+	rec->dma_size = snd_pcm_lib_buffer_bytes(subs);
+	rec->period_size = snd_pcm_lib_period_bytes(subs);
+	rec->nperiods = rec->dma_size / rec->period_size;
+	rec->cur_period = 0;
+	rate_index = snd_pmac_rate_index(chip, rec, runtime->rate);
+
+	/* set up constraints */
+	astr = snd_pmac_get_stream(chip, another_stream(rec->stream));
+	snd_runtime_check(astr, return -EINVAL);
+	astr->cur_freqs = 1 << rate_index;
+	astr->cur_formats = 1 << runtime->format;
+	chip->rate_index = rate_index;
+	chip->format = runtime->format;
+
+	/* We really want to execute a DMA stop command, after the AWACS
+	 * is initialized.
+	 * For reasons I don't understand, it stops the hissing noise
+	 * common to many PowerBook G3 systems and random noise otherwise
+	 * captured on iBook2's about every third time. -ReneR
+	 */
+	spin_lock_irq(&chip->reg_lock);
+	snd_pmac_dma_stop(rec);
+	st_le16(&chip->extra_dma.cmds->command, DBDMA_STOP);
+	snd_pmac_dma_set_command(rec, &chip->extra_dma);
+	snd_pmac_dma_run(rec, RUN);
+	spin_unlock_irq(&chip->reg_lock);
+	mdelay(5);
+	spin_lock_irq(&chip->reg_lock);
+	/* continuous DMA memory type doesn't provide the physical address,
+	 * so we need to resolve the address here...
+	 */
+	offset = virt_to_bus(runtime->dma_area);
+	for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) {
+		st_le32(&cp->phy_addr, offset);
+		st_le16(&cp->req_count, rec->period_size);
+		/*st_le16(&cp->res_count, 0);*/
+		st_le16(&cp->xfer_status, 0);
+		offset += rec->period_size;
+	}
+	/* make loop */
+	st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS);
+	st_le32(&cp->cmd_dep, rec->cmd.addr);
+
+	snd_pmac_dma_stop(rec);
+	snd_pmac_dma_set_command(rec, &rec->cmd);
+	spin_unlock_irq(&chip->reg_lock);
+
+	return 0;
+}
+
+
+/*
+ * PCM trigger/stop
+ */
+static int snd_pmac_pcm_trigger(pmac_t *chip, pmac_stream_t *rec,
+				snd_pcm_substream_t *subs, int cmd)
+{
+	volatile struct dbdma_cmd __iomem *cp;
+	int i, command;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (rec->running)
+			return -EBUSY;
+		command = (subs->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			   OUTPUT_MORE : INPUT_MORE) + INTR_ALWAYS;
+		spin_lock(&chip->reg_lock);
+		snd_pmac_beep_stop(chip);
+		snd_pmac_pcm_set_format(chip);
+		for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++)
+			out_le16(&cp->command, command);
+		snd_pmac_dma_set_command(rec, &rec->cmd);
+		(void)in_le32(&rec->dma->status);
+		snd_pmac_dma_run(rec, RUN|WAKE);
+		rec->running = 1;
+		spin_unlock(&chip->reg_lock);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		spin_lock(&chip->reg_lock);
+		rec->running = 0;
+		/*printk("stopped!!\n");*/
+		snd_pmac_dma_stop(rec);
+		for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++)
+			out_le16(&cp->command, DBDMA_STOP);
+		spin_unlock(&chip->reg_lock);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * return the current pointer
+ */
+inline
+static snd_pcm_uframes_t snd_pmac_pcm_pointer(pmac_t *chip, pmac_stream_t *rec,
+					      snd_pcm_substream_t *subs)
+{
+	int count = 0;
+
+#if 1 /* hmm.. how can we get the current dma pointer?? */
+	int stat;
+	volatile struct dbdma_cmd __iomem *cp = &rec->cmd.cmds[rec->cur_period];
+	stat = ld_le16(&cp->xfer_status);
+	if (stat & (ACTIVE|DEAD)) {
+		count = in_le16(&cp->res_count);
+		if (count)
+			count = rec->period_size - count;
+	}
+#endif
+	count += rec->cur_period * rec->period_size;
+	/*printk("pointer=%d\n", count);*/
+	return bytes_to_frames(subs->runtime, count);
+}
+
+/*
+ * playback
+ */
+
+static int snd_pmac_playback_prepare(snd_pcm_substream_t *subs)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_prepare(chip, &chip->playback, subs);
+}
+
+static int snd_pmac_playback_trigger(snd_pcm_substream_t *subs,
+				     int cmd)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_trigger(chip, &chip->playback, subs, cmd);
+}
+
+static snd_pcm_uframes_t snd_pmac_playback_pointer(snd_pcm_substream_t *subs)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_pointer(chip, &chip->playback, subs);
+}
+
+
+/*
+ * capture
+ */
+
+static int snd_pmac_capture_prepare(snd_pcm_substream_t *subs)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_prepare(chip, &chip->capture, subs);
+}
+
+static int snd_pmac_capture_trigger(snd_pcm_substream_t *subs,
+				    int cmd)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_trigger(chip, &chip->capture, subs, cmd);
+}
+
+static snd_pcm_uframes_t snd_pmac_capture_pointer(snd_pcm_substream_t *subs)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_pointer(chip, &chip->capture, subs);
+}
+
+
+/*
+ * update playback/capture pointer from interrupts
+ */
+static void snd_pmac_pcm_update(pmac_t *chip, pmac_stream_t *rec)
+{
+	volatile struct dbdma_cmd __iomem *cp;
+	int c;
+	int stat;
+
+	spin_lock(&chip->reg_lock);
+	if (rec->running) {
+		cp = &rec->cmd.cmds[rec->cur_period];
+		for (c = 0; c < rec->nperiods; c++) { /* at most all fragments */
+			stat = ld_le16(&cp->xfer_status);
+			if (! (stat & ACTIVE))
+				break;
+			/*printk("update frag %d\n", rec->cur_period);*/
+			st_le16(&cp->xfer_status, 0);
+			st_le16(&cp->req_count, rec->period_size);
+			/*st_le16(&cp->res_count, 0);*/
+			rec->cur_period++;
+			if (rec->cur_period >= rec->nperiods) {
+				rec->cur_period = 0;
+				cp = rec->cmd.cmds;
+			} else
+				cp++;
+			spin_unlock(&chip->reg_lock);
+			snd_pcm_period_elapsed(rec->substream);
+			spin_lock(&chip->reg_lock);
+		}
+	}
+	spin_unlock(&chip->reg_lock);
+}
+
+
+/*
+ * hw info
+ */
+
+static snd_pcm_hardware_t snd_pmac_playback =
+{
+	.info =			(SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		7350,
+	.rate_max =		44100,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	131072,
+	.period_bytes_min =	256,
+	.period_bytes_max =	16384,
+	.periods_min =		3,
+	.periods_max =		PMAC_MAX_FRAGS,
+};
+
+static snd_pcm_hardware_t snd_pmac_capture =
+{
+	.info =			(SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		7350,
+	.rate_max =		44100,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	131072,
+	.period_bytes_min =	256,
+	.period_bytes_max =	16384,
+	.periods_min =		3,
+	.periods_max =		PMAC_MAX_FRAGS,
+};
+
+
+#if 0 // NYI
+static int snd_pmac_hw_rule_rate(snd_pcm_hw_params_t *params,
+				 snd_pcm_hw_rule_t *rule)
+{
+	pmac_t *chip = rule->private;
+	pmac_stream_t *rec = snd_pmac_get_stream(chip, rule->deps[0]);
+	int i, freq_table[8], num_freqs;
+
+	snd_runtime_check(rec, return -EINVAL);
+	num_freqs = 0;
+	for (i = chip->num_freqs - 1; i >= 0; i--) {
+		if (rec->cur_freqs & (1 << i))
+			freq_table[num_freqs++] = chip->freq_table[i];
+	}
+
+	return snd_interval_list(hw_param_interval(params, rule->var),
+				 num_freqs, freq_table, 0);
+}
+
+static int snd_pmac_hw_rule_format(snd_pcm_hw_params_t *params,
+				   snd_pcm_hw_rule_t *rule)
+{
+	pmac_t *chip = rule->private;
+	pmac_stream_t *rec = snd_pmac_get_stream(chip, rule->deps[0]);
+
+	snd_runtime_check(rec, return -EINVAL);
+	return snd_mask_refine_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT),
+				   rec->cur_formats);
+}
+#endif // NYI
+
+static int snd_pmac_pcm_open(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs)
+{
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	int i, j, fflags;
+	static int typical_freqs[] = {
+		44100,
+		22050,
+		11025,
+		0,
+	};
+	static int typical_freq_flags[] = {
+		SNDRV_PCM_RATE_44100,
+		SNDRV_PCM_RATE_22050,
+		SNDRV_PCM_RATE_11025,
+		0,
+	};
+
+	/* look up frequency table and fill bit mask */
+	runtime->hw.rates = 0;
+	fflags = chip->freqs_ok;
+	for (i = 0; typical_freqs[i]; i++) {
+		for (j = 0; j < chip->num_freqs; j++) {
+			if ((chip->freqs_ok & (1 << j)) &&
+			    chip->freq_table[j] == typical_freqs[i]) {
+				runtime->hw.rates |= typical_freq_flags[i];
+				fflags &= ~(1 << j);
+				break;
+			}
+		}
+	}
+	if (fflags) /* rest */
+		runtime->hw.rates |= SNDRV_PCM_RATE_KNOT;
+
+	/* check for minimum and maximum rates */
+	for (i = 0; i < chip->num_freqs; i++) {
+		if (chip->freqs_ok & (1 << i)) {
+			runtime->hw.rate_max = chip->freq_table[i];
+			break;
+		}
+	}
+	for (i = chip->num_freqs - 1; i >= 0; i--) {
+		if (chip->freqs_ok & (1 << i)) {
+			runtime->hw.rate_min = chip->freq_table[i];
+			break;
+		}
+	}
+	runtime->hw.formats = chip->formats_ok;
+	if (chip->can_capture) {
+		if (! chip->can_duplex)
+			runtime->hw.info |= SNDRV_PCM_INFO_HALF_DUPLEX;
+		runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+	}
+	runtime->private_data = rec;
+	rec->substream = subs;
+
+#if 0 /* FIXME: still under development.. */
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			    snd_pmac_hw_rule_rate, chip, rec->stream, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+			    snd_pmac_hw_rule_format, chip, rec->stream, -1);
+#endif
+
+	runtime->hw.periods_max = rec->cmd.size - 1;
+
+	if (chip->can_duplex)
+		snd_pcm_set_sync(subs);
+
+	/* constraints to fix choppy sound */
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	return 0;
+}
+
+static int snd_pmac_pcm_close(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs)
+{
+	pmac_stream_t *astr;
+
+	snd_pmac_dma_stop(rec);
+
+	astr = snd_pmac_get_stream(chip, another_stream(rec->stream));
+	snd_runtime_check(astr, return -EINVAL);
+
+	/* reset constraints */
+	astr->cur_freqs = chip->freqs_ok;
+	astr->cur_formats = chip->formats_ok;
+	
+	return 0;
+}
+
+static int snd_pmac_playback_open(snd_pcm_substream_t *subs)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+
+	subs->runtime->hw = snd_pmac_playback;
+	return snd_pmac_pcm_open(chip, &chip->playback, subs);
+}
+
+static int snd_pmac_capture_open(snd_pcm_substream_t *subs)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+
+	subs->runtime->hw = snd_pmac_capture;
+	return snd_pmac_pcm_open(chip, &chip->capture, subs);
+}
+
+static int snd_pmac_playback_close(snd_pcm_substream_t *subs)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+
+	return snd_pmac_pcm_close(chip, &chip->playback, subs);
+}
+
+static int snd_pmac_capture_close(snd_pcm_substream_t *subs)
+{
+	pmac_t *chip = snd_pcm_substream_chip(subs);
+
+	return snd_pmac_pcm_close(chip, &chip->capture, subs);
+}
+
+/*
+ */
+
+static snd_pcm_ops_t snd_pmac_playback_ops = {
+	.open =		snd_pmac_playback_open,
+	.close =	snd_pmac_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_pmac_pcm_hw_params,
+	.hw_free =	snd_pmac_pcm_hw_free,
+	.prepare =	snd_pmac_playback_prepare,
+	.trigger =	snd_pmac_playback_trigger,
+	.pointer =	snd_pmac_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_pmac_capture_ops = {
+	.open =		snd_pmac_capture_open,
+	.close =	snd_pmac_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_pmac_pcm_hw_params,
+	.hw_free =	snd_pmac_pcm_hw_free,
+	.prepare =	snd_pmac_capture_prepare,
+	.trigger =	snd_pmac_capture_trigger,
+	.pointer =	snd_pmac_capture_pointer,
+};
+
+static void pmac_pcm_free(snd_pcm_t *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int __init snd_pmac_pcm_new(pmac_t *chip)
+{
+	snd_pcm_t *pcm;
+	int err;
+	int num_captures = 1;
+
+	if (! chip->can_capture)
+		num_captures = 0;
+	err = snd_pcm_new(chip->card, chip->card->driver, 0, 1, num_captures, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pmac_playback_ops);
+	if (chip->can_capture)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pmac_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->private_free = pmac_pcm_free;
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcm = pcm;
+
+	chip->formats_ok = SNDRV_PCM_FMTBIT_S16_BE;
+	if (chip->can_byte_swap)
+		chip->formats_ok |= SNDRV_PCM_FMTBIT_S16_LE;
+
+	chip->playback.cur_formats = chip->formats_ok;
+	chip->capture.cur_formats = chip->formats_ok;
+	chip->playback.cur_freqs = chip->freqs_ok;
+	chip->capture.cur_freqs = chip->freqs_ok;
+
+	/* preallocate 64k buffer */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, 
+					      snd_dma_continuous_data(GFP_KERNEL),
+					      64 * 1024, 64 * 1024);
+
+	return 0;
+}
+
+
+static void snd_pmac_dbdma_reset(pmac_t *chip)
+{
+	out_le32(&chip->playback.dma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16);
+	snd_pmac_wait_ack(&chip->playback);
+	out_le32(&chip->capture.dma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16);
+	snd_pmac_wait_ack(&chip->capture);
+}
+
+
+/*
+ * handling beep
+ */
+void snd_pmac_beep_dma_start(pmac_t *chip, int bytes, unsigned long addr, int speed)
+{
+	pmac_stream_t *rec = &chip->playback;
+
+	snd_pmac_dma_stop(rec);
+	st_le16(&chip->extra_dma.cmds->req_count, bytes);
+	st_le16(&chip->extra_dma.cmds->xfer_status, 0);
+	st_le32(&chip->extra_dma.cmds->cmd_dep, chip->extra_dma.addr);
+	st_le32(&chip->extra_dma.cmds->phy_addr, addr);
+	st_le16(&chip->extra_dma.cmds->command, OUTPUT_MORE + BR_ALWAYS);
+	out_le32(&chip->awacs->control,
+		 (in_le32(&chip->awacs->control) & ~0x1f00)
+		 | (speed << 8));
+	out_le32(&chip->awacs->byteswap, 0);
+	snd_pmac_dma_set_command(rec, &chip->extra_dma);
+	snd_pmac_dma_run(rec, RUN);
+}
+
+void snd_pmac_beep_dma_stop(pmac_t *chip)
+{
+	snd_pmac_dma_stop(&chip->playback);
+	st_le16(&chip->extra_dma.cmds->command, DBDMA_STOP);
+	snd_pmac_pcm_set_format(chip); /* reset format */
+}
+
+
+/*
+ * interrupt handlers
+ */
+static irqreturn_t
+snd_pmac_tx_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	pmac_t *chip = devid;
+	snd_pmac_pcm_update(chip, &chip->playback);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t
+snd_pmac_rx_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	pmac_t *chip = devid;
+	snd_pmac_pcm_update(chip, &chip->capture);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t
+snd_pmac_ctrl_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	pmac_t *chip = devid;
+	int ctrl = in_le32(&chip->awacs->control);
+
+	/*printk("pmac: control interrupt.. 0x%x\n", ctrl);*/
+	if (ctrl & MASK_PORTCHG) {
+		/* do something when headphone is plugged/unplugged? */
+		if (chip->update_automute)
+			chip->update_automute(chip, 1);
+	}
+	if (ctrl & MASK_CNTLERR) {
+		int err = (in_le32(&chip->awacs->codec_stat) & MASK_ERRCODE) >> 16;
+		if (err && chip->model <= PMAC_SCREAMER)
+			snd_printk(KERN_DEBUG "error %x\n", err);
+	}
+	/* Writing 1s to the CNTLERR and PORTCHG bits clears them... */
+	out_le32(&chip->awacs->control, ctrl);
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * a wrapper to feature call for compatibility
+ */
+#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK)
+static void snd_pmac_sound_feature(pmac_t *chip, int enable)
+{
+#ifdef CONFIG_PPC_HAS_FEATURE_CALLS
+	ppc_md.feature_call(PMAC_FTR_SOUND_CHIP_ENABLE, chip->node, 0, enable);
+#else
+	if (chip->is_pbook_G3) {
+		pmu_suspend();
+		feature_clear(chip->node, FEATURE_Sound_power);
+		feature_clear(chip->node, FEATURE_Sound_CLK_enable);
+		big_mdelay(1000); /* XXX */
+		pmu_resume();
+	}
+	if (chip->is_pbook_3400) {
+		feature_set(chip->node, FEATURE_IOBUS_enable);
+		udelay(10);
+	}
+#endif
+}
+#else /* CONFIG_PM && CONFIG_PMAC_PBOOK */
+#define snd_pmac_sound_feature(chip,enable) /**/
+#endif /* CONFIG_PM && CONFIG_PMAC_PBOOK */
+
+/*
+ * release resources
+ */
+
+static int snd_pmac_free(pmac_t *chip)
+{
+	int i;
+
+	/* stop sounds */
+	if (chip->initialized) {
+		snd_pmac_dbdma_reset(chip);
+		/* disable interrupts from awacs interface */
+		out_le32(&chip->awacs->control, in_le32(&chip->awacs->control) & 0xfff);
+	}
+
+	snd_pmac_sound_feature(chip, 0);
+#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK)
+	snd_pmac_unregister_sleep_notifier(chip);
+#endif
+
+	/* clean up mixer if any */
+	if (chip->mixer_free)
+		chip->mixer_free(chip);
+
+	snd_pmac_detach_beep(chip);
+
+	/* release resources */
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void*)chip);
+	if (chip->tx_irq >= 0)
+		free_irq(chip->tx_irq, (void*)chip);
+	if (chip->rx_irq >= 0)
+		free_irq(chip->rx_irq, (void*)chip);
+	snd_pmac_dbdma_free(&chip->playback.cmd);
+	snd_pmac_dbdma_free(&chip->capture.cmd);
+	snd_pmac_dbdma_free(&chip->extra_dma);
+	if (chip->macio_base)
+		iounmap(chip->macio_base);
+	if (chip->latch_base)
+		iounmap(chip->latch_base);
+	if (chip->awacs)
+		iounmap(chip->awacs);
+	if (chip->playback.dma)
+		iounmap(chip->playback.dma);
+	if (chip->capture.dma)
+		iounmap(chip->capture.dma);
+	if (chip->node) {
+		for (i = 0; i < 3; i++) {
+			if (chip->of_requested & (1 << i))
+				release_OF_resource(chip->node, i);
+		}
+	}
+	kfree(chip);
+	return 0;
+}
+
+
+/*
+ * free the device
+ */
+static int snd_pmac_dev_free(snd_device_t *device)
+{
+	pmac_t *chip = device->device_data;
+	return snd_pmac_free(chip);
+}
+
+
+/*
+ * check the machine support byteswap (little-endian)
+ */
+
+static void __init detect_byte_swap(pmac_t *chip)
+{
+	struct device_node *mio;
+
+	/* if seems that Keylargo can't byte-swap  */
+	for (mio = chip->node->parent; mio; mio = mio->parent) {
+		if (strcmp(mio->name, "mac-io") == 0) {
+			if (device_is_compatible(mio, "Keylargo"))
+				chip->can_byte_swap = 0;
+			break;
+		}
+	}
+
+	/* it seems the Pismo & iBook can't byte-swap in hardware. */
+	if (machine_is_compatible("PowerBook3,1") ||
+	    machine_is_compatible("PowerBook2,1"))
+		chip->can_byte_swap = 0 ;
+
+	if (machine_is_compatible("PowerBook2,1"))
+		chip->can_duplex = 0;
+}
+
+
+/*
+ * detect a sound chip
+ */
+static int __init snd_pmac_detect(pmac_t *chip)
+{
+	struct device_node *sound;
+	unsigned int *prop, l;
+
+	if (_machine != _MACH_Pmac)
+		return -ENODEV;
+
+	chip->subframe = 0;
+	chip->revision = 0;
+	chip->freqs_ok = 0xff; /* all ok */
+	chip->model = PMAC_AWACS;
+	chip->can_byte_swap = 1;
+	chip->can_duplex = 1;
+	chip->can_capture = 1;
+	chip->num_freqs = ARRAY_SIZE(awacs_freqs);
+	chip->freq_table = awacs_freqs;
+
+	chip->control_mask = MASK_IEPC | MASK_IEE | 0x11; /* default */
+
+	/* check machine type */
+	if (machine_is_compatible("AAPL,3400/2400")
+	    || machine_is_compatible("AAPL,3500"))
+		chip->is_pbook_3400 = 1;
+	else if (machine_is_compatible("PowerBook1,1")
+		 || machine_is_compatible("AAPL,PowerBook1998"))
+		chip->is_pbook_G3 = 1;
+	chip->node = find_devices("awacs");
+	if (chip->node)
+		return 0; /* ok */
+
+	/*
+	 * powermac G3 models have a node called "davbus"
+	 * with a child called "sound".
+	 */
+	chip->node = find_devices("davbus");
+	/*
+	 * if we didn't find a davbus device, try 'i2s-a' since
+	 * this seems to be what iBooks have
+	 */
+	if (! chip->node)
+		chip->node = find_devices("i2s-a");
+	if (! chip->node)
+		return -ENODEV;
+	sound = find_devices("sound");
+	while (sound && sound->parent != chip->node)
+		sound = sound->next;
+	if (! sound)
+		return -ENODEV;
+	prop = (unsigned int *) get_property(sound, "sub-frame", NULL);
+	if (prop && *prop < 16)
+		chip->subframe = *prop;
+	/* This should be verified on older screamers */
+	if (device_is_compatible(sound, "screamer")) {
+		chip->model = PMAC_SCREAMER;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+	}
+	if (device_is_compatible(sound, "burgundy")) {
+		chip->model = PMAC_BURGUNDY;
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	if (device_is_compatible(sound, "daca")) {
+		chip->model = PMAC_DACA;
+		chip->can_capture = 0;  /* no capture */
+		chip->can_duplex = 0;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	if (device_is_compatible(sound, "tumbler")) {
+		chip->model = PMAC_TUMBLER;
+		chip->can_capture = 0;  /* no capture */
+		chip->can_duplex = 0;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+		chip->num_freqs = ARRAY_SIZE(tumbler_freqs);
+		chip->freq_table = tumbler_freqs;
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	if (device_is_compatible(sound, "snapper")) {
+		chip->model = PMAC_SNAPPER;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+		chip->num_freqs = ARRAY_SIZE(tumbler_freqs);
+		chip->freq_table = tumbler_freqs;
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	if (device_is_compatible(sound, "AOAKeylargo")) {
+		/* Seems to support the stock AWACS frequencies, but has
+		   a snapper mixer */
+		chip->model = PMAC_SNAPPER;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	prop = (unsigned int *)get_property(sound, "device-id", NULL);
+	if (prop)
+		chip->device_id = *prop;
+	chip->has_iic = (find_devices("perch") != NULL);
+
+	detect_byte_swap(chip);
+
+	/* look for a property saying what sample rates
+	   are available */
+	prop = (unsigned int *) get_property(sound, "sample-rates", &l);
+	if (! prop)
+		prop = (unsigned int *) get_property(sound, "output-frame-rates", &l);
+	if (prop) {
+		int i;
+		chip->freqs_ok = 0;
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			for (i = 0; i < chip->num_freqs; ++i) {
+				if (r == chip->freq_table[i]) {
+					chip->freqs_ok |= (1 << i);
+					break;
+				}
+			}
+		}
+	} else {
+		/* assume only 44.1khz */
+		chip->freqs_ok = 1;
+	}
+
+	return 0;
+}
+
+/*
+ * exported - boolean info callbacks for ease of programming
+ */
+int snd_pmac_boolean_stereo_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+int snd_pmac_boolean_mono_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+/*
+ * auto-mute
+ */
+static int pmac_auto_mute_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = chip->auto_mute;
+	return 0;
+}
+
+static int pmac_auto_mute_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	if (ucontrol->value.integer.value[0] != chip->auto_mute) {
+		chip->auto_mute = ucontrol->value.integer.value[0];
+		if (chip->update_automute)
+			chip->update_automute(chip, 1);
+		return 1;
+	}
+	return 0;
+}
+
+static int pmac_hp_detect_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	if (chip->detect_headphone)
+		ucontrol->value.integer.value[0] = chip->detect_headphone(chip);
+	else
+		ucontrol->value.integer.value[0] = 0;
+	return 0;
+}
+
+static snd_kcontrol_new_t auto_mute_controls[] __initdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Auto Mute Switch",
+	  .info = snd_pmac_boolean_mono_info,
+	  .get = pmac_auto_mute_get,
+	  .put = pmac_auto_mute_put,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Headphone Detection",
+	  .access = SNDRV_CTL_ELEM_ACCESS_READ,
+	  .info = snd_pmac_boolean_mono_info,
+	  .get = pmac_hp_detect_get,
+	},
+};
+
+int __init snd_pmac_add_automute(pmac_t *chip)
+{
+	int err;
+	chip->auto_mute = 1;
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&auto_mute_controls[0], chip));
+	if (err < 0)
+		return err;
+	chip->hp_detect_ctl = snd_ctl_new1(&auto_mute_controls[1], chip);
+	return snd_ctl_add(chip->card, chip->hp_detect_ctl);
+}
+#endif /* PMAC_SUPPORT_AUTOMUTE */
+
+/*
+ * create and detect a pmac chip record
+ */
+int __init snd_pmac_new(snd_card_t *card, pmac_t **chip_return)
+{
+	pmac_t *chip;
+	struct device_node *np;
+	int i, err;
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_pmac_dev_free,
+	};
+
+	snd_runtime_check(chip_return, return -EINVAL);
+	*chip_return = NULL;
+
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->card = card;
+
+	spin_lock_init(&chip->reg_lock);
+	chip->irq = chip->tx_irq = chip->rx_irq = -1;
+
+	chip->playback.stream = SNDRV_PCM_STREAM_PLAYBACK;
+	chip->capture.stream = SNDRV_PCM_STREAM_CAPTURE;
+
+	if ((err = snd_pmac_detect(chip)) < 0)
+		goto __error;
+
+	if (snd_pmac_dbdma_alloc(&chip->playback.cmd, PMAC_MAX_FRAGS + 1) < 0 ||
+	    snd_pmac_dbdma_alloc(&chip->capture.cmd, PMAC_MAX_FRAGS + 1) < 0 ||
+	    snd_pmac_dbdma_alloc(&chip->extra_dma, 2) < 0) {
+		err = -ENOMEM;
+		goto __error;
+	}
+
+	np = chip->node;
+	if (np->n_addrs < 3 || np->n_intrs < 3) {
+		err = -ENODEV;
+		goto __error;
+	}
+
+	for (i = 0; i < 3; i++) {
+		static char *name[3] = { NULL, "- Tx DMA", "- Rx DMA" };
+		if (! request_OF_resource(np, i, name[i])) {
+			snd_printk(KERN_ERR "pmac: can't request resource %d!\n", i);
+			err = -ENODEV;
+			goto __error;
+		}
+		chip->of_requested |= (1 << i);
+	}
+
+	chip->awacs = ioremap(np->addrs[0].address, 0x1000);
+	chip->playback.dma = ioremap(np->addrs[1].address, 0x100);
+	chip->capture.dma = ioremap(np->addrs[2].address, 0x100);
+	if (chip->model <= PMAC_BURGUNDY) {
+		if (request_irq(np->intrs[0].line, snd_pmac_ctrl_intr, 0,
+				"PMac", (void*)chip)) {
+			snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", np->intrs[0].line);
+			err = -EBUSY;
+			goto __error;
+		}
+		chip->irq = np->intrs[0].line;
+	}
+	if (request_irq(np->intrs[1].line, snd_pmac_tx_intr, 0,
+			"PMac Output", (void*)chip)) {
+		snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", np->intrs[1].line);
+		err = -EBUSY;
+		goto __error;
+	}
+	chip->tx_irq = np->intrs[1].line;
+	if (request_irq(np->intrs[2].line, snd_pmac_rx_intr, 0,
+			"PMac Input", (void*)chip)) {
+		snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", np->intrs[2].line);
+		err = -EBUSY;
+		goto __error;
+	}
+	chip->rx_irq = np->intrs[2].line;
+
+	snd_pmac_sound_feature(chip, 1);
+
+	/* reset */
+	out_le32(&chip->awacs->control, 0x11);
+
+	/* Powerbooks have odd ways of enabling inputs such as
+	   an expansion-bay CD or sound from an internal modem
+	   or a PC-card modem. */
+	if (chip->is_pbook_3400) {
+		/* Enable CD and PC-card sound inputs. */
+		/* This is done by reading from address
+		 * f301a000, + 0x10 to enable the expansion-bay
+		 * CD sound input, + 0x80 to enable the PC-card
+		 * sound input.  The 0x100 enables the SCSI bus
+		 * terminator power.
+		 */
+		chip->latch_base = ioremap (0xf301a000, 0x1000);
+		in_8(chip->latch_base + 0x190);
+	} else if (chip->is_pbook_G3) {
+		struct device_node* mio;
+		for (mio = chip->node->parent; mio; mio = mio->parent) {
+			if (strcmp(mio->name, "mac-io") == 0
+			    && mio->n_addrs > 0) {
+				chip->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.
+		 */
+		if (chip->macio_base)
+			out_8(chip->macio_base + 0x37, 3);
+	}
+
+	/* Reset dbdma channels */
+	snd_pmac_dbdma_reset(chip);
+
+#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK)
+	/* add sleep notifier */
+	if (! snd_pmac_register_sleep_notifier(chip))
+		snd_card_set_pm_callback(chip->card, snd_pmac_suspend, snd_pmac_resume, chip);
+#endif
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0)
+		goto __error;
+
+	*chip_return = chip;
+	return 0;
+
+ __error:
+	snd_pmac_free(chip);
+	return err;
+}
+
+
+/*
+ * sleep notify for powerbook
+ */
+
+#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK)
+
+/*
+ * Save state when going to sleep, restore it afterwards.
+ */
+
+static int snd_pmac_suspend(snd_card_t *card, pm_message_t state)
+{
+	pmac_t *chip = card->pm_private_data;
+	unsigned long flags;
+
+	if (chip->suspend)
+		chip->suspend(chip);
+	snd_pcm_suspend_all(chip->pcm);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_pmac_beep_stop(chip);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (chip->irq >= 0)
+		disable_irq(chip->irq);
+	if (chip->tx_irq >= 0)
+		disable_irq(chip->tx_irq);
+	if (chip->rx_irq >= 0)
+		disable_irq(chip->rx_irq);
+	snd_pmac_sound_feature(chip, 0);
+	return 0;
+}
+
+static int snd_pmac_resume(snd_card_t *card)
+{
+	pmac_t *chip = card->pm_private_data;
+
+	snd_pmac_sound_feature(chip, 1);
+	if (chip->resume)
+		chip->resume(chip);
+	/* enable CD sound input */
+	if (chip->macio_base && chip->is_pbook_G3) {
+		out_8(chip->macio_base + 0x37, 3);
+	} else if (chip->is_pbook_3400) {
+		in_8(chip->latch_base + 0x190);
+	}
+
+	snd_pmac_pcm_set_format(chip);
+
+	if (chip->irq >= 0)
+		enable_irq(chip->irq);
+	if (chip->tx_irq >= 0)
+		enable_irq(chip->tx_irq);
+	if (chip->rx_irq >= 0)
+		enable_irq(chip->rx_irq);
+
+	return 0;
+}
+
+/* the chip is stored statically by snd_pmac_register_sleep_notifier
+ * because we can't have any private data for notify callback.
+ */
+static pmac_t *sleeping_pmac = NULL;
+
+static int snd_pmac_sleep_notify(struct pmu_sleep_notifier *self, int when)
+{
+	pmac_t *chip;
+
+	chip = sleeping_pmac;
+	snd_runtime_check(chip, return 0);
+
+	switch (when) {
+	case PBOOK_SLEEP_NOW:
+		snd_pmac_suspend(chip->card, PMSG_SUSPEND);
+		break;
+	case PBOOK_WAKE:
+		snd_pmac_resume(chip->card);
+		break;
+	}
+	return PBOOK_SLEEP_OK;
+}
+
+static struct pmu_sleep_notifier snd_pmac_sleep_notifier = {
+	snd_pmac_sleep_notify, SLEEP_LEVEL_SOUND,
+};
+
+static int __init snd_pmac_register_sleep_notifier(pmac_t *chip)
+{
+	/* should be protected here.. */
+	snd_assert(! sleeping_pmac, return -EBUSY);
+	sleeping_pmac = chip;
+	pmu_register_sleep_notifier(&snd_pmac_sleep_notifier);
+	return 0;
+}
+						    
+static int snd_pmac_unregister_sleep_notifier(pmac_t *chip)
+{
+	/* should be protected here.. */
+	snd_assert(sleeping_pmac == chip, return -ENODEV);
+	pmu_unregister_sleep_notifier(&snd_pmac_sleep_notifier);
+	sleeping_pmac = NULL;
+	return 0;
+}
+
+#endif /* CONFIG_PM && CONFIG_PMAC_PBOOK */
diff --git a/sound/ppc/pmac.h b/sound/ppc/pmac.h
new file mode 100644
index 000000000000..a699b01210ee
--- /dev/null
+++ b/sound/ppc/pmac.h
@@ -0,0 +1,214 @@
+/*
+ * Driver for PowerMac onboard soundchips
+ * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de>
+ *   based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#ifndef __PMAC_H
+#define __PMAC_H
+
+#include <linux/version.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include "awacs.h"
+
+#include <linux/adb.h>
+#ifdef CONFIG_ADB_CUDA
+#include <linux/cuda.h>
+#endif
+#ifdef CONFIG_ADB_PMU
+#include <linux/pmu.h>
+#endif
+#include <linux/nvram.h>
+#include <linux/tty.h>
+#include <linux/vt_kern.h>
+#include <asm/dbdma.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+
+/* maximum number of fragments */
+#define PMAC_MAX_FRAGS		32
+
+
+#define PMAC_SUPPORT_AUTOMUTE
+
+/*
+ * typedefs
+ */
+typedef struct snd_pmac pmac_t;
+typedef struct snd_pmac_stream pmac_stream_t;
+typedef struct snd_pmac_beep pmac_beep_t;
+typedef struct snd_pmac_dbdma pmac_dbdma_t;
+
+
+/*
+ * DBDMA space
+ */
+struct snd_pmac_dbdma {
+	unsigned long addr;
+	struct dbdma_cmd __iomem *cmds;
+	void *space;
+	int size;
+};
+
+/*
+ * playback/capture stream
+ */
+struct snd_pmac_stream {
+	int running;	/* boolean */
+
+	int stream;	/* PLAYBACK/CAPTURE */
+
+	int dma_size; /* in bytes */
+	int period_size; /* in bytes */
+	int buffer_size; /* in kbytes */
+	int nperiods, cur_period;
+
+	pmac_dbdma_t cmd;
+	volatile struct dbdma_regs __iomem *dma;
+
+	snd_pcm_substream_t *substream;
+
+	unsigned int cur_freqs;		/* currently available frequencies */
+	unsigned int cur_formats;	/* currently available formats */
+};
+
+
+/*
+ */
+
+enum snd_pmac_model {
+	PMAC_AWACS, PMAC_SCREAMER, PMAC_BURGUNDY, PMAC_DACA, PMAC_TUMBLER, PMAC_SNAPPER
+};
+
+struct snd_pmac {
+	snd_card_t *card;
+
+	/* h/w info */
+	struct device_node *node;
+	unsigned int revision;
+	unsigned int manufacturer;
+	unsigned int subframe;
+	unsigned int device_id;
+	enum snd_pmac_model model;
+
+	unsigned int has_iic : 1;
+	unsigned int is_pbook_3400 : 1;
+	unsigned int is_pbook_G3 : 1;
+
+	unsigned int can_byte_swap : 1;
+	unsigned int can_duplex : 1;
+	unsigned int can_capture : 1;
+
+	unsigned int auto_mute : 1;
+	unsigned int initialized : 1;
+	unsigned int feature_is_set : 1;
+
+	unsigned int of_requested;
+
+	int num_freqs;
+	int *freq_table;
+	unsigned int freqs_ok;		/* bit flags */
+	unsigned int formats_ok;	/* pcm hwinfo */
+	int active;
+	int rate_index;
+	int format;			/* current format */
+
+	spinlock_t reg_lock;
+	volatile struct awacs_regs __iomem *awacs;
+	int awacs_reg[8]; /* register cache */
+	unsigned int hp_stat_mask;
+
+	unsigned char __iomem *latch_base;
+	unsigned char __iomem *macio_base;
+
+	pmac_stream_t playback;
+	pmac_stream_t capture;
+
+	pmac_dbdma_t extra_dma;
+
+	int irq, tx_irq, rx_irq;
+
+	snd_pcm_t *pcm;
+
+	pmac_beep_t *beep;
+
+	unsigned int control_mask;	/* control mask */
+
+	/* mixer stuffs */
+	void *mixer_data;
+	void (*mixer_free)(pmac_t *);
+	snd_kcontrol_t *master_sw_ctl;
+	snd_kcontrol_t *speaker_sw_ctl;
+	snd_kcontrol_t *drc_sw_ctl;	/* only used for tumbler -ReneR */
+	snd_kcontrol_t *hp_detect_ctl;
+
+	/* lowlevel callbacks */
+	void (*set_format)(pmac_t *chip);
+	void (*update_automute)(pmac_t *chip, int do_notify);
+	int (*detect_headphone)(pmac_t *chip);
+#ifdef CONFIG_PMAC_PBOOK
+	void (*suspend)(pmac_t *chip);
+	void (*resume)(pmac_t *chip);
+#endif
+
+};
+
+
+/* exported functions */
+int snd_pmac_new(snd_card_t *card, pmac_t **chip_return);
+int snd_pmac_pcm_new(pmac_t *chip);
+int snd_pmac_attach_beep(pmac_t *chip);
+void snd_pmac_detach_beep(pmac_t *chip);
+void snd_pmac_beep_stop(pmac_t *chip);
+unsigned int snd_pmac_rate_index(pmac_t *chip, pmac_stream_t *rec, unsigned int rate);
+
+void snd_pmac_beep_dma_start(pmac_t *chip, int bytes, unsigned long addr, int speed);
+void snd_pmac_beep_dma_stop(pmac_t *chip);
+
+/* initialize mixer */
+int snd_pmac_awacs_init(pmac_t *chip);
+int snd_pmac_burgundy_init(pmac_t *chip);
+int snd_pmac_daca_init(pmac_t *chip);
+int snd_pmac_tumbler_init(pmac_t *chip);
+int snd_pmac_tumbler_post_init(void);
+
+/* i2c functions */
+typedef struct snd_pmac_keywest {
+	int addr;
+	struct i2c_client *client;
+	int id;
+	int (*init_client)(struct snd_pmac_keywest *i2c);
+	char *name;
+} pmac_keywest_t;
+
+int snd_pmac_keywest_init(pmac_keywest_t *i2c);
+void snd_pmac_keywest_cleanup(pmac_keywest_t *i2c);
+
+/* misc */
+int snd_pmac_boolean_stereo_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo);
+int snd_pmac_boolean_mono_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo);
+
+int snd_pmac_add_automute(pmac_t *chip);
+
+#define big_mdelay(msec) do {\
+	set_current_state(TASK_UNINTERRUPTIBLE);\
+	schedule_timeout(((msec) * HZ + 999) / 1000);\
+} while (0)
+
+#endif /* __PMAC_H */
diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c
new file mode 100644
index 000000000000..8f1953a8e290
--- /dev/null
+++ b/sound/ppc/powermac.c
@@ -0,0 +1,159 @@
+/*
+ * Driver for PowerMac AWACS
+ * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de>
+ *   based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "pmac.h"
+#include "awacs.h"
+#include "burgundy.h"
+
+#define CHIP_NAME "PMac"
+
+MODULE_DESCRIPTION("PowerMac");
+MODULE_SUPPORTED_DEVICE("{{Apple,PowerMac}}");
+MODULE_LICENSE("GPL");
+
+static int index = SNDRV_DEFAULT_IDX1;		/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;		/* ID for this card */
+static int enable_beep = 1;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for " CHIP_NAME " soundchip.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for " CHIP_NAME " soundchip.");
+module_param(enable_beep, bool, 0444);
+MODULE_PARM_DESC(enable_beep, "Enable beep using PCM.");
+
+
+/*
+ * card entry
+ */
+
+static snd_card_t *snd_pmac_card = NULL;
+
+/*
+ */
+
+static int __init snd_pmac_probe(void)
+{
+	snd_card_t *card;
+	pmac_t *chip;
+	char *name_ext;
+	int err;
+
+	card = snd_card_new(index, id, THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+
+	if ((err = snd_pmac_new(card, &chip)) < 0)
+		goto __error;
+
+	switch (chip->model) {
+	case PMAC_BURGUNDY:
+		strcpy(card->driver, "PMac Burgundy");
+		strcpy(card->shortname, "PowerMac Burgundy");
+		sprintf(card->longname, "%s (Dev %d) Sub-frame %d",
+			card->shortname, chip->device_id, chip->subframe);
+		if ((err = snd_pmac_burgundy_init(chip)) < 0)
+			goto __error;
+		break;
+	case PMAC_DACA:
+		strcpy(card->driver, "PMac DACA");
+		strcpy(card->shortname, "PowerMac DACA");
+		sprintf(card->longname, "%s (Dev %d) Sub-frame %d",
+			card->shortname, chip->device_id, chip->subframe);
+		if ((err = snd_pmac_daca_init(chip)) < 0)
+			goto __error;
+		break;
+	case PMAC_TUMBLER:
+	case PMAC_SNAPPER:
+		name_ext = chip->model == PMAC_TUMBLER ? "Tumbler" : "Snapper";
+		sprintf(card->driver, "PMac %s", name_ext);
+		sprintf(card->shortname, "PowerMac %s", name_ext);
+		sprintf(card->longname, "%s (Dev %d) Sub-frame %d",
+			card->shortname, chip->device_id, chip->subframe);
+		if ( snd_pmac_tumbler_init(chip) < 0 || snd_pmac_tumbler_post_init() < 0)
+			goto __error;
+		break;
+	case PMAC_AWACS:
+	case PMAC_SCREAMER:
+		name_ext = chip->model == PMAC_SCREAMER ? "Screamer" : "AWACS";
+		sprintf(card->driver, "PMac %s", name_ext);
+		sprintf(card->shortname, "PowerMac %s", name_ext);
+		if (chip->is_pbook_3400)
+			name_ext = " [PB3400]";
+		else if (chip->is_pbook_G3)
+			name_ext = " [PBG3]";
+		else
+			name_ext = "";
+		sprintf(card->longname, "%s%s Rev %d",
+			card->shortname, name_ext, chip->revision);
+		if ((err = snd_pmac_awacs_init(chip)) < 0)
+			goto __error;
+		break;
+	default:
+		snd_printk("unsupported hardware %d\n", chip->model);
+		err = -EINVAL;
+		goto __error;
+	}
+
+	if ((err = snd_pmac_pcm_new(chip)) < 0)
+		goto __error;
+
+	chip->initialized = 1;
+	if (enable_beep)
+		snd_pmac_attach_beep(chip);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto __error;
+
+	snd_pmac_card = card;
+	return 0;
+
+__error:
+	snd_card_free(card);
+	return err;
+}
+
+
+/*
+ * MODULE stuff
+ */
+
+static int __init alsa_card_pmac_init(void)
+{
+	int err;
+	if ((err = snd_pmac_probe()) < 0)
+		return err;
+	return 0;
+
+}
+
+static void __exit alsa_card_pmac_exit(void)
+{
+	if (snd_pmac_card)
+		snd_card_free(snd_pmac_card);
+}
+
+module_init(alsa_card_pmac_init)
+module_exit(alsa_card_pmac_exit)
diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c
new file mode 100644
index 000000000000..7d10385f0a76
--- /dev/null
+++ b/sound/ppc/tumbler.c
@@ -0,0 +1,1175 @@
+/*
+ * PMac Tumbler/Snapper lowlevel functions
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *   Rene Rebe <rene.rebe@gmx.net>:
+ *     * update from shadow registers on wakeup and headphone plug
+ *     * automatically toggle DRC on headphone plug
+ *	
+ */
+
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <linux/kmod.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#ifdef CONFIG_PPC_HAS_FEATURE_CALLS
+#include <asm/pmac_feature.h>
+#endif
+#include "pmac.h"
+#include "tumbler_volume.h"
+
+/* i2c address for tumbler */
+#define TAS_I2C_ADDR	0x34
+
+/* registers */
+#define TAS_REG_MCS	0x01	/* main control */
+#define TAS_REG_DRC	0x02
+#define TAS_REG_VOL	0x04
+#define TAS_REG_TREBLE	0x05
+#define TAS_REG_BASS	0x06
+#define TAS_REG_INPUT1	0x07
+#define TAS_REG_INPUT2	0x08
+
+/* tas3001c */
+#define TAS_REG_PCM	TAS_REG_INPUT1
+ 
+/* tas3004 */
+#define TAS_REG_LMIX	TAS_REG_INPUT1
+#define TAS_REG_RMIX	TAS_REG_INPUT2
+#define TAS_REG_MCS2	0x43		/* main control 2 */
+#define TAS_REG_ACS	0x40		/* analog control */
+
+/* mono volumes for tas3001c/tas3004 */
+enum {
+	VOL_IDX_PCM_MONO, /* tas3001c only */
+	VOL_IDX_BASS, VOL_IDX_TREBLE,
+	VOL_IDX_LAST_MONO
+};
+
+/* stereo volumes for tas3004 */
+enum {
+	VOL_IDX_PCM, VOL_IDX_PCM2, VOL_IDX_ADC,
+	VOL_IDX_LAST_MIX
+};
+
+typedef struct pmac_gpio {
+#ifdef CONFIG_PPC_HAS_FEATURE_CALLS
+	unsigned int addr;
+#else
+	void __iomem *addr;
+#endif
+	int active_state;
+} pmac_gpio_t;
+
+typedef struct pmac_tumbler_t {
+	pmac_keywest_t i2c;
+	pmac_gpio_t audio_reset;
+	pmac_gpio_t amp_mute;
+	pmac_gpio_t hp_mute;
+	pmac_gpio_t hp_detect;
+	int headphone_irq;
+	unsigned int master_vol[2];
+	unsigned int master_switch[2];
+	unsigned int mono_vol[VOL_IDX_LAST_MONO];
+	unsigned int mix_vol[VOL_IDX_LAST_MIX][2]; /* stereo volumes for tas3004 */
+	int drc_range;
+	int drc_enable;
+	int capture_source;
+} pmac_tumbler_t;
+
+
+/*
+ */
+
+static int send_init_client(pmac_keywest_t *i2c, unsigned int *regs)
+{
+	while (*regs > 0) {
+		int err, count = 10;
+		do {
+			err = i2c_smbus_write_byte_data(i2c->client,
+							regs[0], regs[1]);
+			if (err >= 0)
+				break;
+			mdelay(10);
+		} while (count--);
+		if (err < 0)
+			return -ENXIO;
+		regs += 2;
+	}
+	return 0;
+}
+
+
+static int tumbler_init_client(pmac_keywest_t *i2c)
+{
+	static unsigned int regs[] = {
+		/* normal operation, SCLK=64fps, i2s output, i2s input, 16bit width */
+		TAS_REG_MCS, (1<<6)|(2<<4)|(2<<2)|0,
+		0, /* terminator */
+	};
+	return send_init_client(i2c, regs);
+}
+
+static int snapper_init_client(pmac_keywest_t *i2c)
+{
+	static unsigned int regs[] = {
+		/* normal operation, SCLK=64fps, i2s output, 16bit width */
+		TAS_REG_MCS, (1<<6)|(2<<4)|0,
+		/* normal operation, all-pass mode */
+		TAS_REG_MCS2, (1<<1),
+		/* normal output, no deemphasis, A input, power-up, line-in */
+		TAS_REG_ACS, 0,
+		0, /* terminator */
+	};
+	return send_init_client(i2c, regs);
+}
+	
+/*
+ * gpio access
+ */
+#ifdef CONFIG_PPC_HAS_FEATURE_CALLS
+#define do_gpio_write(gp, val) \
+	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, (gp)->addr, val)
+#define do_gpio_read(gp) \
+	pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, (gp)->addr, 0)
+#define tumbler_gpio_free(gp) /* NOP */
+#else
+#define do_gpio_write(gp, val)	writeb(val, (gp)->addr)
+#define do_gpio_read(gp)	readb((gp)->addr)
+static inline void tumbler_gpio_free(pmac_gpio_t *gp)
+{
+	if (gp->addr) {
+		iounmap(gp->addr);
+		gp->addr = NULL;
+	}
+}
+#endif /* CONFIG_PPC_HAS_FEATURE_CALLS */
+
+static void write_audio_gpio(pmac_gpio_t *gp, int active)
+{
+	if (! gp->addr)
+		return;
+	active = active ? gp->active_state : !gp->active_state;
+	do_gpio_write(gp, active ? 0x05 : 0x04);
+}
+
+static int read_audio_gpio(pmac_gpio_t *gp)
+{
+	int ret;
+	if (! gp->addr)
+		return 0;
+	ret = ((do_gpio_read(gp) & 0x02) !=0);
+	return ret == gp->active_state;
+}
+
+/*
+ * update master volume
+ */
+static int tumbler_set_master_volume(pmac_tumbler_t *mix)
+{
+	unsigned char block[6];
+	unsigned int left_vol, right_vol;
+  
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	if (! mix->master_switch[0])
+		left_vol = 0;
+	else {
+		left_vol = mix->master_vol[0];
+		if (left_vol >= ARRAY_SIZE(master_volume_table))
+			left_vol = ARRAY_SIZE(master_volume_table) - 1;
+		left_vol = master_volume_table[left_vol];
+	}
+	if (! mix->master_switch[1])
+		right_vol = 0;
+	else {
+		right_vol = mix->master_vol[1];
+		if (right_vol >= ARRAY_SIZE(master_volume_table))
+			right_vol = ARRAY_SIZE(master_volume_table) - 1;
+		right_vol = master_volume_table[right_vol];
+	}
+
+	block[0] = (left_vol >> 16) & 0xff;
+	block[1] = (left_vol >> 8)  & 0xff;
+	block[2] = (left_vol >> 0)  & 0xff;
+
+	block[3] = (right_vol >> 16) & 0xff;
+	block[4] = (right_vol >> 8)  & 0xff;
+	block[5] = (right_vol >> 0)  & 0xff;
+  
+	if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_VOL,
+				       6, block) < 0) {
+		snd_printk("failed to set volume \n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/* output volume */
+static int tumbler_info_master_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = ARRAY_SIZE(master_volume_table) - 1;
+	return 0;
+}
+
+static int tumbler_get_master_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix = chip->mixer_data;
+	snd_assert(mix, return -ENODEV);
+	ucontrol->value.integer.value[0] = mix->master_vol[0];
+	ucontrol->value.integer.value[1] = mix->master_vol[1];
+	return 0;
+}
+
+static int tumbler_put_master_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix = chip->mixer_data;
+	int change;
+
+	snd_assert(mix, return -ENODEV);
+	change = mix->master_vol[0] != ucontrol->value.integer.value[0] ||
+		mix->master_vol[1] != ucontrol->value.integer.value[1];
+	if (change) {
+		mix->master_vol[0] = ucontrol->value.integer.value[0];
+		mix->master_vol[1] = ucontrol->value.integer.value[1];
+		tumbler_set_master_volume(mix);
+	}
+	return change;
+}
+
+/* output switch */
+static int tumbler_get_master_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix = chip->mixer_data;
+	snd_assert(mix, return -ENODEV);
+	ucontrol->value.integer.value[0] = mix->master_switch[0];
+	ucontrol->value.integer.value[1] = mix->master_switch[1];
+	return 0;
+}
+
+static int tumbler_put_master_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix = chip->mixer_data;
+	int change;
+
+	snd_assert(mix, return -ENODEV);
+	change = mix->master_switch[0] != ucontrol->value.integer.value[0] ||
+		mix->master_switch[1] != ucontrol->value.integer.value[1];
+	if (change) {
+		mix->master_switch[0] = !!ucontrol->value.integer.value[0];
+		mix->master_switch[1] = !!ucontrol->value.integer.value[1];
+		tumbler_set_master_volume(mix);
+	}
+	return change;
+}
+
+
+/*
+ * TAS3001c dynamic range compression
+ */
+
+#define TAS3001_DRC_MAX		0x5f
+
+static int tumbler_set_drc(pmac_tumbler_t *mix)
+{
+	unsigned char val[2];
+
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	if (mix->drc_enable) {
+		val[0] = 0xc1; /* enable, 3:1 compression */
+		if (mix->drc_range > TAS3001_DRC_MAX)
+			val[1] = 0xf0;
+		else if (mix->drc_range < 0)
+			val[1] = 0x91;
+		else
+			val[1] = mix->drc_range + 0x91;
+	} else {
+		val[0] = 0;
+		val[1] = 0;
+	}
+
+	if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_DRC,
+				       2, val) < 0) {
+		snd_printk("failed to set DRC\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * TAS3004
+ */
+
+#define TAS3004_DRC_MAX		0xef
+
+static int snapper_set_drc(pmac_tumbler_t *mix)
+{
+	unsigned char val[6];
+
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	if (mix->drc_enable)
+		val[0] = 0x50; /* 3:1 above threshold */
+	else
+		val[0] = 0x51; /* disabled */
+	val[1] = 0x02; /* 1:1 below threshold */
+	if (mix->drc_range > 0xef)
+		val[2] = 0xef;
+	else if (mix->drc_range < 0)
+		val[2] = 0x00;
+	else
+		val[2] = mix->drc_range;
+	val[3] = 0xb0;
+	val[4] = 0x60;
+	val[5] = 0xa0;
+
+	if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_DRC,
+				       6, val) < 0) {
+		snd_printk("failed to set DRC\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int tumbler_info_drc_value(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max =
+		chip->model == PMAC_TUMBLER ? TAS3001_DRC_MAX : TAS3004_DRC_MAX;
+	return 0;
+}
+
+static int tumbler_get_drc_value(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->drc_range;
+	return 0;
+}
+
+static int tumbler_put_drc_value(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->drc_range != ucontrol->value.integer.value[0];
+	if (change) {
+		mix->drc_range = ucontrol->value.integer.value[0];
+		if (chip->model == PMAC_TUMBLER)
+			tumbler_set_drc(mix);
+		else
+			snapper_set_drc(mix);
+	}
+	return change;
+}
+
+static int tumbler_get_drc_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->drc_enable;
+	return 0;
+}
+
+static int tumbler_put_drc_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->drc_enable != ucontrol->value.integer.value[0];
+	if (change) {
+		mix->drc_enable = !!ucontrol->value.integer.value[0];
+		if (chip->model == PMAC_TUMBLER)
+			tumbler_set_drc(mix);
+		else
+			snapper_set_drc(mix);
+	}
+	return change;
+}
+
+
+/*
+ * mono volumes
+ */
+
+struct tumbler_mono_vol {
+	int index;
+	int reg;
+	int bytes;
+	unsigned int max;
+	unsigned int *table;
+};
+
+static int tumbler_set_mono_volume(pmac_tumbler_t *mix, struct tumbler_mono_vol *info)
+{
+	unsigned char block[4];
+	unsigned int vol;
+	int i;
+  
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	vol = mix->mono_vol[info->index];
+	if (vol >= info->max)
+		vol = info->max - 1;
+	vol = info->table[vol];
+	for (i = 0; i < info->bytes; i++)
+		block[i] = (vol >> ((info->bytes - i - 1) * 8)) & 0xff;
+	if (i2c_smbus_write_block_data(mix->i2c.client, info->reg,
+				       info->bytes, block) < 0) {
+		snd_printk("failed to set mono volume %d\n", info->index);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int tumbler_info_mono(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = info->max - 1;
+	return 0;
+}
+
+static int tumbler_get_mono(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value;
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->mono_vol[info->index];
+	return 0;
+}
+
+static int tumbler_put_mono(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value;
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->mono_vol[info->index] != ucontrol->value.integer.value[0];
+	if (change) {
+		mix->mono_vol[info->index] = ucontrol->value.integer.value[0];
+		tumbler_set_mono_volume(mix, info);
+	}
+	return change;
+}
+
+/* TAS3001c mono volumes */
+static struct tumbler_mono_vol tumbler_pcm_vol_info = {
+	.index = VOL_IDX_PCM_MONO,
+	.reg = TAS_REG_PCM,
+	.bytes = 3,
+	.max = ARRAY_SIZE(mixer_volume_table),
+	.table = mixer_volume_table,
+};
+
+static struct tumbler_mono_vol tumbler_bass_vol_info = {
+	.index = VOL_IDX_BASS,
+	.reg = TAS_REG_BASS,
+	.bytes = 1,
+	.max = ARRAY_SIZE(bass_volume_table),
+	.table = bass_volume_table,
+};
+
+static struct tumbler_mono_vol tumbler_treble_vol_info = {
+	.index = VOL_IDX_TREBLE,
+	.reg = TAS_REG_TREBLE,
+	.bytes = 1,
+	.max = ARRAY_SIZE(treble_volume_table),
+	.table = treble_volume_table,
+};
+
+/* TAS3004 mono volumes */
+static struct tumbler_mono_vol snapper_bass_vol_info = {
+	.index = VOL_IDX_BASS,
+	.reg = TAS_REG_BASS,
+	.bytes = 1,
+	.max = ARRAY_SIZE(snapper_bass_volume_table),
+	.table = snapper_bass_volume_table,
+};
+
+static struct tumbler_mono_vol snapper_treble_vol_info = {
+	.index = VOL_IDX_TREBLE,
+	.reg = TAS_REG_TREBLE,
+	.bytes = 1,
+	.max = ARRAY_SIZE(snapper_treble_volume_table),
+	.table = snapper_treble_volume_table,
+};
+
+
+#define DEFINE_MONO(xname,type) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+	.name = xname, \
+	.info = tumbler_info_mono, \
+	.get = tumbler_get_mono, \
+	.put = tumbler_put_mono, \
+	.private_value = (unsigned long)(&tumbler_##type##_vol_info), \
+}
+
+#define DEFINE_SNAPPER_MONO(xname,type) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+	.name = xname, \
+	.info = tumbler_info_mono, \
+	.get = tumbler_get_mono, \
+	.put = tumbler_put_mono, \
+	.private_value = (unsigned long)(&snapper_##type##_vol_info), \
+}
+
+
+/*
+ * snapper mixer volumes
+ */
+
+static int snapper_set_mix_vol1(pmac_tumbler_t *mix, int idx, int ch, int reg)
+{
+	int i, j, vol;
+	unsigned char block[9];
+
+	vol = mix->mix_vol[idx][ch];
+	if (vol >= ARRAY_SIZE(mixer_volume_table)) {
+		vol = ARRAY_SIZE(mixer_volume_table) - 1;
+		mix->mix_vol[idx][ch] = vol;
+	}
+
+	for (i = 0; i < 3; i++) {
+		vol = mix->mix_vol[i][ch];
+		vol = mixer_volume_table[vol];
+		for (j = 0; j < 3; j++)
+			block[i * 3 + j] = (vol >> ((2 - j) * 8)) & 0xff;
+	}
+	if (i2c_smbus_write_block_data(mix->i2c.client, reg, 9, block) < 0) {
+		snd_printk("failed to set mono volume %d\n", reg);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snapper_set_mix_vol(pmac_tumbler_t *mix, int idx)
+{
+	if (! mix->i2c.client)
+		return -ENODEV;
+	if (snapper_set_mix_vol1(mix, idx, 0, TAS_REG_LMIX) < 0 ||
+	    snapper_set_mix_vol1(mix, idx, 1, TAS_REG_RMIX) < 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int snapper_info_mix(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = ARRAY_SIZE(mixer_volume_table) - 1;
+	return 0;
+}
+
+static int snapper_get_mix(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	int idx = (int)kcontrol->private_value;
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->mix_vol[idx][0];
+	ucontrol->value.integer.value[1] = mix->mix_vol[idx][1];
+	return 0;
+}
+
+static int snapper_put_mix(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	int idx = (int)kcontrol->private_value;
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->mix_vol[idx][0] != ucontrol->value.integer.value[0] ||
+		mix->mix_vol[idx][1] != ucontrol->value.integer.value[1];
+	if (change) {
+		mix->mix_vol[idx][0] = ucontrol->value.integer.value[0];
+		mix->mix_vol[idx][1] = ucontrol->value.integer.value[1];
+		snapper_set_mix_vol(mix, idx);
+	}
+	return change;
+}
+
+
+/*
+ * mute switches
+ */
+
+enum { TUMBLER_MUTE_HP, TUMBLER_MUTE_AMP };
+
+static int tumbler_get_mute_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	pmac_gpio_t *gp;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	gp = (kcontrol->private_value == TUMBLER_MUTE_HP) ? &mix->hp_mute : &mix->amp_mute;
+	ucontrol->value.integer.value[0] = ! read_audio_gpio(gp);
+	return 0;
+}
+
+static int tumbler_put_mute_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix;
+	pmac_gpio_t *gp;
+	int val;
+#ifdef PMAC_SUPPORT_AUTOMUTE
+	if (chip->update_automute && chip->auto_mute)
+		return 0; /* don't touch in the auto-mute mode */
+#endif	
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	gp = (kcontrol->private_value == TUMBLER_MUTE_HP) ? &mix->hp_mute : &mix->amp_mute;
+	val = ! read_audio_gpio(gp);
+	if (val != ucontrol->value.integer.value[0]) {
+		write_audio_gpio(gp, ! ucontrol->value.integer.value[0]);
+		return 1;
+	}
+	return 0;
+}
+
+static int snapper_set_capture_source(pmac_tumbler_t *mix)
+{
+	if (! mix->i2c.client)
+		return -ENODEV;
+	return i2c_smbus_write_byte_data(mix->i2c.client, TAS_REG_ACS,
+					 mix->capture_source ? 2 : 0);
+}
+
+static int snapper_info_capture_source(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	static char *texts[2] = {
+		"Line", "Mic"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snapper_get_capture_source(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix = chip->mixer_data;
+
+	snd_assert(mix, return -ENODEV);
+	ucontrol->value.integer.value[0] = mix->capture_source;
+	return 0;
+}
+
+static int snapper_put_capture_source(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	pmac_t *chip = snd_kcontrol_chip(kcontrol);
+	pmac_tumbler_t *mix = chip->mixer_data;
+	int change;
+
+	snd_assert(mix, return -ENODEV);
+	change = ucontrol->value.integer.value[0] != mix->capture_source;
+	if (change) {
+		mix->capture_source = !!ucontrol->value.integer.value[0];
+		snapper_set_capture_source(mix);
+	}
+	return change;
+}
+
+#define DEFINE_SNAPPER_MIX(xname,idx,ofs) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+	.name = xname, \
+	.info = snapper_info_mix, \
+	.get = snapper_get_mix, \
+	.put = snapper_put_mix, \
+	.index = idx,\
+	.private_value = ofs, \
+}
+
+
+/*
+ */
+static snd_kcontrol_new_t tumbler_mixers[] __initdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Volume",
+	  .info = tumbler_info_master_volume,
+	  .get = tumbler_get_master_volume,
+	  .put = tumbler_put_master_volume
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Switch",
+	  .info = snd_pmac_boolean_stereo_info,
+	  .get = tumbler_get_master_switch,
+	  .put = tumbler_put_master_switch
+	},
+	DEFINE_MONO("Tone Control - Bass", bass),
+	DEFINE_MONO("Tone Control - Treble", treble),
+	DEFINE_MONO("PCM Playback Volume", pcm),
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "DRC Range",
+	  .info = tumbler_info_drc_value,
+	  .get = tumbler_get_drc_value,
+	  .put = tumbler_put_drc_value
+	},
+};
+
+static snd_kcontrol_new_t snapper_mixers[] __initdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Volume",
+	  .info = tumbler_info_master_volume,
+	  .get = tumbler_get_master_volume,
+	  .put = tumbler_put_master_volume
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Switch",
+	  .info = snd_pmac_boolean_stereo_info,
+	  .get = tumbler_get_master_switch,
+	  .put = tumbler_put_master_switch
+	},
+	DEFINE_SNAPPER_MIX("PCM Playback Volume", 0, VOL_IDX_PCM),
+	DEFINE_SNAPPER_MIX("PCM Playback Volume", 1, VOL_IDX_PCM2),
+	DEFINE_SNAPPER_MIX("Monitor Mix Volume", 0, VOL_IDX_ADC),
+	DEFINE_SNAPPER_MONO("Tone Control - Bass", bass),
+	DEFINE_SNAPPER_MONO("Tone Control - Treble", treble),
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "DRC Range",
+	  .info = tumbler_info_drc_value,
+	  .get = tumbler_get_drc_value,
+	  .put = tumbler_put_drc_value
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Input Source", /* FIXME: "Capture Source" doesn't work properly */
+	  .info = snapper_info_capture_source,
+	  .get = snapper_get_capture_source,
+	  .put = snapper_put_capture_source
+	},
+};
+
+static snd_kcontrol_new_t tumbler_hp_sw __initdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Headphone Playback Switch",
+	.info = snd_pmac_boolean_mono_info,
+	.get = tumbler_get_mute_switch,
+	.put = tumbler_put_mute_switch,
+	.private_value = TUMBLER_MUTE_HP,
+};
+static snd_kcontrol_new_t tumbler_speaker_sw __initdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PC Speaker Playback Switch",
+	.info = snd_pmac_boolean_mono_info,
+	.get = tumbler_get_mute_switch,
+	.put = tumbler_put_mute_switch,
+	.private_value = TUMBLER_MUTE_AMP,
+};
+static snd_kcontrol_new_t tumbler_drc_sw __initdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DRC Switch",
+	.info = snd_pmac_boolean_mono_info,
+	.get = tumbler_get_drc_switch,
+	.put = tumbler_put_drc_switch
+};
+
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+/*
+ * auto-mute stuffs
+ */
+static int tumbler_detect_headphone(pmac_t *chip)
+{
+	pmac_tumbler_t *mix = chip->mixer_data;
+	return read_audio_gpio(&mix->hp_detect);
+}
+
+static void check_mute(pmac_t *chip, pmac_gpio_t *gp, int val, int do_notify, snd_kcontrol_t *sw)
+{
+	//pmac_tumbler_t *mix = chip->mixer_data;
+	if (val != read_audio_gpio(gp)) {
+		write_audio_gpio(gp, val);
+		if (do_notify)
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &sw->id);
+	}
+}
+
+static struct work_struct device_change;
+
+static void
+device_change_handler(void *self)
+{
+	pmac_t *chip = (pmac_t*) self;
+	pmac_tumbler_t *mix;
+
+	if (!chip)
+		return;
+
+	mix = chip->mixer_data;
+
+	/* first set the DRC so the speaker do not explode -ReneR */
+	if (chip->model == PMAC_TUMBLER)
+		tumbler_set_drc(mix);
+	else
+		snapper_set_drc(mix);
+
+	/* reset the master volume so the correct amplification is applied */
+	tumbler_set_master_volume(mix);
+}
+
+static void tumbler_update_automute(pmac_t *chip, int do_notify)
+{
+	if (chip->auto_mute) {
+		pmac_tumbler_t *mix = chip->mixer_data;
+		snd_assert(mix, return);
+		if (tumbler_detect_headphone(chip)) {
+			/* mute speaker */
+			check_mute(chip, &mix->amp_mute, 1, do_notify, chip->speaker_sw_ctl);
+			check_mute(chip, &mix->hp_mute, 0, do_notify, chip->master_sw_ctl);
+			mix->drc_enable = 0;
+
+		} else {
+			/* unmute speaker */
+			check_mute(chip, &mix->amp_mute, 0, do_notify, chip->speaker_sw_ctl);
+			check_mute(chip, &mix->hp_mute, 1, do_notify, chip->master_sw_ctl);
+			mix->drc_enable = 1;
+		}
+		if (do_notify) {
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->hp_detect_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			               &chip->drc_sw_ctl->id);
+		}
+
+		/* finally we need to schedule an update of the mixer values
+		   (master and DRC are enough for now) -ReneR */
+		schedule_work(&device_change);
+
+	}
+}
+#endif /* PMAC_SUPPORT_AUTOMUTE */
+
+
+/* interrupt - headphone plug changed */
+static irqreturn_t headphone_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	pmac_t *chip = devid;
+	if (chip->update_automute && chip->initialized) {
+		chip->update_automute(chip, 1);
+		return IRQ_HANDLED;
+	}
+	return IRQ_NONE;
+}
+
+/* look for audio-gpio device */
+static struct device_node *find_audio_device(const char *name)
+{
+	struct device_node *np;
+  
+	if (! (np = find_devices("gpio")))
+		return NULL;
+  
+	for (np = np->child; np; np = np->sibling) {
+		char *property = get_property(np, "audio-gpio", NULL);
+		if (property && strcmp(property, name) == 0)
+			return np;
+	}  
+	return NULL;
+}
+
+/* look for audio-gpio device */
+static struct device_node *find_compatible_audio_device(const char *name)
+{
+	struct device_node *np;
+  
+	if (! (np = find_devices("gpio")))
+		return NULL;
+  
+	for (np = np->child; np; np = np->sibling) {
+		if (device_is_compatible(np, name))
+			return np;
+	}  
+	return NULL;
+}
+
+/* find an audio device and get its address */
+static unsigned long tumbler_find_device(const char *device, pmac_gpio_t *gp, int is_compatible)
+{
+	struct device_node *node;
+	u32 *base;
+
+	if (is_compatible)
+		node = find_compatible_audio_device(device);
+	else
+		node = find_audio_device(device);
+	if (! node) {
+		snd_printdd("cannot find device %s\n", device);
+		return -ENODEV;
+	}
+
+	base = (u32 *)get_property(node, "AAPL,address", NULL);
+	if (! base) {
+		snd_printd("cannot find address for device %s\n", device);
+		return -ENODEV;
+	}
+
+#ifdef CONFIG_PPC_HAS_FEATURE_CALLS
+	gp->addr = (*base) & 0x0000ffff;
+#else
+	gp->addr = ioremap((unsigned long)(*base), 1);
+#endif
+	base = (u32 *)get_property(node, "audio-gpio-active-state", NULL);
+	if (base)
+		gp->active_state = *base;
+	else
+		gp->active_state = 1;
+
+
+	return (node->n_intrs > 0) ? node->intrs[0].line : 0;
+}
+
+/* reset audio */
+static void tumbler_reset_audio(pmac_t *chip)
+{
+	pmac_tumbler_t *mix = chip->mixer_data;
+
+	write_audio_gpio(&mix->audio_reset, 0);
+	big_mdelay(200);
+	write_audio_gpio(&mix->audio_reset, 1);
+	big_mdelay(100);
+	write_audio_gpio(&mix->audio_reset, 0);
+	big_mdelay(100);
+}
+
+#ifdef CONFIG_PMAC_PBOOK
+/* resume mixer */
+static void tumbler_resume(pmac_t *chip)
+{
+	pmac_tumbler_t *mix = chip->mixer_data;
+
+	snd_assert(mix, return);
+
+	tumbler_reset_audio(chip);
+	if (mix->i2c.client && mix->i2c.init_client) {
+		if (mix->i2c.init_client(&mix->i2c) < 0)
+			printk(KERN_ERR "tumbler_init_client error\n");
+	} else
+		printk(KERN_ERR "tumbler: i2c is not initialized\n");
+	if (chip->model == PMAC_TUMBLER) {
+		tumbler_set_mono_volume(mix, &tumbler_pcm_vol_info);
+		tumbler_set_mono_volume(mix, &tumbler_bass_vol_info);
+		tumbler_set_mono_volume(mix, &tumbler_treble_vol_info);
+		tumbler_set_drc(mix);
+	} else {
+		snapper_set_mix_vol(mix, VOL_IDX_PCM);
+		snapper_set_mix_vol(mix, VOL_IDX_PCM2);
+		snapper_set_mix_vol(mix, VOL_IDX_ADC);
+		tumbler_set_mono_volume(mix, &snapper_bass_vol_info);
+		tumbler_set_mono_volume(mix, &snapper_treble_vol_info);
+		snapper_set_drc(mix);
+		snapper_set_capture_source(mix);
+	}
+	tumbler_set_master_volume(mix);
+	if (chip->update_automute)
+		chip->update_automute(chip, 0);
+}
+#endif
+
+/* initialize tumbler */
+static int __init tumbler_init(pmac_t *chip)
+{
+	int irq, err;
+	pmac_tumbler_t *mix = chip->mixer_data;
+	snd_assert(mix, return -EINVAL);
+
+	tumbler_find_device("audio-hw-reset", &mix->audio_reset, 0);
+	tumbler_find_device("amp-mute", &mix->amp_mute, 0);
+	tumbler_find_device("headphone-mute", &mix->hp_mute, 0);
+	irq = tumbler_find_device("headphone-detect", &mix->hp_detect, 0);
+	if (irq < 0)
+		irq = tumbler_find_device("keywest-gpio15", &mix->hp_detect, 1);
+
+	tumbler_reset_audio(chip);
+
+	/* activate headphone status interrupts */
+  	if (irq >= 0) {
+		unsigned char val;
+		if ((err = request_irq(irq, headphone_intr, 0,
+				       "Tumbler Headphone Detection", chip)) < 0)
+			return err;
+		/* activate headphone status interrupts */
+		val = do_gpio_read(&mix->hp_detect);
+		do_gpio_write(&mix->hp_detect, val | 0x80);
+	}
+	mix->headphone_irq = irq;
+  
+	return 0;
+}
+
+static void tumbler_cleanup(pmac_t *chip)
+{
+	pmac_tumbler_t *mix = chip->mixer_data;
+	if (! mix)
+		return;
+
+	if (mix->headphone_irq >= 0)
+		free_irq(mix->headphone_irq, chip);
+	tumbler_gpio_free(&mix->audio_reset);
+	tumbler_gpio_free(&mix->amp_mute);
+	tumbler_gpio_free(&mix->hp_mute);
+	tumbler_gpio_free(&mix->hp_detect);
+	snd_pmac_keywest_cleanup(&mix->i2c);
+	kfree(mix);
+	chip->mixer_data = NULL;
+}
+
+/* exported */
+int __init snd_pmac_tumbler_init(pmac_t *chip)
+{
+	int i, err;
+	pmac_tumbler_t *mix;
+	u32 *paddr;
+	struct device_node *tas_node;
+	char *chipname;
+
+#ifdef CONFIG_KMOD
+	if (current->fs->root)
+		request_module("i2c-keywest");
+#endif /* CONFIG_KMOD */	
+
+	mix = kmalloc(sizeof(*mix), GFP_KERNEL);
+	if (! mix)
+		return -ENOMEM;
+	memset(mix, 0, sizeof(*mix));
+	mix->headphone_irq = -1;
+
+	chip->mixer_data = mix;
+	chip->mixer_free = tumbler_cleanup;
+
+	if ((err = tumbler_init(chip)) < 0)
+		return err;
+
+	/* set up TAS */
+	tas_node = find_devices("deq");
+	if (tas_node == NULL)
+		return -ENODEV;
+
+	paddr = (u32 *)get_property(tas_node, "i2c-address", NULL);
+	if (paddr)
+		mix->i2c.addr = (*paddr) >> 1;
+	else
+		mix->i2c.addr = TAS_I2C_ADDR;
+
+	if (chip->model == PMAC_TUMBLER) {
+		mix->i2c.init_client = tumbler_init_client;
+		mix->i2c.name = "TAS3001c";
+		chipname = "Tumbler";
+	} else {
+		mix->i2c.init_client = snapper_init_client;
+		mix->i2c.name = "TAS3004";
+		chipname = "Snapper";
+	}
+
+	if ((err = snd_pmac_keywest_init(&mix->i2c)) < 0)
+		return err;
+
+	/*
+	 * build mixers
+	 */
+	sprintf(chip->card->mixername, "PowerMac %s", chipname);
+
+	if (chip->model == PMAC_TUMBLER) {
+		for (i = 0; i < ARRAY_SIZE(tumbler_mixers); i++) {
+			if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&tumbler_mixers[i], chip))) < 0)
+				return err;
+		}
+	} else {
+		for (i = 0; i < ARRAY_SIZE(snapper_mixers); i++) {
+			if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snapper_mixers[i], chip))) < 0)
+				return err;
+		}
+	}
+	chip->master_sw_ctl = snd_ctl_new1(&tumbler_hp_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0)
+		return err;
+	chip->speaker_sw_ctl = snd_ctl_new1(&tumbler_speaker_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0)
+		return err;
+	chip->drc_sw_ctl = snd_ctl_new1(&tumbler_drc_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->drc_sw_ctl)) < 0)
+		return err;
+
+
+#ifdef CONFIG_PMAC_PBOOK
+	chip->resume = tumbler_resume;
+#endif
+
+	INIT_WORK(&device_change, device_change_handler, (void *)chip);
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+	if (mix->headphone_irq >=0 && (err = snd_pmac_add_automute(chip)) < 0)
+		return err;
+	chip->detect_headphone = tumbler_detect_headphone;
+	chip->update_automute = tumbler_update_automute;
+	tumbler_update_automute(chip, 0); /* update the status only */
+#endif
+
+	return 0;
+}
diff --git a/sound/ppc/tumbler_volume.h b/sound/ppc/tumbler_volume.h
new file mode 100644
index 000000000000..ef8d85d58b02
--- /dev/null
+++ b/sound/ppc/tumbler_volume.h
@@ -0,0 +1,250 @@
+/* volume tables, taken from TAS3001c data manual */
+/* volume gain values */
+/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */
+static unsigned int master_volume_table[] = {
+	0x00000015, 0x00000016,	0x00000017,
+	0x00000019, 0x0000001a,	0x0000001c,
+	0x0000001d, 0x0000001f,	0x00000021,
+	0x00000023, 0x00000025,	0x00000027,
+	0x00000029, 0x0000002c,	0x0000002e,
+	0x00000031, 0x00000034,	0x00000037,
+	0x0000003a, 0x0000003e,	0x00000042,
+	0x00000045, 0x0000004a,	0x0000004e,
+	0x00000053, 0x00000057,	0x0000005d,
+	0x00000062, 0x00000068,	0x0000006e,
+	0x00000075, 0x0000007b,	0x00000083,
+	0x0000008b, 0x00000093,	0x0000009b,
+	0x000000a5, 0x000000ae,	0x000000b9,
+	0x000000c4, 0x000000cf,	0x000000dc,
+	0x000000e9, 0x000000f6,	0x00000105,
+	0x00000114, 0x00000125,	0x00000136,
+	0x00000148, 0x0000015c,	0x00000171,
+	0x00000186, 0x0000019e,	0x000001b6,
+	0x000001d0, 0x000001eb,	0x00000209,
+	0x00000227, 0x00000248,	0x0000026b,
+	0x0000028f, 0x000002b6,	0x000002df,
+	0x0000030b, 0x00000339,	0x0000036a,
+	0x0000039e, 0x000003d5,	0x0000040f,
+	0x0000044c, 0x0000048d,	0x000004d2,
+	0x0000051c, 0x00000569,	0x000005bb,
+	0x00000612, 0x0000066e,	0x000006d0,
+	0x00000737, 0x000007a5,	0x00000818,
+	0x00000893, 0x00000915,	0x0000099f,
+	0x00000a31, 0x00000acc,	0x00000b6f,
+	0x00000c1d, 0x00000cd5,	0x00000d97,
+	0x00000e65, 0x00000f40,	0x00001027,
+	0x0000111c, 0x00001220,	0x00001333,
+	0x00001456, 0x0000158a,	0x000016d1,
+	0x0000182b, 0x0000199a,	0x00001b1e,
+	0x00001cb9, 0x00001e6d,	0x0000203a,
+	0x00002223, 0x00002429,	0x0000264e,
+	0x00002893, 0x00002afa,	0x00002d86,
+	0x00003039, 0x00003314,	0x0000361b,
+	0x00003950, 0x00003cb5,	0x0000404e,
+	0x0000441d, 0x00004827,	0x00004c6d,
+	0x000050f4, 0x000055c0,	0x00005ad5,
+	0x00006037, 0x000065ea,	0x00006bf4,
+	0x0000725a, 0x00007920,	0x0000804e,
+	0x000087e8, 0x00008ff6,	0x0000987d,
+	0x0000a186, 0x0000ab19,	0x0000b53c,
+	0x0000bff9, 0x0000cb59,	0x0000d766,
+	0x0000e429, 0x0000f1ae,	0x00010000,
+	0x00010f2b, 0x00011f3d,	0x00013042,
+	0x00014249, 0x00015562,	0x0001699c,
+	0x00017f09, 0x000195bc,	0x0001adc6,
+	0x0001c73d, 0x0001e237,	0x0001feca,
+	0x00021d0e, 0x00023d1d,	0x00025f12,
+	0x0002830b, 0x0002a925,	0x0002d182,
+	0x0002fc42, 0x0003298b,	0x00035983,
+	0x00038c53, 0x0003c225,	0x0003fb28,
+	0x0004378b, 0x00047783,	0x0004bb44,
+	0x0005030a, 0x00054f10,	0x00059f98,
+	0x0005f4e5, 0x00064f40,	0x0006aef6,
+	0x00071457, 0x00077fbb,	0x0007f17b,
+};
+
+/* treble table for TAS3001c */
+/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */
+static unsigned int treble_volume_table[] = {
+	0x96, 0x95, 0x94,
+	0x93, 0x92, 0x91,
+	0x90, 0x8f, 0x8e,
+	0x8d, 0x8c, 0x8b,
+	0x8a, 0x89, 0x88,
+	0x87, 0x86, 0x85,
+	0x84, 0x83, 0x82,
+	0x81, 0x80, 0x7f,
+	0x7e, 0x7d, 0x7c,
+	0x7b, 0x7a, 0x79,
+	0x78, 0x77, 0x76,
+	0x75, 0x74, 0x73,
+	0x72, 0x71, 0x70,
+	0x6e, 0x6d, 0x6c,
+	0x6b, 0x69, 0x68,
+	0x66, 0x65, 0x63,
+	0x62, 0x60, 0x5e,
+	0x5c, 0x5a, 0x57,
+	0x55, 0x52, 0x4f,
+	0x4c, 0x49, 0x45,
+	0x42, 0x3e, 0x3a,
+	0x36, 0x32, 0x2d,
+	0x28, 0x22, 0x1c,
+	0x16, 0x10, 0x09,
+	0x01,
+};
+
+/* bass table for TAS3001c */
+/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */
+static unsigned int bass_volume_table[] = {
+	0x86, 0x82, 0x7f,
+	0x7d, 0x7a, 0x78,
+	0x76, 0x74, 0x72,
+	0x70, 0x6e, 0x6d,
+	0x6b, 0x69, 0x66,
+	0x64, 0x61, 0x5f,
+	0x5d, 0x5c, 0x5a,
+	0x59, 0x58, 0x56,
+	0x55, 0x54, 0x53,
+	0x51, 0x4f, 0x4d,
+	0x4b, 0x49, 0x46,
+	0x44, 0x42, 0x40,
+	0x3e, 0x3c, 0x3b,
+	0x39, 0x38, 0x36,
+	0x35, 0x33, 0x31,
+	0x30, 0x2e, 0x2c,
+	0x2b, 0x29, 0x28,
+	0x26, 0x25, 0x23,
+	0x21, 0x1f, 0x1c,
+	0x19, 0x18, 0x17,
+	0x16, 0x14, 0x13,
+	0x12, 0x10, 0x0f,
+	0x0d, 0x0b, 0x0a,
+	0x08, 0x06, 0x03,
+	0x01,
+};
+
+/* mixer (pcm) volume table */
+/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */
+static unsigned int mixer_volume_table[] = {
+	0x00014b, 0x00015f, 0x000174,
+	0x00018a, 0x0001a1, 0x0001ba,
+	0x0001d4, 0x0001f0, 0x00020d,
+	0x00022c, 0x00024d, 0x000270,
+	0x000295, 0x0002bc, 0x0002e6,
+	0x000312, 0x000340, 0x000372,
+	0x0003a6, 0x0003dd, 0x000418,
+	0x000456, 0x000498, 0x0004de,
+	0x000528, 0x000576, 0x0005c9,
+	0x000620, 0x00067d, 0x0006e0,
+	0x000748, 0x0007b7, 0x00082c,
+	0x0008a8, 0x00092b, 0x0009b6,
+	0x000a49, 0x000ae5, 0x000b8b,
+	0x000c3a, 0x000cf3, 0x000db8,
+	0x000e88, 0x000f64, 0x00104e,
+	0x001145, 0x00124b, 0x001361,
+	0x001487, 0x0015be, 0x001708,
+	0x001865, 0x0019d8, 0x001b60,
+	0x001cff, 0x001eb7, 0x002089,
+	0x002276, 0x002481, 0x0026ab,
+	0x0028f5, 0x002b63, 0x002df5,
+	0x0030ae, 0x003390, 0x00369e,
+	0x0039db, 0x003d49, 0x0040ea,
+	0x0044c3, 0x0048d6, 0x004d27,
+	0x0051b9, 0x005691, 0x005bb2,
+	0x006121, 0x0066e3, 0x006cfb,
+	0x007370, 0x007a48, 0x008186,
+	0x008933, 0x009154, 0x0099f1,
+	0x00a310, 0x00acba, 0x00b6f6,
+	0x00c1cd, 0x00cd49, 0x00d973,
+	0x00e655, 0x00f3fb, 0x010270,
+	0x0111c0, 0x0121f9, 0x013328,
+	0x01455b, 0x0158a2, 0x016d0e,
+	0x0182af, 0x019999, 0x01b1de,
+	0x01cb94, 0x01e6cf, 0x0203a7,
+	0x022235, 0x024293, 0x0264db,
+	0x02892c, 0x02afa3, 0x02d862,
+	0x03038a, 0x033142, 0x0361af,
+	0x0394fa, 0x03cb50, 0x0404de,
+	0x0441d5, 0x048268, 0x04c6d0,
+	0x050f44, 0x055c04, 0x05ad50,
+	0x06036e, 0x065ea5, 0x06bf44,
+	0x07259d, 0x079207, 0x0804dc,
+	0x087e80, 0x08ff59, 0x0987d5,
+	0x0a1866, 0x0ab189, 0x0b53be,
+	0x0bff91, 0x0cb591, 0x0d765a,
+	0x0e4290, 0x0f1adf, 0x100000,
+	0x10f2b4, 0x11f3c9, 0x13041a,
+	0x14248e, 0x15561a, 0x1699c0,
+	0x17f094, 0x195bb8, 0x1adc61,
+	0x1c73d5, 0x1e236d, 0x1fec98,
+	0x21d0d9, 0x23d1cd, 0x25f125,
+	0x2830af, 0x2a9254, 0x2d1818,
+	0x2fc420, 0x3298b0, 0x35982f,
+	0x38c528, 0x3c224c, 0x3fb278,
+	0x437880, 0x477828, 0x4bb446,
+	0x5030a1, 0x54f106, 0x59f980,
+	0x5f4e52, 0x64f403, 0x6aef5d,
+	0x714575, 0x77fbaa, 0x7f17af,
+};
+
+
+/* treble table for TAS3004 */
+/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */
+static unsigned int snapper_treble_volume_table[] = {
+	0x96, 0x95, 0x94,
+	0x93, 0x92, 0x91,
+	0x90, 0x8f, 0x8e,
+	0x8d, 0x8c, 0x8b,
+	0x8a, 0x89, 0x88,
+	0x87, 0x86, 0x85,
+	0x84, 0x83, 0x82,
+	0x81, 0x80, 0x7f,
+	0x7e, 0x7d, 0x7c,
+	0x7b, 0x7a, 0x79,
+	0x78, 0x77, 0x76,
+	0x75, 0x74, 0x73,
+	0x72, 0x71, 0x70,
+	0x6f, 0x6d, 0x6c,
+	0x6b, 0x69, 0x68,
+	0x67, 0x65, 0x63,
+	0x62, 0x60, 0x5d,
+	0x5b, 0x59, 0x56,
+	0x53, 0x51, 0x4d,
+	0x4a, 0x47, 0x43,
+	0x3f, 0x3b, 0x36,
+	0x31, 0x2c, 0x26,
+	0x20, 0x1a, 0x13,
+	0x08, 0x04, 0x01,
+	0x01,
+};
+
+/* bass table for TAS3004 */
+/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */
+static unsigned int snapper_bass_volume_table[] = {
+	0x96, 0x95, 0x94,
+	0x93, 0x92, 0x91,
+	0x90, 0x8f, 0x8e,
+	0x8d, 0x8c, 0x8b,
+	0x8a, 0x89, 0x88,
+	0x87, 0x86, 0x85,
+	0x84, 0x83, 0x82,
+	0x81, 0x80, 0x7f,
+	0x7e, 0x7d, 0x7c,
+	0x7b, 0x7a, 0x79,
+	0x78, 0x77, 0x76,
+	0x75, 0x74, 0x73,
+	0x72, 0x71, 0x6f,
+	0x6e, 0x6d, 0x6b,
+	0x6a, 0x69, 0x67,
+	0x66, 0x65, 0x63,
+	0x62, 0x61, 0x5f,
+	0x5d, 0x5b, 0x58,
+	0x55, 0x52, 0x4f,
+	0x4c, 0x49, 0x46,
+	0x43, 0x3f, 0x3b,
+	0x37, 0x33, 0x2e,
+	0x29, 0x24, 0x1e,
+	0x18, 0x11, 0x0a,
+	0x01,
+};
+