Merge branch 'topic/aoa' into to-push
authorTakashi Iwai <tiwai@suse.de>
Thu, 25 Dec 2008 10:40:24 +0000 (11:40 +0100)
committerTakashi Iwai <tiwai@suse.de>
Thu, 25 Dec 2008 10:40:24 +0000 (11:40 +0100)
39 files changed:
sound/aoa/codecs/Makefile
sound/aoa/codecs/onyx.c [new file with mode: 0644]
sound/aoa/codecs/onyx.h [new file with mode: 0644]
sound/aoa/codecs/snd-aoa-codec-onyx.c [deleted file]
sound/aoa/codecs/snd-aoa-codec-onyx.h [deleted file]
sound/aoa/codecs/snd-aoa-codec-tas-basstreble.h [deleted file]
sound/aoa/codecs/snd-aoa-codec-tas-gain-table.h [deleted file]
sound/aoa/codecs/snd-aoa-codec-tas.c [deleted file]
sound/aoa/codecs/snd-aoa-codec-tas.h [deleted file]
sound/aoa/codecs/snd-aoa-codec-toonie.c [deleted file]
sound/aoa/codecs/tas-basstreble.h [new file with mode: 0644]
sound/aoa/codecs/tas-gain-table.h [new file with mode: 0644]
sound/aoa/codecs/tas.c [new file with mode: 0644]
sound/aoa/codecs/tas.h [new file with mode: 0644]
sound/aoa/codecs/toonie.c [new file with mode: 0644]
sound/aoa/core/Makefile
sound/aoa/core/alsa.c [new file with mode: 0644]
sound/aoa/core/alsa.h [new file with mode: 0644]
sound/aoa/core/core.c [new file with mode: 0644]
sound/aoa/core/gpio-feature.c [new file with mode: 0644]
sound/aoa/core/gpio-pmf.c [new file with mode: 0644]
sound/aoa/core/snd-aoa-alsa.c [deleted file]
sound/aoa/core/snd-aoa-alsa.h [deleted file]
sound/aoa/core/snd-aoa-core.c [deleted file]
sound/aoa/core/snd-aoa-gpio-feature.c [deleted file]
sound/aoa/core/snd-aoa-gpio-pmf.c [deleted file]
sound/aoa/fabrics/Makefile
sound/aoa/fabrics/layout.c [new file with mode: 0644]
sound/aoa/fabrics/snd-aoa-fabric-layout.c [deleted file]
sound/aoa/soundbus/i2sbus/Makefile
sound/aoa/soundbus/i2sbus/control.c [new file with mode: 0644]
sound/aoa/soundbus/i2sbus/core.c [new file with mode: 0644]
sound/aoa/soundbus/i2sbus/i2sbus-control.c [deleted file]
sound/aoa/soundbus/i2sbus/i2sbus-core.c [deleted file]
sound/aoa/soundbus/i2sbus/i2sbus-interface.h [deleted file]
sound/aoa/soundbus/i2sbus/i2sbus-pcm.c [deleted file]
sound/aoa/soundbus/i2sbus/i2sbus.h
sound/aoa/soundbus/i2sbus/interface.h [new file with mode: 0644]
sound/aoa/soundbus/i2sbus/pcm.c [new file with mode: 0644]

index 31cbe68..c3ee77f 100644 (file)
@@ -1,3 +1,7 @@
+snd-aoa-codec-onyx-objs := onyx.o
+snd-aoa-codec-tas-objs := tas.o
+snd-aoa-codec-toonie-objs := toonie.o
+
 obj-$(CONFIG_SND_AOA_ONYX) += snd-aoa-codec-onyx.o
 obj-$(CONFIG_SND_AOA_TAS) += snd-aoa-codec-tas.o
 obj-$(CONFIG_SND_AOA_TOONIE) += snd-aoa-codec-toonie.o
diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c
new file mode 100644 (file)
index 0000000..15500b9
--- /dev/null
@@ -0,0 +1,1118 @@
+/*
+ * Apple Onboard Audio driver for Onyx codec
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ *
+ * This is a driver for the pcm3052 codec chip (codenamed Onyx)
+ * that is present in newer Apple hardware (with digital output).
+ *
+ * The Onyx codec has the following connections (listed by the bit
+ * to be used in aoa_codec.connected):
+ *  0: analog output
+ *  1: digital output
+ *  2: line input
+ *  3: microphone input
+ * Note that even though I know of no machine that has for example
+ * the digital output connected but not the analog, I have handled
+ * all the different cases in the code so that this driver may serve
+ * as a good example of what to do.
+ *
+ * NOTE: This driver assumes that there's at most one chip to be
+ *      used with one alsa card, in form of creating all kinds
+ *      of mixer elements without regard for their existence.
+ *      But snd-aoa assumes that there's at most one card, so
+ *      this means you can only have one onyx on a system. This
+ *      should probably be fixed by changing the assumption of
+ *      having just a single card on a system, and making the
+ *      'card' pointer accessible to anyone who needs it instead
+ *      of hiding it in the aoa_snd_* functions...
+ *
+ */
+#include <linux/delay.h>
+#include <linux/module.h>
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");
+
+#include "onyx.h"
+#include "../aoa.h"
+#include "../soundbus/soundbus.h"
+
+
+#define PFX "snd-aoa-codec-onyx: "
+
+struct onyx {
+       /* cache registers 65 to 80, they are write-only! */
+       u8                      cache[16];
+       struct i2c_client       i2c;
+       struct aoa_codec        codec;
+       u32                     initialised:1,
+                               spdif_locked:1,
+                               analog_locked:1,
+                               original_mute:2;
+       int                     open_count;
+       struct codec_info       *codec_info;
+
+       /* mutex serializes concurrent access to the device
+        * and this structure.
+        */
+       struct mutex mutex;
+};
+#define codec_to_onyx(c) container_of(c, struct onyx, codec)
+
+/* both return 0 if all ok, else on error */
+static int onyx_read_register(struct onyx *onyx, u8 reg, u8 *value)
+{
+       s32 v;
+
+       if (reg != ONYX_REG_CONTROL) {
+               *value = onyx->cache[reg-FIRSTREGISTER];
+               return 0;
+       }
+       v = i2c_smbus_read_byte_data(&onyx->i2c, reg);
+       if (v < 0)
+               return -1;
+       *value = (u8)v;
+       onyx->cache[ONYX_REG_CONTROL-FIRSTREGISTER] = *value;
+       return 0;
+}
+
+static int onyx_write_register(struct onyx *onyx, u8 reg, u8 value)
+{
+       int result;
+
+       result = i2c_smbus_write_byte_data(&onyx->i2c, reg, value);
+       if (!result)
+               onyx->cache[reg-FIRSTREGISTER] = value;
+       return result;
+}
+
+/* alsa stuff */
+
+static int onyx_dev_register(struct snd_device *dev)
+{
+       return 0;
+}
+
+static struct snd_device_ops ops = {
+       .dev_register = onyx_dev_register,
+};
+
+/* this is necessary because most alsa mixer programs
+ * can't properly handle the negative range */
+#define VOLUME_RANGE_SHIFT     128
+
+static int onyx_snd_vol_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 2;
+       uinfo->value.integer.min = -128 + VOLUME_RANGE_SHIFT;
+       uinfo->value.integer.max = -1 + VOLUME_RANGE_SHIFT;
+       return 0;
+}
+
+static int onyx_snd_vol_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       s8 l, r;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
+       onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
+       mutex_unlock(&onyx->mutex);
+
+       ucontrol->value.integer.value[0] = l + VOLUME_RANGE_SHIFT;
+       ucontrol->value.integer.value[1] = r + VOLUME_RANGE_SHIFT;
+
+       return 0;
+}
+
+static int onyx_snd_vol_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       s8 l, r;
+
+       if (ucontrol->value.integer.value[0] < -128 + VOLUME_RANGE_SHIFT ||
+           ucontrol->value.integer.value[0] > -1 + VOLUME_RANGE_SHIFT)
+               return -EINVAL;
+       if (ucontrol->value.integer.value[1] < -128 + VOLUME_RANGE_SHIFT ||
+           ucontrol->value.integer.value[1] > -1 + VOLUME_RANGE_SHIFT)
+               return -EINVAL;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
+       onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
+
+       if (l + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[0] &&
+           r + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[1]) {
+               mutex_unlock(&onyx->mutex);
+               return 0;
+       }
+
+       onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_LEFT,
+                           ucontrol->value.integer.value[0]
+                            - VOLUME_RANGE_SHIFT);
+       onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT,
+                           ucontrol->value.integer.value[1]
+                            - VOLUME_RANGE_SHIFT);
+       mutex_unlock(&onyx->mutex);
+
+       return 1;
+}
+
+static struct snd_kcontrol_new volume_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Master Playback Volume",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = onyx_snd_vol_info,
+       .get = onyx_snd_vol_get,
+       .put = onyx_snd_vol_put,
+};
+
+/* like above, this is necessary because a lot
+ * of alsa mixer programs don't handle ranges
+ * that don't start at 0 properly.
+ * even alsamixer is one of them... */
+#define INPUTGAIN_RANGE_SHIFT  (-3)
+
+static int onyx_snd_inputgain_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 3 + INPUTGAIN_RANGE_SHIFT;
+       uinfo->value.integer.max = 28 + INPUTGAIN_RANGE_SHIFT;
+       return 0;
+}
+
+static int onyx_snd_inputgain_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       u8 ig;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &ig);
+       mutex_unlock(&onyx->mutex);
+
+       ucontrol->value.integer.value[0] =
+               (ig & ONYX_ADC_PGA_GAIN_MASK) + INPUTGAIN_RANGE_SHIFT;
+
+       return 0;
+}
+
+static int onyx_snd_inputgain_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       u8 v, n;
+
+       if (ucontrol->value.integer.value[0] < 3 + INPUTGAIN_RANGE_SHIFT ||
+           ucontrol->value.integer.value[0] > 28 + INPUTGAIN_RANGE_SHIFT)
+               return -EINVAL;
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
+       n = v;
+       n &= ~ONYX_ADC_PGA_GAIN_MASK;
+       n |= (ucontrol->value.integer.value[0] - INPUTGAIN_RANGE_SHIFT)
+               & ONYX_ADC_PGA_GAIN_MASK;
+       onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, n);
+       mutex_unlock(&onyx->mutex);
+
+       return n != v;
+}
+
+static struct snd_kcontrol_new inputgain_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Master Capture Volume",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = onyx_snd_inputgain_info,
+       .get = onyx_snd_inputgain_get,
+       .put = onyx_snd_inputgain_put,
+};
+
+static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       static char *texts[] = { "Line-In", "Microphone" };
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->count = 1;
+       uinfo->value.enumerated.items = 2;
+       if (uinfo->value.enumerated.item > 1)
+               uinfo->value.enumerated.item = 1;
+       strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+       return 0;
+}
+
+static int onyx_snd_capture_source_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       s8 v;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
+       mutex_unlock(&onyx->mutex);
+
+       ucontrol->value.enumerated.item[0] = !!(v&ONYX_ADC_INPUT_MIC);
+
+       return 0;
+}
+
+static void onyx_set_capture_source(struct onyx *onyx, int mic)
+{
+       s8 v;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
+       v &= ~ONYX_ADC_INPUT_MIC;
+       if (mic)
+               v |= ONYX_ADC_INPUT_MIC;
+       onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, v);
+       mutex_unlock(&onyx->mutex);
+}
+
+static int onyx_snd_capture_source_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       if (ucontrol->value.enumerated.item[0] > 1)
+               return -EINVAL;
+       onyx_set_capture_source(snd_kcontrol_chip(kcontrol),
+                               ucontrol->value.enumerated.item[0]);
+       return 1;
+}
+
+static struct snd_kcontrol_new capture_source_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       /* If we name this 'Input Source', it properly shows up in
+        * alsamixer as a selection, * but it's shown under the
+        * 'Playback' category.
+        * If I name it 'Capture Source', it shows up in strange
+        * ways (two bools of which one can be selected at a
+        * time) but at least it's shown in the 'Capture'
+        * category.
+        * I was told that this was due to backward compatibility,
+        * but I don't understand then why the mangling is *not*
+        * done when I name it "Input Source".....
+        */
+       .name = "Capture Source",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = onyx_snd_capture_source_info,
+       .get = onyx_snd_capture_source_get,
+       .put = onyx_snd_capture_source_put,
+};
+
+#define onyx_snd_mute_info     snd_ctl_boolean_stereo_info
+
+static int onyx_snd_mute_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       u8 c;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &c);
+       mutex_unlock(&onyx->mutex);
+
+       ucontrol->value.integer.value[0] = !(c & ONYX_MUTE_LEFT);
+       ucontrol->value.integer.value[1] = !(c & ONYX_MUTE_RIGHT);
+
+       return 0;
+}
+
+static int onyx_snd_mute_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       u8 v = 0, c = 0;
+       int err = -EBUSY;
+
+       mutex_lock(&onyx->mutex);
+       if (onyx->analog_locked)
+               goto out_unlock;
+
+       onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
+       c = v;
+       c &= ~(ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT);
+       if (!ucontrol->value.integer.value[0])
+               c |= ONYX_MUTE_LEFT;
+       if (!ucontrol->value.integer.value[1])
+               c |= ONYX_MUTE_RIGHT;
+       err = onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, c);
+
+ out_unlock:
+       mutex_unlock(&onyx->mutex);
+
+       return !err ? (v != c) : err;
+}
+
+static struct snd_kcontrol_new mute_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Master Playback Switch",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = onyx_snd_mute_info,
+       .get = onyx_snd_mute_get,
+       .put = onyx_snd_mute_put,
+};
+
+
+#define onyx_snd_single_bit_info       snd_ctl_boolean_mono_info
+
+#define FLAG_POLARITY_INVERT   1
+#define FLAG_SPDIFLOCK         2
+
+static int onyx_snd_single_bit_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       u8 c;
+       long int pv = kcontrol->private_value;
+       u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
+       u8 address = (pv >> 8) & 0xff;
+       u8 mask = pv & 0xff;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, address, &c);
+       mutex_unlock(&onyx->mutex);
+
+       ucontrol->value.integer.value[0] = !!(c & mask) ^ polarity;
+
+       return 0;
+}
+
+static int onyx_snd_single_bit_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       u8 v = 0, c = 0;
+       int err;
+       long int pv = kcontrol->private_value;
+       u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
+       u8 spdiflock = (pv >> 16) & FLAG_SPDIFLOCK;
+       u8 address = (pv >> 8) & 0xff;
+       u8 mask = pv & 0xff;
+
+       mutex_lock(&onyx->mutex);
+       if (spdiflock && onyx->spdif_locked) {
+               /* even if alsamixer doesn't care.. */
+               err = -EBUSY;
+               goto out_unlock;
+       }
+       onyx_read_register(onyx, address, &v);
+       c = v;
+       c &= ~(mask);
+       if (!!ucontrol->value.integer.value[0] ^ polarity)
+               c |= mask;
+       err = onyx_write_register(onyx, address, c);
+
+ out_unlock:
+       mutex_unlock(&onyx->mutex);
+
+       return !err ? (v != c) : err;
+}
+
+#define SINGLE_BIT(n, type, description, address, mask, flags)         \
+static struct snd_kcontrol_new n##_control = {                         \
+       .iface = SNDRV_CTL_ELEM_IFACE_##type,                           \
+       .name = description,                                            \
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,                      \
+       .info = onyx_snd_single_bit_info,                               \
+       .get = onyx_snd_single_bit_get,                                 \
+       .put = onyx_snd_single_bit_put,                                 \
+       .private_value = (flags << 16) | (address << 8) | mask          \
+}
+
+SINGLE_BIT(spdif,
+          MIXER,
+          SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+          ONYX_REG_DIG_INFO4,
+          ONYX_SPDIF_ENABLE,
+          FLAG_SPDIFLOCK);
+SINGLE_BIT(ovr1,
+          MIXER,
+          "Oversampling Rate",
+          ONYX_REG_DAC_CONTROL,
+          ONYX_OVR1,
+          0);
+SINGLE_BIT(flt0,
+          MIXER,
+          "Fast Digital Filter Rolloff",
+          ONYX_REG_DAC_FILTER,
+          ONYX_ROLLOFF_FAST,
+          FLAG_POLARITY_INVERT);
+SINGLE_BIT(hpf,
+          MIXER,
+          "Highpass Filter",
+          ONYX_REG_ADC_HPF_BYPASS,
+          ONYX_HPF_DISABLE,
+          FLAG_POLARITY_INVERT);
+SINGLE_BIT(dm12,
+          MIXER,
+          "Digital De-Emphasis",
+          ONYX_REG_DAC_DEEMPH,
+          ONYX_DIGDEEMPH_CTRL,
+          0);
+
+static int onyx_spdif_info(struct snd_kcontrol *kcontrol,
+                          struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+       uinfo->count = 1;
+       return 0;
+}
+
+static int onyx_spdif_mask_get(struct snd_kcontrol *kcontrol,
+                              struct snd_ctl_elem_value *ucontrol)
+{
+       /* datasheet page 30, all others are 0 */
+       ucontrol->value.iec958.status[0] = 0x3e;
+       ucontrol->value.iec958.status[1] = 0xff;
+
+       ucontrol->value.iec958.status[3] = 0x3f;
+       ucontrol->value.iec958.status[4] = 0x0f;
+
+       return 0;
+}
+
+static struct snd_kcontrol_new onyx_spdif_mask = {
+       .access =       SNDRV_CTL_ELEM_ACCESS_READ,
+       .iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+       .name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+       .info =         onyx_spdif_info,
+       .get =          onyx_spdif_mask_get,
+};
+
+static int onyx_spdif_get(struct snd_kcontrol *kcontrol,
+                         struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       u8 v;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
+       ucontrol->value.iec958.status[0] = v & 0x3e;
+
+       onyx_read_register(onyx, ONYX_REG_DIG_INFO2, &v);
+       ucontrol->value.iec958.status[1] = v;
+
+       onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
+       ucontrol->value.iec958.status[3] = v & 0x3f;
+
+       onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
+       ucontrol->value.iec958.status[4] = v & 0x0f;
+       mutex_unlock(&onyx->mutex);
+
+       return 0;
+}
+
+static int onyx_spdif_put(struct snd_kcontrol *kcontrol,
+                         struct snd_ctl_elem_value *ucontrol)
+{
+       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+       u8 v;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
+       v = (v & ~0x3e) | (ucontrol->value.iec958.status[0] & 0x3e);
+       onyx_write_register(onyx, ONYX_REG_DIG_INFO1, v);
+
+       v = ucontrol->value.iec958.status[1];
+       onyx_write_register(onyx, ONYX_REG_DIG_INFO2, v);
+
+       onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
+       v = (v & ~0x3f) | (ucontrol->value.iec958.status[3] & 0x3f);
+       onyx_write_register(onyx, ONYX_REG_DIG_INFO3, v);
+
+       onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
+       v = (v & ~0x0f) | (ucontrol->value.iec958.status[4] & 0x0f);
+       onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
+       mutex_unlock(&onyx->mutex);
+
+       return 1;
+}
+
+static struct snd_kcontrol_new onyx_spdif_ctrl = {
+       .access =       SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+       .name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+       .info =         onyx_spdif_info,
+       .get =          onyx_spdif_get,
+       .put =          onyx_spdif_put,
+};
+
+/* our registers */
+
+static u8 register_map[] = {
+       ONYX_REG_DAC_ATTEN_LEFT,
+       ONYX_REG_DAC_ATTEN_RIGHT,
+       ONYX_REG_CONTROL,
+       ONYX_REG_DAC_CONTROL,
+       ONYX_REG_DAC_DEEMPH,
+       ONYX_REG_DAC_FILTER,
+       ONYX_REG_DAC_OUTPHASE,
+       ONYX_REG_ADC_CONTROL,
+       ONYX_REG_ADC_HPF_BYPASS,
+       ONYX_REG_DIG_INFO1,
+       ONYX_REG_DIG_INFO2,
+       ONYX_REG_DIG_INFO3,
+       ONYX_REG_DIG_INFO4
+};
+
+static u8 initial_values[ARRAY_SIZE(register_map)] = {
+       0x80, 0x80, /* muted */
+       ONYX_MRST | ONYX_SRST, /* but handled specially! */
+       ONYX_MUTE_LEFT | ONYX_MUTE_RIGHT,
+       0, /* no deemphasis */
+       ONYX_DAC_FILTER_ALWAYS,
+       ONYX_OUTPHASE_INVERTED,
+       (-1 /*dB*/ + 8) & 0xF, /* line in selected, -1 dB gain*/
+       ONYX_ADC_HPF_ALWAYS,
+       (1<<2), /* pcm audio */
+       2,      /* category: pcm coder */
+       0,      /* sampling frequency 44.1 kHz, clock accuracy level II */
+       1       /* 24 bit depth */
+};
+
+/* reset registers of chip, either to initial or to previous values */
+static int onyx_register_init(struct onyx *onyx)
+{
+       int i;
+       u8 val;
+       u8 regs[sizeof(initial_values)];
+
+       if (!onyx->initialised) {
+               memcpy(regs, initial_values, sizeof(initial_values));
+               if (onyx_read_register(onyx, ONYX_REG_CONTROL, &val))
+                       return -1;
+               val &= ~ONYX_SILICONVERSION;
+               val |= initial_values[3];
+               regs[3] = val;
+       } else {
+               for (i=0; i<sizeof(register_map); i++)
+                       regs[i] = onyx->cache[register_map[i]-FIRSTREGISTER];
+       }
+
+       for (i=0; i<sizeof(register_map); i++) {
+               if (onyx_write_register(onyx, register_map[i], regs[i]))
+                       return -1;
+       }
+       onyx->initialised = 1;
+       return 0;
+}
+
+static struct transfer_info onyx_transfers[] = {
+       /* this is first so we can skip it if no input is present...
+        * No hardware exists with that, but it's here as an example
+        * of what to do :) */
+       {
+               /* analog input */
+               .formats = SNDRV_PCM_FMTBIT_S8 |
+                          SNDRV_PCM_FMTBIT_S16_BE |
+                          SNDRV_PCM_FMTBIT_S24_BE,
+               .rates = SNDRV_PCM_RATE_8000_96000,
+               .transfer_in = 1,
+               .must_be_clock_source = 0,
+               .tag = 0,
+       },
+       {
+               /* if analog and digital are currently off, anything should go,
+                * so this entry describes everything we can do... */
+               .formats = SNDRV_PCM_FMTBIT_S8 |
+                          SNDRV_PCM_FMTBIT_S16_BE |
+                          SNDRV_PCM_FMTBIT_S24_BE
+#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
+                          | SNDRV_PCM_FMTBIT_COMPRESSED_16BE
+#endif
+               ,
+               .rates = SNDRV_PCM_RATE_8000_96000,
+               .tag = 0,
+       },
+       {
+               /* analog output */
+               .formats = SNDRV_PCM_FMTBIT_S8 |
+                          SNDRV_PCM_FMTBIT_S16_BE |
+                          SNDRV_PCM_FMTBIT_S24_BE,
+               .rates = SNDRV_PCM_RATE_8000_96000,
+               .transfer_in = 0,
+               .must_be_clock_source = 0,
+               .tag = 1,
+       },
+       {
+               /* digital pcm output, also possible for analog out */
+               .formats = SNDRV_PCM_FMTBIT_S8 |
+                          SNDRV_PCM_FMTBIT_S16_BE |
+                          SNDRV_PCM_FMTBIT_S24_BE,
+               .rates = SNDRV_PCM_RATE_32000 |
+                        SNDRV_PCM_RATE_44100 |
+                        SNDRV_PCM_RATE_48000,
+               .transfer_in = 0,
+               .must_be_clock_source = 0,
+               .tag = 2,
+       },
+#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
+       /* Once alsa gets supports for this kind of thing we can add it... */
+       {
+               /* digital compressed output */
+               .formats =  SNDRV_PCM_FMTBIT_COMPRESSED_16BE,
+               .rates = SNDRV_PCM_RATE_32000 |
+                        SNDRV_PCM_RATE_44100 |
+                        SNDRV_PCM_RATE_48000,
+               .tag = 2,
+       },
+#endif
+       {}
+};
+
+static int onyx_usable(struct codec_info_item *cii,
+                      struct transfer_info *ti,
+                      struct transfer_info *out)
+{
+       u8 v;
+       struct onyx *onyx = cii->codec_data;
+       int spdif_enabled, analog_enabled;
+
+       mutex_lock(&onyx->mutex);
+       onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
+       spdif_enabled = !!(v & ONYX_SPDIF_ENABLE);
+       onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
+       analog_enabled =
+               (v & (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT))
+                != (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT);
+       mutex_unlock(&onyx->mutex);
+
+       switch (ti->tag) {
+       case 0: return 1;
+       case 1: return analog_enabled;
+       case 2: return spdif_enabled;
+       }
+       return 1;
+}
+
+static int onyx_prepare(struct codec_info_item *cii,
+                       struct bus_info *bi,
+                       struct snd_pcm_substream *substream)
+{
+       u8 v;
+       struct onyx *onyx = cii->codec_data;
+       int err = -EBUSY;
+
+       mutex_lock(&onyx->mutex);
+
+#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
+       if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) {
+               /* mute and lock analog output */
+               onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
+               if (onyx_write_register(onyx,
+                                       ONYX_REG_DAC_CONTROL,
+                                       v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT))
+                       goto out_unlock;
+               onyx->analog_locked = 1;
+               err = 0;
+               goto out_unlock;
+       }
+#endif
+       switch (substream->runtime->rate) {
+       case 32000:
+       case 44100:
+       case 48000:
+               /* these rates are ok for all outputs */
+               /* FIXME: program spdif channel control bits here so that
+                *        userspace doesn't have to if it only plays pcm! */
+               err = 0;
+               goto out_unlock;
+       default:
+               /* got some rate that the digital output can't do,
+                * so disable and lock it */
+               onyx_read_register(cii->codec_data, ONYX_REG_DIG_INFO4, &v);
+               if (onyx_write_register(onyx,
+                                       ONYX_REG_DIG_INFO4,
+                                       v & ~ONYX_SPDIF_ENABLE))
+                       goto out_unlock;
+               onyx->spdif_locked = 1;
+               err = 0;
+               goto out_unlock;
+       }
+
+ out_unlock:
+       mutex_unlock(&onyx->mutex);
+
+       return err;
+}
+
+static int onyx_open(struct codec_info_item *cii,
+                    struct snd_pcm_substream *substream)
+{
+       struct onyx *onyx = cii->codec_data;
+
+       mutex_lock(&onyx->mutex);
+       onyx->open_count++;
+       mutex_unlock(&onyx->mutex);
+
+       return 0;
+}
+
+static int onyx_close(struct codec_info_item *cii,
+                     struct snd_pcm_substream *substream)
+{
+       struct onyx *onyx = cii->codec_data;
+
+       mutex_lock(&onyx->mutex);
+       onyx->open_count--;
+       if (!onyx->open_count)
+               onyx->spdif_locked = onyx->analog_locked = 0;
+       mutex_unlock(&onyx->mutex);
+
+       return 0;
+}
+
+static int onyx_switch_clock(struct codec_info_item *cii,
+                            enum clock_switch what)
+{
+       struct onyx *onyx = cii->codec_data;
+
+       mutex_lock(&onyx->mutex);
+       /* this *MUST* be more elaborate later... */
+       switch (what) {
+       case CLOCK_SWITCH_PREPARE_SLAVE:
+               onyx->codec.gpio->methods->all_amps_off(onyx->codec.gpio);
+               break;
+       case CLOCK_SWITCH_SLAVE:
+               onyx->codec.gpio->methods->all_amps_restore(onyx->codec.gpio);
+               break;
+       default: /* silence warning */
+               break;
+       }
+       mutex_unlock(&onyx->mutex);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int onyx_suspend(struct codec_info_item *cii, pm_message_t state)
+{
+       struct onyx *onyx = cii->codec_data;
+       u8 v;
+       int err = -ENXIO;
+
+       mutex_lock(&onyx->mutex);
+       if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v))
+               goto out_unlock;
+       onyx_write_register(onyx, ONYX_REG_CONTROL, v | ONYX_ADPSV | ONYX_DAPSV);
+       /* Apple does a sleep here but the datasheet says to do it on resume */
+       err = 0;
+ out_unlock:
+       mutex_unlock(&onyx->mutex);
+
+       return err;
+}
+
+static int onyx_resume(struct codec_info_item *cii)
+{
+       struct onyx *onyx = cii->codec_data;
+       u8 v;
+       int err = -ENXIO;
+
+       mutex_lock(&onyx->mutex);
+
+       /* reset codec */
+       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
+       msleep(1);
+       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
+       msleep(1);
+       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
+       msleep(1);
+
+       /* take codec out of suspend (if it still is after reset) */
+       if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v))
+               goto out_unlock;
+       onyx_write_register(onyx, ONYX_REG_CONTROL, v & ~(ONYX_ADPSV | ONYX_DAPSV));
+       /* FIXME: should divide by sample rate, but 8k is the lowest we go */
+       msleep(2205000/8000);
+       /* reset all values */
+       onyx_register_init(onyx);
+       err = 0;
+ out_unlock:
+       mutex_unlock(&onyx->mutex);
+
+       return err;
+}
+
+#endif /* CONFIG_PM */
+
+static struct codec_info onyx_codec_info = {
+       .transfers = onyx_transfers,
+       .sysclock_factor = 256,
+       .bus_factor = 64,
+       .owner = THIS_MODULE,
+       .usable = onyx_usable,
+       .prepare = onyx_prepare,
+       .open = onyx_open,
+       .close = onyx_close,
+       .switch_clock = onyx_switch_clock,
+#ifdef CONFIG_PM
+       .suspend = onyx_suspend,
+       .resume = onyx_resume,
+#endif
+};
+
+static int onyx_init_codec(struct aoa_codec *codec)
+{
+       struct onyx *onyx = codec_to_onyx(codec);
+       struct snd_kcontrol *ctl;
+       struct codec_info *ci = &onyx_codec_info;
+       u8 v;
+       int err;
+
+       if (!onyx->codec.gpio || !onyx->codec.gpio->methods) {
+               printk(KERN_ERR PFX "gpios not assigned!!\n");
+               return -EINVAL;
+       }
+
+       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
+       msleep(1);
+       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
+       msleep(1);
+       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
+       msleep(1);
+
+       if (onyx_register_init(onyx)) {
+               printk(KERN_ERR PFX "failed to initialise onyx registers\n");
+               return -ENODEV;
+       }
+
+       if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, onyx, &ops)) {
+               printk(KERN_ERR PFX "failed to create onyx snd device!\n");
+               return -ENODEV;
+       }
+
+       /* nothing connected? what a joke! */
+       if ((onyx->codec.connected & 0xF) == 0)
+               return -ENOTCONN;
+
+       /* if no inputs are present... */
+       if ((onyx->codec.connected & 0xC) == 0) {
+               if (!onyx->codec_info)
+                       onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL);
+               if (!onyx->codec_info)
+                       return -ENOMEM;
+               ci = onyx->codec_info;
+               *ci = onyx_codec_info;
+               ci->transfers++;
+       }
+
+       /* if no outputs are present... */
+       if ((onyx->codec.connected & 3) == 0) {
+               if (!onyx->codec_info)
+                       onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL);
+               if (!onyx->codec_info)
+                       return -ENOMEM;
+               ci = onyx->codec_info;
+               /* this is fine as there have to be inputs
+                * if we end up in this part of the code */
+               *ci = onyx_codec_info;
+               ci->transfers[1].formats = 0;
+       }
+
+       if (onyx->codec.soundbus_dev->attach_codec(onyx->codec.soundbus_dev,
+                                                  aoa_get_card(),
+                                                  ci, onyx)) {
+               printk(KERN_ERR PFX "error creating onyx pcm\n");
+               return -ENODEV;
+       }
+#define ADDCTL(n)                                                      \
+       do {                                                            \
+               ctl = snd_ctl_new1(&n, onyx);                           \
+               if (ctl) {                                              \
+                       ctl->id.device =                                \
+                               onyx->codec.soundbus_dev->pcm->device;  \
+                       err = aoa_snd_ctl_add(ctl);                     \
+                       if (err)                                        \
+                               goto error;                             \
+               }                                                       \
+       } while (0)
+
+       if (onyx->codec.soundbus_dev->pcm) {
+               /* give the user appropriate controls
+                * depending on what inputs are connected */
+               if ((onyx->codec.connected & 0xC) == 0xC)
+                       ADDCTL(capture_source_control);
+               else if (onyx->codec.connected & 4)
+                       onyx_set_capture_source(onyx, 0);
+               else
+                       onyx_set_capture_source(onyx, 1);
+               if (onyx->codec.connected & 0xC)
+                       ADDCTL(inputgain_control);
+
+               /* depending on what output is connected,
+                * give the user appropriate controls */
+               if (onyx->codec.connected & 1) {
+                       ADDCTL(volume_control);
+                       ADDCTL(mute_control);
+                       ADDCTL(ovr1_control);
+                       ADDCTL(flt0_control);
+                       ADDCTL(hpf_control);
+                       ADDCTL(dm12_control);
+                       /* spdif control defaults to off */
+               }
+               if (onyx->codec.connected & 2) {
+                       ADDCTL(onyx_spdif_mask);
+                       ADDCTL(onyx_spdif_ctrl);
+               }
+               if ((onyx->codec.connected & 3) == 3)
+                       ADDCTL(spdif_control);
+               /* if only S/PDIF is connected, enable it unconditionally */
+               if ((onyx->codec.connected & 3) == 2) {
+                       onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
+                       v |= ONYX_SPDIF_ENABLE;
+                       onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
+               }
+       }
+#undef ADDCTL
+       printk(KERN_INFO PFX "attached to onyx codec via i2c\n");
+
+       return 0;
+ error:
+       onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
+       snd_device_free(aoa_get_card(), onyx);
+       return err;
+}
+
+static void onyx_exit_codec(struct aoa_codec *codec)
+{
+       struct onyx *onyx = codec_to_onyx(codec);
+
+       if (!onyx->codec.soundbus_dev) {
+               printk(KERN_ERR PFX "onyx_exit_codec called without soundbus_dev!\n");
+               return;
+       }
+       onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
+}
+
+static struct i2c_driver onyx_driver;
+
+static int onyx_create(struct i2c_adapter *adapter,
+                      struct device_node *node,
+                      int addr)
+{
+       struct onyx *onyx;
+       u8 dummy;
+
+       onyx = kzalloc(sizeof(struct onyx), GFP_KERNEL);
+
+       if (!onyx)
+               return -ENOMEM;
+
+       mutex_init(&onyx->mutex);
+       onyx->i2c.driver = &onyx_driver;
+       onyx->i2c.adapter = adapter;
+       onyx->i2c.addr = addr & 0x7f;
+       strlcpy(onyx->i2c.name, "onyx audio codec", I2C_NAME_SIZE);
+
+       if (i2c_attach_client(&onyx->i2c)) {
+               printk(KERN_ERR PFX "failed to attach to i2c\n");
+               goto fail;
+       }
+
+       /* we try to read from register ONYX_REG_CONTROL
+        * to check if the codec is present */
+       if (onyx_read_register(onyx, ONYX_REG_CONTROL, &dummy) != 0) {
+               i2c_detach_client(&onyx->i2c);
+               printk(KERN_ERR PFX "failed to read control register\n");
+               goto fail;
+       }
+
+       strlcpy(onyx->codec.name, "onyx", MAX_CODEC_NAME_LEN);
+       onyx->codec.owner = THIS_MODULE;
+       onyx->codec.init = onyx_init_codec;
+       onyx->codec.exit = onyx_exit_codec;
+       onyx->codec.node = of_node_get(node);
+
+       if (aoa_codec_register(&onyx->codec)) {
+               i2c_detach_client(&onyx->i2c);
+               goto fail;
+       }
+       printk(KERN_DEBUG PFX "created and attached onyx instance\n");
+       return 0;
+ fail:
+       kfree(onyx);
+       return -EINVAL;
+}
+
+static int onyx_i2c_attach(struct i2c_adapter *adapter)
+{
+       struct device_node *busnode, *dev = NULL;
+       struct pmac_i2c_bus *bus;
+
+       bus = pmac_i2c_adapter_to_bus(adapter);
+       if (bus == NULL)
+               return -ENODEV;
+       busnode = pmac_i2c_get_bus_node(bus);
+
+       while ((dev = of_get_next_child(busnode, dev)) != NULL) {
+               if (of_device_is_compatible(dev, "pcm3052")) {
+                       const u32 *addr;
+                       printk(KERN_DEBUG PFX "found pcm3052\n");
+                       addr = of_get_property(dev, "reg", NULL);
+                       if (!addr)
+                               return -ENODEV;
+                       return onyx_create(adapter, dev, (*addr)>>1);
+               }
+       }
+
+       /* if that didn't work, try desperate mode for older
+        * machines that have stuff missing from the device tree */
+
+       if (!of_device_is_compatible(busnode, "k2-i2c"))
+               return -ENODEV;
+
+       printk(KERN_DEBUG PFX "found k2-i2c, checking if onyx chip is on it\n");
+       /* probe both possible addresses for the onyx chip */
+       if (onyx_create(adapter, NULL, 0x46) == 0)
+               return 0;
+       return onyx_create(adapter, NULL, 0x47);
+}
+
+static int onyx_i2c_detach(struct i2c_client *client)
+{
+       struct onyx *onyx = container_of(client, struct onyx, i2c);
+       int err;
+
+       if ((err = i2c_detach_client(client)))
+               return err;
+       aoa_codec_unregister(&onyx->codec);
+       of_node_put(onyx->codec.node);
+       if (onyx->codec_info)
+               kfree(onyx->codec_info);
+       kfree(onyx);
+       return 0;
+}
+
+static struct i2c_driver onyx_driver = {
+       .driver = {
+               .name = "aoa_codec_onyx",
+               .owner = THIS_MODULE,
+       },
+       .attach_adapter = onyx_i2c_attach,
+       .detach_client = onyx_i2c_detach,
+};
+
+static int __init onyx_init(void)
+{
+       return i2c_add_driver(&onyx_driver);
+}
+
+static void __exit onyx_exit(void)
+{
+       i2c_del_driver(&onyx_driver);
+}
+
+module_init(onyx_init);
+module_exit(onyx_exit);
diff --git a/sound/aoa/codecs/onyx.h b/sound/aoa/codecs/onyx.h
new file mode 100644 (file)
index 0000000..ffd2025
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Apple Onboard Audio driver for Onyx codec (header)
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#ifndef __SND_AOA_CODEC_ONYX_H
+#define __SND_AOA_CODEC_ONYX_H
+#include <stddef.h>
+#include <linux/i2c.h>
+#include <asm/pmac_low_i2c.h>
+#include <asm/prom.h>
+
+/* PCM3052 register definitions */
+
+/* the attenuation registers take values from
+ * -1 (0dB) to -127 (-63.0 dB) or others (muted) */
+#define ONYX_REG_DAC_ATTEN_LEFT                65
+#define FIRSTREGISTER                  ONYX_REG_DAC_ATTEN_LEFT
+#define ONYX_REG_DAC_ATTEN_RIGHT       66
+
+#define ONYX_REG_CONTROL               67
+#      define ONYX_MRST                (1<<7)
+#      define ONYX_SRST                (1<<6)
+#      define ONYX_ADPSV               (1<<5)
+#      define ONYX_DAPSV               (1<<4)
+#      define ONYX_SILICONVERSION      (1<<0)
+/* all others reserved */
+
+#define ONYX_REG_DAC_CONTROL           68
+#      define ONYX_OVR1                (1<<6)
+#      define ONYX_MUTE_RIGHT          (1<<1)
+#      define ONYX_MUTE_LEFT           (1<<0)
+
+#define ONYX_REG_DAC_DEEMPH            69
+#      define ONYX_DIGDEEMPH_SHIFT     5
+#      define ONYX_DIGDEEMPH_MASK      (3<<ONYX_DIGDEEMPH_SHIFT)
+#      define ONYX_DIGDEEMPH_CTRL      (1<<4)
+
+#define ONYX_REG_DAC_FILTER            70
+#      define ONYX_ROLLOFF_FAST        (1<<5)
+#      define ONYX_DAC_FILTER_ALWAYS   (1<<2)
+
+#define        ONYX_REG_DAC_OUTPHASE           71
+#      define ONYX_OUTPHASE_INVERTED   (1<<0)
+
+#define ONYX_REG_ADC_CONTROL           72
+#      define ONYX_ADC_INPUT_MIC       (1<<5)
+/* 8 + input gain in dB, valid range for input gain is -4 .. 20 dB */
+#      define ONYX_ADC_PGA_GAIN_MASK   0x1f
+
+#define ONYX_REG_ADC_HPF_BYPASS                75
+#      define ONYX_HPF_DISABLE         (1<<3)
+#      define ONYX_ADC_HPF_ALWAYS      (1<<2)
+
+#define ONYX_REG_DIG_INFO1             77
+#      define ONYX_MASK_DIN_TO_BPZ     (1<<7)
+/* bits 1-5 control channel bits 1-5 */
+#      define ONYX_DIGOUT_DISABLE      (1<<0)
+
+#define ONYX_REG_DIG_INFO2             78
+/* controls channel bits 8-15 */
+
+#define ONYX_REG_DIG_INFO3             79
+/* control channel bits 24-29, high 2 bits reserved */
+
+#define ONYX_REG_DIG_INFO4             80
+#      define ONYX_VALIDL              (1<<7)
+#      define ONYX_VALIDR              (1<<6)
+#      define ONYX_SPDIF_ENABLE        (1<<5)
+/* lower 4 bits control bits 32-35 of channel control and word length */
+#      define ONYX_WORDLEN_MASK        (0xF)
+
+#endif /* __SND_AOA_CODEC_ONYX_H */
diff --git a/sound/aoa/codecs/snd-aoa-codec-onyx.c b/sound/aoa/codecs/snd-aoa-codec-onyx.c
deleted file mode 100644 (file)
index 6a3837d..0000000
+++ /dev/null
@@ -1,1118 +0,0 @@
-/*
- * Apple Onboard Audio driver for Onyx codec
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- *
- *
- * This is a driver for the pcm3052 codec chip (codenamed Onyx)
- * that is present in newer Apple hardware (with digital output).
- *
- * The Onyx codec has the following connections (listed by the bit
- * to be used in aoa_codec.connected):
- *  0: analog output
- *  1: digital output
- *  2: line input
- *  3: microphone input
- * Note that even though I know of no machine that has for example
- * the digital output connected but not the analog, I have handled
- * all the different cases in the code so that this driver may serve
- * as a good example of what to do.
- *
- * NOTE: This driver assumes that there's at most one chip to be
- *      used with one alsa card, in form of creating all kinds
- *      of mixer elements without regard for their existence.
- *      But snd-aoa assumes that there's at most one card, so
- *      this means you can only have one onyx on a system. This
- *      should probably be fixed by changing the assumption of
- *      having just a single card on a system, and making the
- *      'card' pointer accessible to anyone who needs it instead
- *      of hiding it in the aoa_snd_* functions...
- *
- */
-#include <linux/delay.h>
-#include <linux/module.h>
-MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");
-
-#include "snd-aoa-codec-onyx.h"
-#include "../aoa.h"
-#include "../soundbus/soundbus.h"
-
-
-#define PFX "snd-aoa-codec-onyx: "
-
-struct onyx {
-       /* cache registers 65 to 80, they are write-only! */
-       u8                      cache[16];
-       struct i2c_client       i2c;
-       struct aoa_codec        codec;
-       u32                     initialised:1,
-                               spdif_locked:1,
-                               analog_locked:1,
-                               original_mute:2;
-       int                     open_count;
-       struct codec_info       *codec_info;
-
-       /* mutex serializes concurrent access to the device
-        * and this structure.
-        */
-       struct mutex mutex;
-};
-#define codec_to_onyx(c) container_of(c, struct onyx, codec)
-
-/* both return 0 if all ok, else on error */
-static int onyx_read_register(struct onyx *onyx, u8 reg, u8 *value)
-{
-       s32 v;
-
-       if (reg != ONYX_REG_CONTROL) {
-               *value = onyx->cache[reg-FIRSTREGISTER];
-               return 0;
-       }
-       v = i2c_smbus_read_byte_data(&onyx->i2c, reg);
-       if (v < 0)
-               return -1;
-       *value = (u8)v;
-       onyx->cache[ONYX_REG_CONTROL-FIRSTREGISTER] = *value;
-       return 0;
-}
-
-static int onyx_write_register(struct onyx *onyx, u8 reg, u8 value)
-{
-       int result;
-
-       result = i2c_smbus_write_byte_data(&onyx->i2c, reg, value);
-       if (!result)
-               onyx->cache[reg-FIRSTREGISTER] = value;
-       return result;
-}
-
-/* alsa stuff */
-
-static int onyx_dev_register(struct snd_device *dev)
-{
-       return 0;
-}
-
-static struct snd_device_ops ops = {
-       .dev_register = onyx_dev_register,
-};
-
-/* this is necessary because most alsa mixer programs
- * can't properly handle the negative range */
-#define VOLUME_RANGE_SHIFT     128
-
-static int onyx_snd_vol_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-       uinfo->count = 2;
-       uinfo->value.integer.min = -128 + VOLUME_RANGE_SHIFT;
-       uinfo->value.integer.max = -1 + VOLUME_RANGE_SHIFT;
-       return 0;
-}
-
-static int onyx_snd_vol_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       s8 l, r;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
-       onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
-       mutex_unlock(&onyx->mutex);
-
-       ucontrol->value.integer.value[0] = l + VOLUME_RANGE_SHIFT;
-       ucontrol->value.integer.value[1] = r + VOLUME_RANGE_SHIFT;
-
-       return 0;
-}
-
-static int onyx_snd_vol_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       s8 l, r;
-
-       if (ucontrol->value.integer.value[0] < -128 + VOLUME_RANGE_SHIFT ||
-           ucontrol->value.integer.value[0] > -1 + VOLUME_RANGE_SHIFT)
-               return -EINVAL;
-       if (ucontrol->value.integer.value[1] < -128 + VOLUME_RANGE_SHIFT ||
-           ucontrol->value.integer.value[1] > -1 + VOLUME_RANGE_SHIFT)
-               return -EINVAL;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
-       onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
-
-       if (l + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[0] &&
-           r + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[1]) {
-               mutex_unlock(&onyx->mutex);
-               return 0;
-       }
-
-       onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_LEFT,
-                           ucontrol->value.integer.value[0]
-                            - VOLUME_RANGE_SHIFT);
-       onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT,
-                           ucontrol->value.integer.value[1]
-                            - VOLUME_RANGE_SHIFT);
-       mutex_unlock(&onyx->mutex);
-
-       return 1;
-}
-
-static struct snd_kcontrol_new volume_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Master Playback Volume",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = onyx_snd_vol_info,
-       .get = onyx_snd_vol_get,
-       .put = onyx_snd_vol_put,
-};
-
-/* like above, this is necessary because a lot
- * of alsa mixer programs don't handle ranges
- * that don't start at 0 properly.
- * even alsamixer is one of them... */
-#define INPUTGAIN_RANGE_SHIFT  (-3)
-
-static int onyx_snd_inputgain_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-       uinfo->count = 1;
-       uinfo->value.integer.min = 3 + INPUTGAIN_RANGE_SHIFT;
-       uinfo->value.integer.max = 28 + INPUTGAIN_RANGE_SHIFT;
-       return 0;
-}
-
-static int onyx_snd_inputgain_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       u8 ig;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &ig);
-       mutex_unlock(&onyx->mutex);
-
-       ucontrol->value.integer.value[0] =
-               (ig & ONYX_ADC_PGA_GAIN_MASK) + INPUTGAIN_RANGE_SHIFT;
-
-       return 0;
-}
-
-static int onyx_snd_inputgain_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       u8 v, n;
-
-       if (ucontrol->value.integer.value[0] < 3 + INPUTGAIN_RANGE_SHIFT ||
-           ucontrol->value.integer.value[0] > 28 + INPUTGAIN_RANGE_SHIFT)
-               return -EINVAL;
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
-       n = v;
-       n &= ~ONYX_ADC_PGA_GAIN_MASK;
-       n |= (ucontrol->value.integer.value[0] - INPUTGAIN_RANGE_SHIFT)
-               & ONYX_ADC_PGA_GAIN_MASK;
-       onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, n);
-       mutex_unlock(&onyx->mutex);
-
-       return n != v;
-}
-
-static struct snd_kcontrol_new inputgain_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Master Capture Volume",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = onyx_snd_inputgain_info,
-       .get = onyx_snd_inputgain_get,
-       .put = onyx_snd_inputgain_put,
-};
-
-static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       static char *texts[] = { "Line-In", "Microphone" };
-
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
-       uinfo->count = 1;
-       uinfo->value.enumerated.items = 2;
-       if (uinfo->value.enumerated.item > 1)
-               uinfo->value.enumerated.item = 1;
-       strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
-       return 0;
-}
-
-static int onyx_snd_capture_source_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       s8 v;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
-       mutex_unlock(&onyx->mutex);
-
-       ucontrol->value.enumerated.item[0] = !!(v&ONYX_ADC_INPUT_MIC);
-
-       return 0;
-}
-
-static void onyx_set_capture_source(struct onyx *onyx, int mic)
-{
-       s8 v;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
-       v &= ~ONYX_ADC_INPUT_MIC;
-       if (mic)
-               v |= ONYX_ADC_INPUT_MIC;
-       onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, v);
-       mutex_unlock(&onyx->mutex);
-}
-
-static int onyx_snd_capture_source_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       if (ucontrol->value.enumerated.item[0] > 1)
-               return -EINVAL;
-       onyx_set_capture_source(snd_kcontrol_chip(kcontrol),
-                               ucontrol->value.enumerated.item[0]);
-       return 1;
-}
-
-static struct snd_kcontrol_new capture_source_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       /* If we name this 'Input Source', it properly shows up in
-        * alsamixer as a selection, * but it's shown under the 
-        * 'Playback' category.
-        * If I name it 'Capture Source', it shows up in strange
-        * ways (two bools of which one can be selected at a
-        * time) but at least it's shown in the 'Capture'
-        * category.
-        * I was told that this was due to backward compatibility,
-        * but I don't understand then why the mangling is *not*
-        * done when I name it "Input Source".....
-        */
-       .name = "Capture Source",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = onyx_snd_capture_source_info,
-       .get = onyx_snd_capture_source_get,
-       .put = onyx_snd_capture_source_put,
-};
-
-#define onyx_snd_mute_info     snd_ctl_boolean_stereo_info
-
-static int onyx_snd_mute_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       u8 c;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &c);
-       mutex_unlock(&onyx->mutex);
-
-       ucontrol->value.integer.value[0] = !(c & ONYX_MUTE_LEFT);
-       ucontrol->value.integer.value[1] = !(c & ONYX_MUTE_RIGHT);
-
-       return 0;
-}
-
-static int onyx_snd_mute_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       u8 v = 0, c = 0;
-       int err = -EBUSY;
-
-       mutex_lock(&onyx->mutex);
-       if (onyx->analog_locked)
-               goto out_unlock;
-
-       onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
-       c = v;
-       c &= ~(ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT);
-       if (!ucontrol->value.integer.value[0])
-               c |= ONYX_MUTE_LEFT;
-       if (!ucontrol->value.integer.value[1])
-               c |= ONYX_MUTE_RIGHT;
-       err = onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, c);
-
- out_unlock:
-       mutex_unlock(&onyx->mutex);
-
-       return !err ? (v != c) : err;
-}
-
-static struct snd_kcontrol_new mute_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Master Playback Switch",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = onyx_snd_mute_info,
-       .get = onyx_snd_mute_get,
-       .put = onyx_snd_mute_put,
-};
-
-
-#define onyx_snd_single_bit_info       snd_ctl_boolean_mono_info
-
-#define FLAG_POLARITY_INVERT   1
-#define FLAG_SPDIFLOCK         2
-
-static int onyx_snd_single_bit_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       u8 c;
-       long int pv = kcontrol->private_value;
-       u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
-       u8 address = (pv >> 8) & 0xff;
-       u8 mask = pv & 0xff;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, address, &c);
-       mutex_unlock(&onyx->mutex);
-
-       ucontrol->value.integer.value[0] = !!(c & mask) ^ polarity;
-
-       return 0;
-}
-
-static int onyx_snd_single_bit_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       u8 v = 0, c = 0;
-       int err;
-       long int pv = kcontrol->private_value;
-       u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
-       u8 spdiflock = (pv >> 16) & FLAG_SPDIFLOCK;
-       u8 address = (pv >> 8) & 0xff;
-       u8 mask = pv & 0xff;
-
-       mutex_lock(&onyx->mutex);
-       if (spdiflock && onyx->spdif_locked) {
-               /* even if alsamixer doesn't care.. */
-               err = -EBUSY;
-               goto out_unlock;
-       }
-       onyx_read_register(onyx, address, &v);
-       c = v;
-       c &= ~(mask);
-       if (!!ucontrol->value.integer.value[0] ^ polarity)
-               c |= mask;
-       err = onyx_write_register(onyx, address, c);
-
- out_unlock:
-       mutex_unlock(&onyx->mutex);
-
-       return !err ? (v != c) : err;
-}
-
-#define SINGLE_BIT(n, type, description, address, mask, flags)         \
-static struct snd_kcontrol_new n##_control = {                         \
-       .iface = SNDRV_CTL_ELEM_IFACE_##type,                           \
-       .name = description,                                            \
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,                      \
-       .info = onyx_snd_single_bit_info,                               \
-       .get = onyx_snd_single_bit_get,                                 \
-       .put = onyx_snd_single_bit_put,                                 \
-       .private_value = (flags << 16) | (address << 8) | mask          \
-}
-
-SINGLE_BIT(spdif,
-          MIXER,
-          SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
-          ONYX_REG_DIG_INFO4,
-          ONYX_SPDIF_ENABLE,
-          FLAG_SPDIFLOCK);
-SINGLE_BIT(ovr1,
-          MIXER,
-          "Oversampling Rate",
-          ONYX_REG_DAC_CONTROL,
-          ONYX_OVR1,
-          0);
-SINGLE_BIT(flt0,
-          MIXER,
-          "Fast Digital Filter Rolloff",
-          ONYX_REG_DAC_FILTER,
-          ONYX_ROLLOFF_FAST,
-          FLAG_POLARITY_INVERT);
-SINGLE_BIT(hpf,
-          MIXER,
-          "Highpass Filter",
-          ONYX_REG_ADC_HPF_BYPASS,
-          ONYX_HPF_DISABLE,
-          FLAG_POLARITY_INVERT);
-SINGLE_BIT(dm12,
-          MIXER,
-          "Digital De-Emphasis",
-          ONYX_REG_DAC_DEEMPH,
-          ONYX_DIGDEEMPH_CTRL,
-          0);
-
-static int onyx_spdif_info(struct snd_kcontrol *kcontrol,
-                          struct snd_ctl_elem_info *uinfo)
-{
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
-       uinfo->count = 1;
-       return 0;
-}
-
-static int onyx_spdif_mask_get(struct snd_kcontrol *kcontrol,
-                              struct snd_ctl_elem_value *ucontrol)
-{
-       /* datasheet page 30, all others are 0 */
-       ucontrol->value.iec958.status[0] = 0x3e;
-       ucontrol->value.iec958.status[1] = 0xff;
-
-       ucontrol->value.iec958.status[3] = 0x3f;
-       ucontrol->value.iec958.status[4] = 0x0f;
-       
-       return 0;
-}
-
-static struct snd_kcontrol_new onyx_spdif_mask = {
-       .access =       SNDRV_CTL_ELEM_ACCESS_READ,
-       .iface =        SNDRV_CTL_ELEM_IFACE_PCM,
-       .name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
-       .info =         onyx_spdif_info,
-       .get =          onyx_spdif_mask_get,
-};
-
-static int onyx_spdif_get(struct snd_kcontrol *kcontrol,
-                         struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       u8 v;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
-       ucontrol->value.iec958.status[0] = v & 0x3e;
-
-       onyx_read_register(onyx, ONYX_REG_DIG_INFO2, &v);
-       ucontrol->value.iec958.status[1] = v;
-
-       onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
-       ucontrol->value.iec958.status[3] = v & 0x3f;
-
-       onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
-       ucontrol->value.iec958.status[4] = v & 0x0f;
-       mutex_unlock(&onyx->mutex);
-
-       return 0;
-}
-
-static int onyx_spdif_put(struct snd_kcontrol *kcontrol,
-                         struct snd_ctl_elem_value *ucontrol)
-{
-       struct onyx *onyx = snd_kcontrol_chip(kcontrol);
-       u8 v;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
-       v = (v & ~0x3e) | (ucontrol->value.iec958.status[0] & 0x3e);
-       onyx_write_register(onyx, ONYX_REG_DIG_INFO1, v);
-
-       v = ucontrol->value.iec958.status[1];
-       onyx_write_register(onyx, ONYX_REG_DIG_INFO2, v);
-
-       onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
-       v = (v & ~0x3f) | (ucontrol->value.iec958.status[3] & 0x3f);
-       onyx_write_register(onyx, ONYX_REG_DIG_INFO3, v);
-
-       onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
-       v = (v & ~0x0f) | (ucontrol->value.iec958.status[4] & 0x0f);
-       onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
-       mutex_unlock(&onyx->mutex);
-
-       return 1;
-}
-
-static struct snd_kcontrol_new onyx_spdif_ctrl = {
-       .access =       SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .iface =        SNDRV_CTL_ELEM_IFACE_PCM,
-       .name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
-       .info =         onyx_spdif_info,
-       .get =          onyx_spdif_get,
-       .put =          onyx_spdif_put,
-};
-
-/* our registers */
-
-static u8 register_map[] = {
-       ONYX_REG_DAC_ATTEN_LEFT,
-       ONYX_REG_DAC_ATTEN_RIGHT,
-       ONYX_REG_CONTROL,
-       ONYX_REG_DAC_CONTROL,
-       ONYX_REG_DAC_DEEMPH,
-       ONYX_REG_DAC_FILTER,
-       ONYX_REG_DAC_OUTPHASE,
-       ONYX_REG_ADC_CONTROL,
-       ONYX_REG_ADC_HPF_BYPASS,
-       ONYX_REG_DIG_INFO1,
-       ONYX_REG_DIG_INFO2,
-       ONYX_REG_DIG_INFO3,
-       ONYX_REG_DIG_INFO4
-};
-
-static u8 initial_values[ARRAY_SIZE(register_map)] = {
-       0x80, 0x80, /* muted */
-       ONYX_MRST | ONYX_SRST, /* but handled specially! */
-       ONYX_MUTE_LEFT | ONYX_MUTE_RIGHT,
-       0, /* no deemphasis */
-       ONYX_DAC_FILTER_ALWAYS,
-       ONYX_OUTPHASE_INVERTED,
-       (-1 /*dB*/ + 8) & 0xF, /* line in selected, -1 dB gain*/
-       ONYX_ADC_HPF_ALWAYS,
-       (1<<2), /* pcm audio */
-       2,      /* category: pcm coder */
-       0,      /* sampling frequency 44.1 kHz, clock accuracy level II */
-       1       /* 24 bit depth */
-};
-
-/* reset registers of chip, either to initial or to previous values */
-static int onyx_register_init(struct onyx *onyx)
-{
-       int i;
-       u8 val;
-       u8 regs[sizeof(initial_values)];
-
-       if (!onyx->initialised) {
-               memcpy(regs, initial_values, sizeof(initial_values));
-               if (onyx_read_register(onyx, ONYX_REG_CONTROL, &val))
-                       return -1;
-               val &= ~ONYX_SILICONVERSION;
-               val |= initial_values[3];
-               regs[3] = val;
-       } else {
-               for (i=0; i<sizeof(register_map); i++)
-                       regs[i] = onyx->cache[register_map[i]-FIRSTREGISTER];
-       }
-
-       for (i=0; i<sizeof(register_map); i++) {
-               if (onyx_write_register(onyx, register_map[i], regs[i]))
-                       return -1;
-       }
-       onyx->initialised = 1;
-       return 0;
-}
-
-static struct transfer_info onyx_transfers[] = {
-       /* this is first so we can skip it if no input is present...
-        * No hardware exists with that, but it's here as an example
-        * of what to do :) */
-       {
-               /* analog input */
-               .formats = SNDRV_PCM_FMTBIT_S8 |
-                          SNDRV_PCM_FMTBIT_S16_BE |
-                          SNDRV_PCM_FMTBIT_S24_BE,
-               .rates = SNDRV_PCM_RATE_8000_96000,
-               .transfer_in = 1,
-               .must_be_clock_source = 0,
-               .tag = 0,
-       },
-       {
-               /* if analog and digital are currently off, anything should go,
-                * so this entry describes everything we can do... */
-               .formats = SNDRV_PCM_FMTBIT_S8 |
-                          SNDRV_PCM_FMTBIT_S16_BE |
-                          SNDRV_PCM_FMTBIT_S24_BE
-#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
-                          | SNDRV_PCM_FMTBIT_COMPRESSED_16BE
-#endif
-               ,
-               .rates = SNDRV_PCM_RATE_8000_96000,
-               .tag = 0,
-       },
-       {
-               /* analog output */
-               .formats = SNDRV_PCM_FMTBIT_S8 |
-                          SNDRV_PCM_FMTBIT_S16_BE |
-                          SNDRV_PCM_FMTBIT_S24_BE,
-               .rates = SNDRV_PCM_RATE_8000_96000,
-               .transfer_in = 0,
-               .must_be_clock_source = 0,
-               .tag = 1,
-       },
-       {
-               /* digital pcm output, also possible for analog out */
-               .formats = SNDRV_PCM_FMTBIT_S8 |
-                          SNDRV_PCM_FMTBIT_S16_BE |
-                          SNDRV_PCM_FMTBIT_S24_BE,
-               .rates = SNDRV_PCM_RATE_32000 |
-                        SNDRV_PCM_RATE_44100 |
-                        SNDRV_PCM_RATE_48000,
-               .transfer_in = 0,
-               .must_be_clock_source = 0,
-               .tag = 2,
-       },
-#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
-       /* Once alsa gets supports for this kind of thing we can add it... */
-       {
-               /* digital compressed output */
-               .formats =  SNDRV_PCM_FMTBIT_COMPRESSED_16BE,
-               .rates = SNDRV_PCM_RATE_32000 |
-                        SNDRV_PCM_RATE_44100 |
-                        SNDRV_PCM_RATE_48000,
-               .tag = 2,
-       },
-#endif
-       {}
-};
-
-static int onyx_usable(struct codec_info_item *cii,
-                      struct transfer_info *ti,
-                      struct transfer_info *out)
-{
-       u8 v;
-       struct onyx *onyx = cii->codec_data;
-       int spdif_enabled, analog_enabled;
-
-       mutex_lock(&onyx->mutex);
-       onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
-       spdif_enabled = !!(v & ONYX_SPDIF_ENABLE);
-       onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
-       analog_enabled = 
-               (v & (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT))
-                != (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT);
-       mutex_unlock(&onyx->mutex);
-
-       switch (ti->tag) {
-       case 0: return 1;
-       case 1: return analog_enabled;
-       case 2: return spdif_enabled;
-       }
-       return 1;
-}
-
-static int onyx_prepare(struct codec_info_item *cii,
-                       struct bus_info *bi,
-                       struct snd_pcm_substream *substream)
-{
-       u8 v;
-       struct onyx *onyx = cii->codec_data;
-       int err = -EBUSY;
-
-       mutex_lock(&onyx->mutex);
-
-#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
-       if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) {
-               /* mute and lock analog output */
-               onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
-               if (onyx_write_register(onyx,
-                                       ONYX_REG_DAC_CONTROL,
-                                       v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT))
-                       goto out_unlock;
-               onyx->analog_locked = 1;
-               err = 0;
-               goto out_unlock;
-       }
-#endif
-       switch (substream->runtime->rate) {
-       case 32000:
-       case 44100:
-       case 48000:
-               /* these rates are ok for all outputs */
-               /* FIXME: program spdif channel control bits here so that
-                *        userspace doesn't have to if it only plays pcm! */
-               err = 0;
-               goto out_unlock;
-       default:
-               /* got some rate that the digital output can't do,
-                * so disable and lock it */
-               onyx_read_register(cii->codec_data, ONYX_REG_DIG_INFO4, &v);
-               if (onyx_write_register(onyx,
-                                       ONYX_REG_DIG_INFO4,
-                                       v & ~ONYX_SPDIF_ENABLE))
-                       goto out_unlock;
-               onyx->spdif_locked = 1;
-               err = 0;
-               goto out_unlock;
-       }
-
- out_unlock:
-       mutex_unlock(&onyx->mutex);
-
-       return err;
-}
-
-static int onyx_open(struct codec_info_item *cii,
-                    struct snd_pcm_substream *substream)
-{
-       struct onyx *onyx = cii->codec_data;
-
-       mutex_lock(&onyx->mutex);
-       onyx->open_count++;
-       mutex_unlock(&onyx->mutex);
-
-       return 0;
-}
-
-static int onyx_close(struct codec_info_item *cii,
-                     struct snd_pcm_substream *substream)
-{
-       struct onyx *onyx = cii->codec_data;
-
-       mutex_lock(&onyx->mutex);
-       onyx->open_count--;
-       if (!onyx->open_count)
-               onyx->spdif_locked = onyx->analog_locked = 0;
-       mutex_unlock(&onyx->mutex);
-
-       return 0;
-}
-
-static int onyx_switch_clock(struct codec_info_item *cii,
-                            enum clock_switch what)
-{
-       struct onyx *onyx = cii->codec_data;
-
-       mutex_lock(&onyx->mutex);
-       /* this *MUST* be more elaborate later... */
-       switch (what) {
-       case CLOCK_SWITCH_PREPARE_SLAVE:
-               onyx->codec.gpio->methods->all_amps_off(onyx->codec.gpio);
-               break;
-       case CLOCK_SWITCH_SLAVE:
-               onyx->codec.gpio->methods->all_amps_restore(onyx->codec.gpio);
-               break;
-       default: /* silence warning */
-               break;
-       }
-       mutex_unlock(&onyx->mutex);
-
-       return 0;
-}
-
-#ifdef CONFIG_PM
-
-static int onyx_suspend(struct codec_info_item *cii, pm_message_t state)
-{
-       struct onyx *onyx = cii->codec_data;
-       u8 v;
-       int err = -ENXIO;
-
-       mutex_lock(&onyx->mutex);
-       if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v))
-               goto out_unlock;
-       onyx_write_register(onyx, ONYX_REG_CONTROL, v | ONYX_ADPSV | ONYX_DAPSV);
-       /* Apple does a sleep here but the datasheet says to do it on resume */
-       err = 0;
- out_unlock:
-       mutex_unlock(&onyx->mutex);
-
-       return err;
-}
-
-static int onyx_resume(struct codec_info_item *cii)
-{
-       struct onyx *onyx = cii->codec_data;
-       u8 v;
-       int err = -ENXIO;
-
-       mutex_lock(&onyx->mutex);
-
-       /* reset codec */
-       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
-       msleep(1);
-       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
-       msleep(1);
-       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
-       msleep(1);
-
-       /* take codec out of suspend (if it still is after reset) */
-       if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v))
-               goto out_unlock;
-       onyx_write_register(onyx, ONYX_REG_CONTROL, v & ~(ONYX_ADPSV | ONYX_DAPSV));
-       /* FIXME: should divide by sample rate, but 8k is the lowest we go */
-       msleep(2205000/8000);
-       /* reset all values */
-       onyx_register_init(onyx);
-       err = 0;
- out_unlock:
-       mutex_unlock(&onyx->mutex);
-
-       return err;
-}
-
-#endif /* CONFIG_PM */
-
-static struct codec_info onyx_codec_info = {
-       .transfers = onyx_transfers,
-       .sysclock_factor = 256,
-       .bus_factor = 64,
-       .owner = THIS_MODULE,
-       .usable = onyx_usable,
-       .prepare = onyx_prepare,
-       .open = onyx_open,
-       .close = onyx_close,
-       .switch_clock = onyx_switch_clock,
-#ifdef CONFIG_PM
-       .suspend = onyx_suspend,
-       .resume = onyx_resume,
-#endif
-};
-
-static int onyx_init_codec(struct aoa_codec *codec)
-{
-       struct onyx *onyx = codec_to_onyx(codec);
-       struct snd_kcontrol *ctl;
-       struct codec_info *ci = &onyx_codec_info;
-       u8 v;
-       int err;
-
-       if (!onyx->codec.gpio || !onyx->codec.gpio->methods) {
-               printk(KERN_ERR PFX "gpios not assigned!!\n");
-               return -EINVAL;
-       }
-
-       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
-       msleep(1);
-       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
-       msleep(1);
-       onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
-       msleep(1);
-       
-       if (onyx_register_init(onyx)) {
-               printk(KERN_ERR PFX "failed to initialise onyx registers\n");
-               return -ENODEV;
-       }
-
-       if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, onyx, &ops)) {
-               printk(KERN_ERR PFX "failed to create onyx snd device!\n");
-               return -ENODEV;
-       }
-
-       /* nothing connected? what a joke! */
-       if ((onyx->codec.connected & 0xF) == 0)
-               return -ENOTCONN;
-
-       /* if no inputs are present... */
-       if ((onyx->codec.connected & 0xC) == 0) {
-               if (!onyx->codec_info)
-                       onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL);
-               if (!onyx->codec_info)
-                       return -ENOMEM;
-               ci = onyx->codec_info;
-               *ci = onyx_codec_info;
-               ci->transfers++;
-       }
-
-       /* if no outputs are present... */
-       if ((onyx->codec.connected & 3) == 0) {
-               if (!onyx->codec_info)
-                       onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL);
-               if (!onyx->codec_info)
-                       return -ENOMEM;
-               ci = onyx->codec_info;
-               /* this is fine as there have to be inputs
-                * if we end up in this part of the code */
-               *ci = onyx_codec_info;
-               ci->transfers[1].formats = 0;
-       }
-
-       if (onyx->codec.soundbus_dev->attach_codec(onyx->codec.soundbus_dev,
-                                                  aoa_get_card(),
-                                                  ci, onyx)) {
-               printk(KERN_ERR PFX "error creating onyx pcm\n");
-               return -ENODEV;
-       }
-#define ADDCTL(n)                                                      \
-       do {                                                            \
-               ctl = snd_ctl_new1(&n, onyx);                           \
-               if (ctl) {                                              \
-                       ctl->id.device =                                \
-                               onyx->codec.soundbus_dev->pcm->device;  \
-                       err = aoa_snd_ctl_add(ctl);                     \
-                       if (err)                                        \
-                               goto error;                             \
-               }                                                       \
-       } while (0)
-
-       if (onyx->codec.soundbus_dev->pcm) {
-               /* give the user appropriate controls
-                * depending on what inputs are connected */
-               if ((onyx->codec.connected & 0xC) == 0xC)
-                       ADDCTL(capture_source_control);
-               else if (onyx->codec.connected & 4)
-                       onyx_set_capture_source(onyx, 0);
-               else
-                       onyx_set_capture_source(onyx, 1);
-               if (onyx->codec.connected & 0xC)
-                       ADDCTL(inputgain_control);
-
-               /* depending on what output is connected,
-                * give the user appropriate controls */
-               if (onyx->codec.connected & 1) {
-                       ADDCTL(volume_control);
-                       ADDCTL(mute_control);
-                       ADDCTL(ovr1_control);
-                       ADDCTL(flt0_control);
-                       ADDCTL(hpf_control);
-                       ADDCTL(dm12_control);
-                       /* spdif control defaults to off */
-               }
-               if (onyx->codec.connected & 2) {
-                       ADDCTL(onyx_spdif_mask);
-                       ADDCTL(onyx_spdif_ctrl);
-               }
-               if ((onyx->codec.connected & 3) == 3)
-                       ADDCTL(spdif_control);
-               /* if only S/PDIF is connected, enable it unconditionally */
-               if ((onyx->codec.connected & 3) == 2) {
-                       onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
-                       v |= ONYX_SPDIF_ENABLE;
-                       onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
-               }
-       }
-#undef ADDCTL
-       printk(KERN_INFO PFX "attached to onyx codec via i2c\n");
-
-       return 0;
- error:
-       onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
-       snd_device_free(aoa_get_card(), onyx);
-       return err;
-}
-
-static void onyx_exit_codec(struct aoa_codec *codec)
-{
-       struct onyx *onyx = codec_to_onyx(codec);
-
-       if (!onyx->codec.soundbus_dev) {
-               printk(KERN_ERR PFX "onyx_exit_codec called without soundbus_dev!\n");
-               return;
-       }
-       onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
-}
-
-static struct i2c_driver onyx_driver;
-
-static int onyx_create(struct i2c_adapter *adapter,
-                      struct device_node *node,
-                      int addr)
-{
-       struct onyx *onyx;
-       u8 dummy;
-
-       onyx = kzalloc(sizeof(struct onyx), GFP_KERNEL);
-
-       if (!onyx)
-               return -ENOMEM;
-
-       mutex_init(&onyx->mutex);
-       onyx->i2c.driver = &onyx_driver;
-       onyx->i2c.adapter = adapter;
-       onyx->i2c.addr = addr & 0x7f;
-       strlcpy(onyx->i2c.name, "onyx audio codec", I2C_NAME_SIZE);
-
-       if (i2c_attach_client(&onyx->i2c)) {
-               printk(KERN_ERR PFX "failed to attach to i2c\n");
-               goto fail;
-       }
-
-       /* we try to read from register ONYX_REG_CONTROL
-        * to check if the codec is present */
-       if (onyx_read_register(onyx, ONYX_REG_CONTROL, &dummy) != 0) {
-               i2c_detach_client(&onyx->i2c);
-               printk(KERN_ERR PFX "failed to read control register\n");
-               goto fail;
-       }
-
-       strlcpy(onyx->codec.name, "onyx", MAX_CODEC_NAME_LEN);
-       onyx->codec.owner = THIS_MODULE;
-       onyx->codec.init = onyx_init_codec;
-       onyx->codec.exit = onyx_exit_codec;
-       onyx->codec.node = of_node_get(node);
-
-       if (aoa_codec_register(&onyx->codec)) {
-               i2c_detach_client(&onyx->i2c);
-               goto fail;
-       }
-       printk(KERN_DEBUG PFX "created and attached onyx instance\n");
-       return 0;
- fail:
-       kfree(onyx);
-       return -EINVAL;
-}
-
-static int onyx_i2c_attach(struct i2c_adapter *adapter)
-{
-       struct device_node *busnode, *dev = NULL;
-       struct pmac_i2c_bus *bus;
-
-       bus = pmac_i2c_adapter_to_bus(adapter);
-       if (bus == NULL)
-               return -ENODEV;
-       busnode = pmac_i2c_get_bus_node(bus);
-
-       while ((dev = of_get_next_child(busnode, dev)) != NULL) {
-               if (of_device_is_compatible(dev, "pcm3052")) {
-                       const u32 *addr;
-                       printk(KERN_DEBUG PFX "found pcm3052\n");
-                       addr = of_get_property(dev, "reg", NULL);
-                       if (!addr)
-                               return -ENODEV;
-                       return onyx_create(adapter, dev, (*addr)>>1);
-               }
-       }
-
-       /* if that didn't work, try desperate mode for older
-        * machines that have stuff missing from the device tree */
-       
-       if (!of_device_is_compatible(busnode, "k2-i2c"))
-               return -ENODEV;
-
-       printk(KERN_DEBUG PFX "found k2-i2c, checking if onyx chip is on it\n");
-       /* probe both possible addresses for the onyx chip */
-       if (onyx_create(adapter, NULL, 0x46) == 0)
-               return 0;
-       return onyx_create(adapter, NULL, 0x47);
-}
-
-static int onyx_i2c_detach(struct i2c_client *client)
-{
-       struct onyx *onyx = container_of(client, struct onyx, i2c);
-       int err;
-
-       if ((err = i2c_detach_client(client)))
-               return err;
-       aoa_codec_unregister(&onyx->codec);
-       of_node_put(onyx->codec.node);
-       if (onyx->codec_info)
-               kfree(onyx->codec_info);
-       kfree(onyx);
-       return 0;
-}
-
-static struct i2c_driver onyx_driver = {
-       .driver = {
-               .name = "aoa_codec_onyx",
-               .owner = THIS_MODULE,
-       },
-       .attach_adapter = onyx_i2c_attach,
-       .detach_client = onyx_i2c_detach,
-};
-
-static int __init onyx_init(void)
-{
-       return i2c_add_driver(&onyx_driver);
-}
-
-static void __exit onyx_exit(void)
-{
-       i2c_del_driver(&onyx_driver);
-}
-
-module_init(onyx_init);
-module_exit(onyx_exit);
diff --git a/sound/aoa/codecs/snd-aoa-codec-onyx.h b/sound/aoa/codecs/snd-aoa-codec-onyx.h
deleted file mode 100644 (file)
index ffd2025..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Apple Onboard Audio driver for Onyx codec (header)
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-#ifndef __SND_AOA_CODEC_ONYX_H
-#define __SND_AOA_CODEC_ONYX_H
-#include <stddef.h>
-#include <linux/i2c.h>
-#include <asm/pmac_low_i2c.h>
-#include <asm/prom.h>
-
-/* PCM3052 register definitions */
-
-/* the attenuation registers take values from
- * -1 (0dB) to -127 (-63.0 dB) or others (muted) */
-#define ONYX_REG_DAC_ATTEN_LEFT                65
-#define FIRSTREGISTER                  ONYX_REG_DAC_ATTEN_LEFT
-#define ONYX_REG_DAC_ATTEN_RIGHT       66
-
-#define ONYX_REG_CONTROL               67
-#      define ONYX_MRST                (1<<7)
-#      define ONYX_SRST                (1<<6)
-#      define ONYX_ADPSV               (1<<5)
-#      define ONYX_DAPSV               (1<<4)
-#      define ONYX_SILICONVERSION      (1<<0)
-/* all others reserved */
-
-#define ONYX_REG_DAC_CONTROL           68
-#      define ONYX_OVR1                (1<<6)
-#      define ONYX_MUTE_RIGHT          (1<<1)
-#      define ONYX_MUTE_LEFT           (1<<0)
-
-#define ONYX_REG_DAC_DEEMPH            69
-#      define ONYX_DIGDEEMPH_SHIFT     5
-#      define ONYX_DIGDEEMPH_MASK      (3<<ONYX_DIGDEEMPH_SHIFT)
-#      define ONYX_DIGDEEMPH_CTRL      (1<<4)
-
-#define ONYX_REG_DAC_FILTER            70
-#      define ONYX_ROLLOFF_FAST        (1<<5)
-#      define ONYX_DAC_FILTER_ALWAYS   (1<<2)
-
-#define        ONYX_REG_DAC_OUTPHASE           71
-#      define ONYX_OUTPHASE_INVERTED   (1<<0)
-
-#define ONYX_REG_ADC_CONTROL           72
-#      define ONYX_ADC_INPUT_MIC       (1<<5)
-/* 8 + input gain in dB, valid range for input gain is -4 .. 20 dB */
-#      define ONYX_ADC_PGA_GAIN_MASK   0x1f
-
-#define ONYX_REG_ADC_HPF_BYPASS                75
-#      define ONYX_HPF_DISABLE         (1<<3)
-#      define ONYX_ADC_HPF_ALWAYS      (1<<2)
-
-#define ONYX_REG_DIG_INFO1             77
-#      define ONYX_MASK_DIN_TO_BPZ     (1<<7)
-/* bits 1-5 control channel bits 1-5 */
-#      define ONYX_DIGOUT_DISABLE      (1<<0)
-
-#define ONYX_REG_DIG_INFO2             78
-/* controls channel bits 8-15 */
-
-#define ONYX_REG_DIG_INFO3             79
-/* control channel bits 24-29, high 2 bits reserved */
-
-#define ONYX_REG_DIG_INFO4             80
-#      define ONYX_VALIDL              (1<<7)
-#      define ONYX_VALIDR              (1<<6)
-#      define ONYX_SPDIF_ENABLE        (1<<5)
-/* lower 4 bits control bits 32-35 of channel control and word length */
-#      define ONYX_WORDLEN_MASK        (0xF)
-
-#endif /* __SND_AOA_CODEC_ONYX_H */
diff --git a/sound/aoa/codecs/snd-aoa-codec-tas-basstreble.h b/sound/aoa/codecs/snd-aoa-codec-tas-basstreble.h
deleted file mode 100644 (file)
index 69b6113..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * This file is only included exactly once!
- *
- * The tables here are derived from the tas3004 datasheet,
- * modulo typo corrections and some smoothing...
- */
-
-#define TAS3004_TREBLE_MIN     0
-#define TAS3004_TREBLE_MAX     72
-#define TAS3004_BASS_MIN       0
-#define TAS3004_BASS_MAX       72
-#define TAS3004_TREBLE_ZERO    36
-#define TAS3004_BASS_ZERO      36
-
-static u8 tas3004_treble_table[] = {
-       150, /* -18 dB */
-       149,
-       148,
-       147,
-       146,
-       145,
-       144,
-       143,
-       142,
-       141,
-       140,
-       139,
-       138,
-       137,
-       136,
-       135,
-       134,
-       133,
-       132,
-       131,
-       130,
-       129,
-       128,
-       127,
-       126,
-       125,
-       124,
-       123,
-       122,
-       121,
-       120,
-       119,
-       118,
-       117,
-       116,
-       115,
-       114, /* 0 dB */
-       113,
-       112,
-       111,
-       109,
-       108,
-       107,
-       105,
-       104,
-       103,
-       101,
-       99,
-       98,
-       96,
-       93,
-       91,
-       89,
-       86,
-       83,
-       81,
-       77,
-       74,
-       71,
-       67,
-       63,
-       59,
-       54,
-       49,
-       44,
-       38,
-       32,
-       26,
-       19,
-       10,
-       4,
-       2,
-       1, /* +18 dB */
-};
-
-static inline u8 tas3004_treble(int idx)
-{
-       return tas3004_treble_table[idx];
-}
-
-/* I only save the difference here to the treble table
- * so that the binary is smaller...
- * I have also ignored completely differences of
- * +/- 1
- */
-static s8 tas3004_bass_diff_to_treble[] = {
-       2, /* 7 dB, offset 50 */
-       2,
-       2,
-       2,
-       2,
-       1,
-       2,
-       2,
-       2,
-       3,
-       4,
-       4,
-       5,
-       6,
-       7,
-       8,
-       9,
-       10,
-       11,
-       14,
-       13,
-       8,
-       1, /* 18 dB */
-};
-
-static inline u8 tas3004_bass(int idx)
-{
-       u8 result = tas3004_treble_table[idx];
-
-       if (idx >= 50)
-               result += tas3004_bass_diff_to_treble[idx-50];
-       return result;
-}
diff --git a/sound/aoa/codecs/snd-aoa-codec-tas-gain-table.h b/sound/aoa/codecs/snd-aoa-codec-tas-gain-table.h
deleted file mode 100644 (file)
index 4cfa675..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- This is the program used to generate below table.
-
-#include <stdio.h>
-#include <math.h>
-int main() {
-  int dB2;
-  printf("/" "* This file is only included exactly once!\n");
-  printf(" *\n");
-  printf(" * If they'd only tell us that generating this table was\n");
-  printf(" * as easy as calculating\n");
-  printf(" *      hwvalue = 1048576.0*exp(0.057564628*dB*2)\n");
-  printf(" * :) *" "/\n");
-  printf("static int tas_gaintable[] = {\n");
-  printf("     0x000000, /" "* -infinity dB *" "/\n");
-  for (dB2=-140;dB2<=36;dB2++)
-    printf("   0x%.6x, /" "* %-02.1f dB *" "/\n", (int)(1048576.0*exp(0.057564628*dB2)), dB2/2.0);
-  printf("};\n\n");
-}
-
-*/
-
-/* This file is only included exactly once!
- *
- * If they'd only tell us that generating this table was
- * as easy as calculating
- *      hwvalue = 1048576.0*exp(0.057564628*dB*2)
- * :) */
-static int tas_gaintable[] = {
-       0x000000, /* -infinity dB */
-       0x00014b, /* -70.0 dB */
-       0x00015f, /* -69.5 dB */
-       0x000174, /* -69.0 dB */
-       0x00018a, /* -68.5 dB */
-       0x0001a1, /* -68.0 dB */
-       0x0001ba, /* -67.5 dB */
-       0x0001d4, /* -67.0 dB */
-       0x0001f0, /* -66.5 dB */
-       0x00020d, /* -66.0 dB */
-       0x00022c, /* -65.5 dB */
-       0x00024d, /* -65.0 dB */
-       0x000270, /* -64.5 dB */
-       0x000295, /* -64.0 dB */
-       0x0002bc, /* -63.5 dB */
-       0x0002e6, /* -63.0 dB */
-       0x000312, /* -62.5 dB */
-       0x000340, /* -62.0 dB */
-       0x000372, /* -61.5 dB */
-       0x0003a6, /* -61.0 dB */
-       0x0003dd, /* -60.5 dB */
-       0x000418, /* -60.0 dB */
-       0x000456, /* -59.5 dB */
-       0x000498, /* -59.0 dB */
-       0x0004de, /* -58.5 dB */
-       0x000528, /* -58.0 dB */
-       0x000576, /* -57.5 dB */
-       0x0005c9, /* -57.0 dB */
-       0x000620, /* -56.5 dB */
-       0x00067d, /* -56.0 dB */
-       0x0006e0, /* -55.5 dB */
-       0x000748, /* -55.0 dB */
-       0x0007b7, /* -54.5 dB */
-       0x00082c, /* -54.0 dB */
-       0x0008a8, /* -53.5 dB */
-       0x00092b, /* -53.0 dB */
-       0x0009b6, /* -52.5 dB */
-       0x000a49, /* -52.0 dB */
-       0x000ae5, /* -51.5 dB */
-       0x000b8b, /* -51.0 dB */
-       0x000c3a, /* -50.5 dB */
-       0x000cf3, /* -50.0 dB */
-       0x000db8, /* -49.5 dB */
-       0x000e88, /* -49.0 dB */
-       0x000f64, /* -48.5 dB */
-       0x00104e, /* -48.0 dB */
-       0x001145, /* -47.5 dB */
-       0x00124b, /* -47.0 dB */
-       0x001361, /* -46.5 dB */
-       0x001487, /* -46.0 dB */
-       0x0015be, /* -45.5 dB */
-       0x001708, /* -45.0 dB */
-       0x001865, /* -44.5 dB */
-       0x0019d8, /* -44.0 dB */
-       0x001b60, /* -43.5 dB */
-       0x001cff, /* -43.0 dB */
-       0x001eb7, /* -42.5 dB */
-       0x002089, /* -42.0 dB */
-       0x002276, /* -41.5 dB */
-       0x002481, /* -41.0 dB */
-       0x0026ab, /* -40.5 dB */
-       0x0028f5, /* -40.0 dB */
-       0x002b63, /* -39.5 dB */
-       0x002df5, /* -39.0 dB */
-       0x0030ae, /* -38.5 dB */
-       0x003390, /* -38.0 dB */
-       0x00369e, /* -37.5 dB */
-       0x0039db, /* -37.0 dB */
-       0x003d49, /* -36.5 dB */
-       0x0040ea, /* -36.0 dB */
-       0x0044c3, /* -35.5 dB */
-       0x0048d6, /* -35.0 dB */
-       0x004d27, /* -34.5 dB */
-       0x0051b9, /* -34.0 dB */
-       0x005691, /* -33.5 dB */
-       0x005bb2, /* -33.0 dB */
-       0x006121, /* -32.5 dB */
-       0x0066e3, /* -32.0 dB */
-       0x006cfb, /* -31.5 dB */
-       0x007370, /* -31.0 dB */
-       0x007a48, /* -30.5 dB */
-       0x008186, /* -30.0 dB */
-       0x008933, /* -29.5 dB */
-       0x009154, /* -29.0 dB */
-       0x0099f1, /* -28.5 dB */
-       0x00a310, /* -28.0 dB */
-       0x00acba, /* -27.5 dB */
-       0x00b6f6, /* -27.0 dB */
-       0x00c1cd, /* -26.5 dB */
-       0x00cd49, /* -26.0 dB */
-       0x00d973, /* -25.5 dB */
-       0x00e655, /* -25.0 dB */
-       0x00f3fb, /* -24.5 dB */
-       0x010270, /* -24.0 dB */
-       0x0111c0, /* -23.5 dB */
-       0x0121f9, /* -23.0 dB */
-       0x013328, /* -22.5 dB */
-       0x01455b, /* -22.0 dB */
-       0x0158a2, /* -21.5 dB */
-       0x016d0e, /* -21.0 dB */
-       0x0182af, /* -20.5 dB */
-       0x019999, /* -20.0 dB */
-       0x01b1de, /* -19.5 dB */
-       0x01cb94, /* -19.0 dB */
-       0x01e6cf, /* -18.5 dB */
-       0x0203a7, /* -18.0 dB */
-       0x022235, /* -17.5 dB */
-       0x024293, /* -17.0 dB */
-       0x0264db, /* -16.5 dB */
-       0x02892c, /* -16.0 dB */
-       0x02afa3, /* -15.5 dB */
-       0x02d862, /* -15.0 dB */
-       0x03038a, /* -14.5 dB */
-       0x033142, /* -14.0 dB */
-       0x0361af, /* -13.5 dB */
-       0x0394fa, /* -13.0 dB */
-       0x03cb50, /* -12.5 dB */
-       0x0404de, /* -12.0 dB */
-       0x0441d5, /* -11.5 dB */
-       0x048268, /* -11.0 dB */
-       0x04c6d0, /* -10.5 dB */
-       0x050f44, /* -10.0 dB */
-       0x055c04, /* -9.5 dB */
-       0x05ad50, /* -9.0 dB */
-       0x06036e, /* -8.5 dB */
-       0x065ea5, /* -8.0 dB */
-       0x06bf44, /* -7.5 dB */
-       0x07259d, /* -7.0 dB */
-       0x079207, /* -6.5 dB */
-       0x0804dc, /* -6.0 dB */
-       0x087e80, /* -5.5 dB */
-       0x08ff59, /* -5.0 dB */
-       0x0987d5, /* -4.5 dB */
-       0x0a1866, /* -4.0 dB */
-       0x0ab189, /* -3.5 dB */
-       0x0b53be, /* -3.0 dB */
-       0x0bff91, /* -2.5 dB */
-       0x0cb591, /* -2.0 dB */
-       0x0d765a, /* -1.5 dB */
-       0x0e4290, /* -1.0 dB */
-       0x0f1adf, /* -0.5 dB */
-       0x100000, /* 0.0 dB */
-       0x10f2b4, /* 0.5 dB */
-       0x11f3c9, /* 1.0 dB */
-       0x13041a, /* 1.5 dB */
-       0x14248e, /* 2.0 dB */
-       0x15561a, /* 2.5 dB */
-       0x1699c0, /* 3.0 dB */
-       0x17f094, /* 3.5 dB */
-       0x195bb8, /* 4.0 dB */
-       0x1adc61, /* 4.5 dB */
-       0x1c73d5, /* 5.0 dB */
-       0x1e236d, /* 5.5 dB */
-       0x1fec98, /* 6.0 dB */
-       0x21d0d9, /* 6.5 dB */
-       0x23d1cd, /* 7.0 dB */
-       0x25f125, /* 7.5 dB */
-       0x2830af, /* 8.0 dB */
-       0x2a9254, /* 8.5 dB */
-       0x2d1818, /* 9.0 dB */
-       0x2fc420, /* 9.5 dB */
-       0x3298b0, /* 10.0 dB */
-       0x35982f, /* 10.5 dB */
-       0x38c528, /* 11.0 dB */
-       0x3c224c, /* 11.5 dB */
-       0x3fb278, /* 12.0 dB */
-       0x4378b0, /* 12.5 dB */
-       0x477829, /* 13.0 dB */
-       0x4bb446, /* 13.5 dB */
-       0x5030a1, /* 14.0 dB */
-       0x54f106, /* 14.5 dB */
-       0x59f980, /* 15.0 dB */
-       0x5f4e52, /* 15.5 dB */
-       0x64f403, /* 16.0 dB */
-       0x6aef5e, /* 16.5 dB */
-       0x714575, /* 17.0 dB */
-       0x77fbaa, /* 17.5 dB */
-       0x7f17af, /* 18.0 dB */
-};
-
diff --git a/sound/aoa/codecs/snd-aoa-codec-tas.c b/sound/aoa/codecs/snd-aoa-codec-tas.c
deleted file mode 100644 (file)
index 6c515b2..0000000
+++ /dev/null
@@ -1,1012 +0,0 @@
-/*
- * Apple Onboard Audio driver for tas codec
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- *
- * Open questions:
- *  - How to distinguish between 3004 and versions?
- *
- * FIXMEs:
- *  - This codec driver doesn't honour the 'connected'
- *    property of the aoa_codec struct, hence if
- *    it is used in machines where not everything is
- *    connected it will display wrong mixer elements.
- *  - Driver assumes that the microphone is always
- *    monaureal and connected to the right channel of
- *    the input. This should also be a codec-dependent
- *    flag, maybe the codec should have 3 different
- *    bits for the three different possibilities how
- *    it can be hooked up...
- *    But as long as I don't see any hardware hooked
- *    up that way...
- *  - As Apple notes in their code, the tas3004 seems
- *    to delay the right channel by one sample. You can
- *    see this when for example recording stereo in
- *    audacity, or recording the tas output via cable
- *    on another machine (use a sinus generator or so).
- *    I tried programming the BiQuads but couldn't
- *    make the delay work, maybe someone can read the
- *    datasheet and fix it. The relevant Apple comment
- *    is in AppleTAS3004Audio.cpp lines 1637 ff. Note
- *    that their comment describing how they program
- *    the filters sucks...
- *
- * Other things:
- *  - this should actually register *two* aoa_codec
- *    structs since it has two inputs. Then it must
- *    use the prepare callback to forbid running the
- *    secondary output on a different clock.
- *    Also, whatever bus knows how to do this must
- *    provide two soundbus_dev devices and the fabric
- *    must be able to link them correctly.
- *
- *    I don't even know if Apple ever uses the second
- *    port on the tas3004 though, I don't think their
- *    i2s controllers can even do it. OTOH, they all
- *    derive the clocks from common clocks, so it
- *    might just be possible. The framework allows the
- *    codec to refine the transfer_info items in the
- *    usable callback, so we can simply remove the
- *    rates the second instance is not using when it
- *    actually is in use.
- *    Maybe we'll need to make the sound busses have
- *    a 'clock group id' value so the codec can
- *    determine if the two outputs can be driven at
- *    the same time. But that is likely overkill, up
- *    to the fabric to not link them up incorrectly,
- *    and up to the hardware designer to not wire
- *    them up in some weird unusable way.
- */
-#include <stddef.h>
-#include <linux/i2c.h>
-#include <asm/pmac_low_i2c.h>
-#include <asm/prom.h>
-#include <linux/delay.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-
-MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("tas codec driver for snd-aoa");
-
-#include "snd-aoa-codec-tas.h"
-#include "snd-aoa-codec-tas-gain-table.h"
-#include "snd-aoa-codec-tas-basstreble.h"
-#include "../aoa.h"
-#include "../soundbus/soundbus.h"
-
-#define PFX "snd-aoa-codec-tas: "
-
-
-struct tas {
-       struct aoa_codec        codec;
-       struct i2c_client       i2c;
-       u32                     mute_l:1, mute_r:1 ,
-                               controls_created:1 ,
-                               drc_enabled:1,
-                               hw_enabled:1;
-       u8                      cached_volume_l, cached_volume_r;
-       u8                      mixer_l[3], mixer_r[3];
-       u8                      bass, treble;
-       u8                      acr;
-       int                     drc_range;
-       /* protects hardware access against concurrency from
-        * userspace when hitting controls and during
-        * codec init/suspend/resume */
-       struct mutex            mtx;
-};
-
-static int tas_reset_init(struct tas *tas);
-
-static struct tas *codec_to_tas(struct aoa_codec *codec)
-{
-       return container_of(codec, struct tas, codec);
-}
-
-static inline int tas_write_reg(struct tas *tas, u8 reg, u8 len, u8 *data)
-{
-       if (len == 1)
-               return i2c_smbus_write_byte_data(&tas->i2c, reg, *data);
-       else
-               return i2c_smbus_write_i2c_block_data(&tas->i2c, reg, len, data);
-}
-
-static void tas3004_set_drc(struct tas *tas)
-{
-       unsigned char val[6];
-
-       if (tas->drc_enabled)
-               val[0] = 0x50; /* 3:1 above threshold */
-       else
-               val[0] = 0x51; /* disabled */
-       val[1] = 0x02; /* 1:1 below threshold */
-       if (tas->drc_range > 0xef)
-               val[2] = 0xef;
-       else if (tas->drc_range < 0)
-               val[2] = 0x00;
-       else
-               val[2] = tas->drc_range;
-       val[3] = 0xb0;
-       val[4] = 0x60;
-       val[5] = 0xa0;
-
-       tas_write_reg(tas, TAS_REG_DRC, 6, val);
-}
-
-static void tas_set_treble(struct tas *tas)
-{
-       u8 tmp;
-
-       tmp = tas3004_treble(tas->treble);
-       tas_write_reg(tas, TAS_REG_TREBLE, 1, &tmp);
-}
-
-static void tas_set_bass(struct tas *tas)
-{
-       u8 tmp;
-
-       tmp = tas3004_bass(tas->bass);
-       tas_write_reg(tas, TAS_REG_BASS, 1, &tmp);
-}
-
-static void tas_set_volume(struct tas *tas)
-{
-       u8 block[6];
-       int tmp;
-       u8 left, right;
-
-       left = tas->cached_volume_l;
-       right = tas->cached_volume_r;
-
-       if (left > 177) left = 177;
-       if (right > 177) right = 177;
-
-       if (tas->mute_l) left = 0;
-       if (tas->mute_r) right = 0;
-
-       /* analysing the volume and mixer tables shows
-        * that they are similar enough when we shift
-        * the mixer table down by 4 bits. The error
-        * is miniscule, in just one item the error
-        * is 1, at a value of 0x07f17b (mixer table
-        * value is 0x07f17a) */
-       tmp = tas_gaintable[left];
-       block[0] = tmp>>20;
-       block[1] = tmp>>12;
-       block[2] = tmp>>4;
-       tmp = tas_gaintable[right];
-       block[3] = tmp>>20;
-       block[4] = tmp>>12;
-       block[5] = tmp>>4;
-       tas_write_reg(tas, TAS_REG_VOL, 6, block);
-}
-
-static void tas_set_mixer(struct tas *tas)
-{
-       u8 block[9];
-       int tmp, i;
-       u8 val;
-
-       for (i=0;i<3;i++) {
-               val = tas->mixer_l[i];
-               if (val > 177) val = 177;
-               tmp = tas_gaintable[val];
-               block[3*i+0] = tmp>>16;
-               block[3*i+1] = tmp>>8;
-               block[3*i+2] = tmp;
-       }
-       tas_write_reg(tas, TAS_REG_LMIX, 9, block);
-
-       for (i=0;i<3;i++) {
-               val = tas->mixer_r[i];
-               if (val > 177) val = 177;
-               tmp = tas_gaintable[val];
-               block[3*i+0] = tmp>>16;
-               block[3*i+1] = tmp>>8;
-               block[3*i+2] = tmp;
-       }
-       tas_write_reg(tas, TAS_REG_RMIX, 9, block);
-}
-
-/* alsa stuff */
-
-static int tas_dev_register(struct snd_device *dev)
-{
-       return 0;
-}
-
-static struct snd_device_ops ops = {
-       .dev_register = tas_dev_register,
-};
-
-static int tas_snd_vol_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-       uinfo->count = 2;
-       uinfo->value.integer.min = 0;
-       uinfo->value.integer.max = 177;
-       return 0;
-}
-
-static int tas_snd_vol_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       ucontrol->value.integer.value[0] = tas->cached_volume_l;
-       ucontrol->value.integer.value[1] = tas->cached_volume_r;
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int tas_snd_vol_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       if (ucontrol->value.integer.value[0] < 0 ||
-           ucontrol->value.integer.value[0] > 177)
-               return -EINVAL;
-       if (ucontrol->value.integer.value[1] < 0 ||
-           ucontrol->value.integer.value[1] > 177)
-               return -EINVAL;
-
-       mutex_lock(&tas->mtx);
-       if (tas->cached_volume_l == ucontrol->value.integer.value[0]
-        && tas->cached_volume_r == ucontrol->value.integer.value[1]) {
-               mutex_unlock(&tas->mtx);
-               return 0;
-       }
-
-       tas->cached_volume_l = ucontrol->value.integer.value[0];
-       tas->cached_volume_r = ucontrol->value.integer.value[1];
-       if (tas->hw_enabled)
-               tas_set_volume(tas);
-       mutex_unlock(&tas->mtx);
-       return 1;
-}
-
-static struct snd_kcontrol_new volume_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Master Playback Volume",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = tas_snd_vol_info,
-       .get = tas_snd_vol_get,
-       .put = tas_snd_vol_put,
-};
-
-#define tas_snd_mute_info      snd_ctl_boolean_stereo_info
-
-static int tas_snd_mute_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       ucontrol->value.integer.value[0] = !tas->mute_l;
-       ucontrol->value.integer.value[1] = !tas->mute_r;
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int tas_snd_mute_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       if (tas->mute_l == !ucontrol->value.integer.value[0]
-        && tas->mute_r == !ucontrol->value.integer.value[1]) {
-               mutex_unlock(&tas->mtx);
-               return 0;
-       }
-
-       tas->mute_l = !ucontrol->value.integer.value[0];
-       tas->mute_r = !ucontrol->value.integer.value[1];
-       if (tas->hw_enabled)
-               tas_set_volume(tas);
-       mutex_unlock(&tas->mtx);
-       return 1;
-}
-
-static struct snd_kcontrol_new mute_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Master Playback Switch",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = tas_snd_mute_info,
-       .get = tas_snd_mute_get,
-       .put = tas_snd_mute_put,
-};
-
-static int tas_snd_mixer_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-       uinfo->count = 2;
-       uinfo->value.integer.min = 0;
-       uinfo->value.integer.max = 177;
-       return 0;
-}
-
-static int tas_snd_mixer_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-       int idx = kcontrol->private_value;
-
-       mutex_lock(&tas->mtx);
-       ucontrol->value.integer.value[0] = tas->mixer_l[idx];
-       ucontrol->value.integer.value[1] = tas->mixer_r[idx];
-       mutex_unlock(&tas->mtx);
-
-       return 0;
-}
-
-static int tas_snd_mixer_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-       int idx = kcontrol->private_value;
-
-       mutex_lock(&tas->mtx);
-       if (tas->mixer_l[idx] == ucontrol->value.integer.value[0]
-        && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) {
-               mutex_unlock(&tas->mtx);
-               return 0;
-       }
-
-       tas->mixer_l[idx] = ucontrol->value.integer.value[0];
-       tas->mixer_r[idx] = ucontrol->value.integer.value[1];
-
-       if (tas->hw_enabled)
-               tas_set_mixer(tas);
-       mutex_unlock(&tas->mtx);
-       return 1;
-}
-
-#define MIXER_CONTROL(n,descr,idx)                     \
-static struct snd_kcontrol_new n##_control = {         \
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,            \
-       .name = descr " Playback Volume",               \
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,      \
-       .info = tas_snd_mixer_info,                     \
-       .get = tas_snd_mixer_get,                       \
-       .put = tas_snd_mixer_put,                       \
-       .private_value = idx,                           \
-}
-
-MIXER_CONTROL(pcm1, "PCM", 0);
-MIXER_CONTROL(monitor, "Monitor", 2);
-
-static int tas_snd_drc_range_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-       uinfo->count = 1;
-       uinfo->value.integer.min = 0;
-       uinfo->value.integer.max = TAS3004_DRC_MAX;
-       return 0;
-}
-
-static int tas_snd_drc_range_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       ucontrol->value.integer.value[0] = tas->drc_range;
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int tas_snd_drc_range_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       if (ucontrol->value.integer.value[0] < 0 ||
-           ucontrol->value.integer.value[0] > TAS3004_DRC_MAX)
-               return -EINVAL;
-
-       mutex_lock(&tas->mtx);
-       if (tas->drc_range == ucontrol->value.integer.value[0]) {
-               mutex_unlock(&tas->mtx);
-               return 0;
-       }
-
-       tas->drc_range = ucontrol->value.integer.value[0];
-       if (tas->hw_enabled)
-               tas3004_set_drc(tas);
-       mutex_unlock(&tas->mtx);
-       return 1;
-}
-
-static struct snd_kcontrol_new drc_range_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "DRC Range",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = tas_snd_drc_range_info,
-       .get = tas_snd_drc_range_get,
-       .put = tas_snd_drc_range_put,
-};
-
-#define tas_snd_drc_switch_info                snd_ctl_boolean_mono_info
-
-static int tas_snd_drc_switch_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       ucontrol->value.integer.value[0] = tas->drc_enabled;
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int tas_snd_drc_switch_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       if (tas->drc_enabled == ucontrol->value.integer.value[0]) {
-               mutex_unlock(&tas->mtx);
-               return 0;
-       }
-
-       tas->drc_enabled = !!ucontrol->value.integer.value[0];
-       if (tas->hw_enabled)
-               tas3004_set_drc(tas);
-       mutex_unlock(&tas->mtx);
-       return 1;
-}
-
-static struct snd_kcontrol_new drc_switch_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "DRC Range Switch",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = tas_snd_drc_switch_info,
-       .get = tas_snd_drc_switch_get,
-       .put = tas_snd_drc_switch_put,
-};
-
-static int tas_snd_capture_source_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       static char *texts[] = { "Line-In", "Microphone" };
-
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
-       uinfo->count = 1;
-       uinfo->value.enumerated.items = 2;
-       if (uinfo->value.enumerated.item > 1)
-               uinfo->value.enumerated.item = 1;
-       strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
-       return 0;
-}
-
-static int tas_snd_capture_source_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B);
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int tas_snd_capture_source_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-       int oldacr;
-
-       if (ucontrol->value.enumerated.item[0] > 1)
-               return -EINVAL;
-       mutex_lock(&tas->mtx);
-       oldacr = tas->acr;
-
-       /*
-        * Despite what the data sheet says in one place, the
-        * TAS_ACR_B_MONAUREAL bit forces mono output even when
-        * input A (line in) is selected.
-        */
-       tas->acr &= ~(TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL);
-       if (ucontrol->value.enumerated.item[0])
-               tas->acr |= TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL |
-                     TAS_ACR_B_MON_SEL_RIGHT;
-       if (oldacr == tas->acr) {
-               mutex_unlock(&tas->mtx);
-               return 0;
-       }
-       if (tas->hw_enabled)
-               tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
-       mutex_unlock(&tas->mtx);
-       return 1;
-}
-
-static struct snd_kcontrol_new capture_source_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       /* If we name this 'Input Source', it properly shows up in
-        * alsamixer as a selection, * but it's shown under the
-        * 'Playback' category.
-        * If I name it 'Capture Source', it shows up in strange
-        * ways (two bools of which one can be selected at a
-        * time) but at least it's shown in the 'Capture'
-        * category.
-        * I was told that this was due to backward compatibility,
-        * but I don't understand then why the mangling is *not*
-        * done when I name it "Input Source".....
-        */
-       .name = "Capture Source",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = tas_snd_capture_source_info,
-       .get = tas_snd_capture_source_get,
-       .put = tas_snd_capture_source_put,
-};
-
-static int tas_snd_treble_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-       uinfo->count = 1;
-       uinfo->value.integer.min = TAS3004_TREBLE_MIN;
-       uinfo->value.integer.max = TAS3004_TREBLE_MAX;
-       return 0;
-}
-
-static int tas_snd_treble_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       ucontrol->value.integer.value[0] = tas->treble;
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int tas_snd_treble_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       if (ucontrol->value.integer.value[0] < TAS3004_TREBLE_MIN ||
-           ucontrol->value.integer.value[0] > TAS3004_TREBLE_MAX)
-               return -EINVAL;
-       mutex_lock(&tas->mtx);
-       if (tas->treble == ucontrol->value.integer.value[0]) {
-               mutex_unlock(&tas->mtx);
-               return 0;
-       }
-
-       tas->treble = ucontrol->value.integer.value[0];
-       if (tas->hw_enabled)
-               tas_set_treble(tas);
-       mutex_unlock(&tas->mtx);
-       return 1;
-}
-
-static struct snd_kcontrol_new treble_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Treble",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = tas_snd_treble_info,
-       .get = tas_snd_treble_get,
-       .put = tas_snd_treble_put,
-};
-
-static int tas_snd_bass_info(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_info *uinfo)
-{
-       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-       uinfo->count = 1;
-       uinfo->value.integer.min = TAS3004_BASS_MIN;
-       uinfo->value.integer.max = TAS3004_BASS_MAX;
-       return 0;
-}
-
-static int tas_snd_bass_get(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       mutex_lock(&tas->mtx);
-       ucontrol->value.integer.value[0] = tas->bass;
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int tas_snd_bass_put(struct snd_kcontrol *kcontrol,
-       struct snd_ctl_elem_value *ucontrol)
-{
-       struct tas *tas = snd_kcontrol_chip(kcontrol);
-
-       if (ucontrol->value.integer.value[0] < TAS3004_BASS_MIN ||
-           ucontrol->value.integer.value[0] > TAS3004_BASS_MAX)
-               return -EINVAL;
-       mutex_lock(&tas->mtx);
-       if (tas->bass == ucontrol->value.integer.value[0]) {
-               mutex_unlock(&tas->mtx);
-               return 0;
-       }
-
-       tas->bass = ucontrol->value.integer.value[0];
-       if (tas->hw_enabled)
-               tas_set_bass(tas);
-       mutex_unlock(&tas->mtx);
-       return 1;
-}
-
-static struct snd_kcontrol_new bass_control = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Bass",
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .info = tas_snd_bass_info,
-       .get = tas_snd_bass_get,
-       .put = tas_snd_bass_put,
-};
-
-static struct transfer_info tas_transfers[] = {
-       {
-               /* input */
-               .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE,
-               .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
-               .transfer_in = 1,
-       },
-       {
-               /* output */
-               .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE,
-               .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
-               .transfer_in = 0,
-       },
-       {}
-};
-
-static int tas_usable(struct codec_info_item *cii,
-                     struct transfer_info *ti,
-                     struct transfer_info *out)
-{
-       return 1;
-}
-
-static int tas_reset_init(struct tas *tas)
-{
-       u8 tmp;
-
-       tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
-       msleep(5);
-       tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
-       msleep(5);
-       tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 1);
-       msleep(20);
-       tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
-       msleep(10);
-       tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
-
-       tmp = TAS_MCS_SCLK64 | TAS_MCS_SPORT_MODE_I2S | TAS_MCS_SPORT_WL_24BIT;
-       if (tas_write_reg(tas, TAS_REG_MCS, 1, &tmp))
-               goto outerr;
-
-       tas->acr |= TAS_ACR_ANALOG_PDOWN;
-       if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
-               goto outerr;
-
-       tmp = 0;
-       if (tas_write_reg(tas, TAS_REG_MCS2, 1, &tmp))
-               goto outerr;
-
-       tas3004_set_drc(tas);
-
-       /* Set treble & bass to 0dB */
-       tas->treble = TAS3004_TREBLE_ZERO;
-       tas->bass = TAS3004_BASS_ZERO;
-       tas_set_treble(tas);
-       tas_set_bass(tas);
-
-       tas->acr &= ~TAS_ACR_ANALOG_PDOWN;
-       if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
-               goto outerr;
-
-       return 0;
- outerr:
-       return -ENODEV;
-}
-
-static int tas_switch_clock(struct codec_info_item *cii, enum clock_switch clock)
-{
-       struct tas *tas = cii->codec_data;
-
-       switch(clock) {
-       case CLOCK_SWITCH_PREPARE_SLAVE:
-               /* Clocks are going away, mute mute mute */
-               tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
-               tas->hw_enabled = 0;
-               break;
-       case CLOCK_SWITCH_SLAVE:
-               /* Clocks are back, re-init the codec */
-               mutex_lock(&tas->mtx);
-               tas_reset_init(tas);
-               tas_set_volume(tas);
-               tas_set_mixer(tas);
-               tas->hw_enabled = 1;
-               tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
-               mutex_unlock(&tas->mtx);
-               break;
-       default:
-               /* doesn't happen as of now */
-               return -EINVAL;
-       }
-       return 0;
-}
-
-#ifdef CONFIG_PM
-/* we are controlled via i2c and assume that is always up
- * If that wasn't the case, we'd have to suspend once
- * our i2c device is suspended, and then take note of that! */
-static int tas_suspend(struct tas *tas)
-{
-       mutex_lock(&tas->mtx);
-       tas->hw_enabled = 0;
-       tas->acr |= TAS_ACR_ANALOG_PDOWN;
-       tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int tas_resume(struct tas *tas)
-{
-       /* reset codec */
-       mutex_lock(&tas->mtx);
-       tas_reset_init(tas);
-       tas_set_volume(tas);
-       tas_set_mixer(tas);
-       tas->hw_enabled = 1;
-       mutex_unlock(&tas->mtx);
-       return 0;
-}
-
-static int _tas_suspend(struct codec_info_item *cii, pm_message_t state)
-{
-       return tas_suspend(cii->codec_data);
-}
-
-static int _tas_resume(struct codec_info_item *cii)
-{
-       return tas_resume(cii->codec_data);
-}
-#else /* CONFIG_PM */
-#define _tas_suspend   NULL
-#define _tas_resume    NULL
-#endif /* CONFIG_PM */
-
-static struct codec_info tas_codec_info = {
-       .transfers = tas_transfers,
-       /* in theory, we can drive it at 512 too...
-        * but so far the framework doesn't allow
-        * for that and I don't see much point in it. */
-       .sysclock_factor = 256,
-       /* same here, could be 32 for just one 16 bit format */
-       .bus_factor = 64,
-       .owner = THIS_MODULE,
-       .usable = tas_usable,
-       .switch_clock = tas_switch_clock,
-       .suspend = _tas_suspend,
-       .resume = _tas_resume,
-};
-
-static int tas_init_codec(struct aoa_codec *codec)
-{
-       struct tas *tas = codec_to_tas(codec);
-       int err;
-
-       if (!tas->codec.gpio || !tas->codec.gpio->methods) {
-               printk(KERN_ERR PFX "gpios not assigned!!\n");
-               return -EINVAL;
-       }
-
-       mutex_lock(&tas->mtx);
-       if (tas_reset_init(tas)) {
-               printk(KERN_ERR PFX "tas failed to initialise\n");
-               mutex_unlock(&tas->mtx);
-               return -ENXIO;
-       }
-       tas->hw_enabled = 1;
-       mutex_unlock(&tas->mtx);
-
-       if (tas->codec.soundbus_dev->attach_codec(tas->codec.soundbus_dev,
-                                                  aoa_get_card(),
-                                                  &tas_codec_info, tas)) {
-               printk(KERN_ERR PFX "error attaching tas to soundbus\n");
-               return -ENODEV;
-       }
-
-       if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, tas, &ops)) {
-               printk(KERN_ERR PFX "failed to create tas snd device!\n");
-               return -ENODEV;
-       }
-       err = aoa_snd_ctl_add(snd_ctl_new1(&volume_control, tas));
-       if (err)
-               goto error;
-
-       err = aoa_snd_ctl_add(snd_ctl_new1(&mute_control, tas));
-       if (err)
-               goto error;
-
-       err = aoa_snd_ctl_add(snd_ctl_new1(&pcm1_control, tas));
-       if (err)
-               goto error;
-
-       err = aoa_snd_ctl_add(snd_ctl_new1(&monitor_control, tas));
-       if (err)
-               goto error;
-
-       err = aoa_snd_ctl_add(snd_ctl_new1(&capture_source_control, tas));
-       if (err)
-               goto error;
-
-       err = aoa_snd_ctl_add(snd_ctl_new1(&drc_range_control, tas));
-       if (err)
-               goto error;
-
-       err = aoa_snd_ctl_add(snd_ctl_new1(&drc_switch_control, tas));
-       if (err)
-               goto error;
-
-       err = aoa_snd_ctl_add(snd_ctl_new1(&treble_control, tas));
-       if (err)
-               goto error;
-
-       err = aoa_snd_ctl_add(snd_ctl_new1(&bass_control, tas));
-       if (err)
-               goto error;
-
-       return 0;
- error:
-       tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
-       snd_device_free(aoa_get_card(), tas);
-       return err;
-}
-
-static void tas_exit_codec(struct aoa_codec *codec)
-{
-       struct tas *tas = codec_to_tas(codec);
-
-       if (!tas->codec.soundbus_dev)
-               return;
-       tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
-}
-       
-
-static struct i2c_driver tas_driver;
-
-static int tas_create(struct i2c_adapter *adapter,
-                      struct device_node *node,
-                      int addr)
-{
-       struct tas *tas;
-
-       tas = kzalloc(sizeof(struct tas), GFP_KERNEL);
-
-       if (!tas)
-               return -ENOMEM;
-
-       mutex_init(&tas->mtx);
-       tas->i2c.driver = &tas_driver;
-       tas->i2c.adapter = adapter;
-       tas->i2c.addr = addr;
-       /* seems that half is a saner default */
-       tas->drc_range = TAS3004_DRC_MAX / 2;
-       strlcpy(tas->i2c.name, "tas audio codec", I2C_NAME_SIZE);
-
-       if (i2c_attach_client(&tas->i2c)) {
-               printk(KERN_ERR PFX "failed to attach to i2c\n");
-               goto fail;
-       }
-
-       strlcpy(tas->codec.name, "tas", MAX_CODEC_NAME_LEN);
-       tas->codec.owner = THIS_MODULE;
-       tas->codec.init = tas_init_codec;
-       tas->codec.exit = tas_exit_codec;
-       tas->codec.node = of_node_get(node);
-
-       if (aoa_codec_register(&tas->codec)) {
-               goto detach;
-       }
-       printk(KERN_DEBUG
-              "snd-aoa-codec-tas: tas found, addr 0x%02x on %s\n",
-              addr, node->full_name);
-       return 0;
- detach:
-       i2c_detach_client(&tas->i2c);
- fail:
-       mutex_destroy(&tas->mtx);
-       kfree(tas);
-       return -EINVAL;
-}
-
-static int tas_i2c_attach(struct i2c_adapter *adapter)
-{
-       struct device_node *busnode, *dev = NULL;
-       struct pmac_i2c_bus *bus;
-
-       bus = pmac_i2c_adapter_to_bus(adapter);
-       if (bus == NULL)
-               return -ENODEV;
-       busnode = pmac_i2c_get_bus_node(bus);
-
-       while ((dev = of_get_next_child(busnode, dev)) != NULL) {
-               if (of_device_is_compatible(dev, "tas3004")) {
-                       const u32 *addr;
-                       printk(KERN_DEBUG PFX "found tas3004\n");
-                       addr = of_get_property(dev, "reg", NULL);
-                       if (!addr)
-                               continue;
-                       return tas_create(adapter, dev, ((*addr) >> 1) & 0x7f);
-               }
-               /* older machines have no 'codec' node with a 'compatible'
-                * property that says 'tas3004', they just have a 'deq'
-                * node without any such property... */
-               if (strcmp(dev->name, "deq") == 0) {
-                       const u32 *_addr;
-                       u32 addr;
-                       printk(KERN_DEBUG PFX "found 'deq' node\n");
-                       _addr = of_get_property(dev, "i2c-address", NULL);
-                       if (!_addr)
-                               continue;
-                       addr = ((*_addr) >> 1) & 0x7f;
-                       /* now, if the address doesn't match any of the two
-                        * that a tas3004 can have, we cannot handle this.
-                        * I doubt it ever happens but hey. */
-                       if (addr != 0x34 && addr != 0x35)
-                               continue;
-                       return tas_create(adapter, dev, addr);
-               }
-       }
-       return -ENODEV;
-}
-
-static int tas_i2c_detach(struct i2c_client *client)
-{
-       struct tas *tas = container_of(client, struct tas, i2c);
-       int err;
-       u8 tmp = TAS_ACR_ANALOG_PDOWN;
-
-       if ((err = i2c_detach_client(client)))
-               return err;
-       aoa_codec_unregister(&tas->codec);
-       of_node_put(tas->codec.node);
-
-       /* power down codec chip */
-       tas_write_reg(tas, TAS_REG_ACR, 1, &tmp);
-
-       mutex_destroy(&tas->mtx);
-       kfree(tas);
-       return 0;
-}
-
-static struct i2c_driver tas_driver = {
-       .driver = {
-               .name = "aoa_codec_tas",
-               .owner = THIS_MODULE,
-       },
-       .attach_adapter = tas_i2c_attach,
-       .detach_client = tas_i2c_detach,
-};
-
-static int __init tas_init(void)
-{
-       return i2c_add_driver(&tas_driver);
-}
-
-static void __exit tas_exit(void)
-{
-       i2c_del_driver(&tas_driver);
-}
-
-module_init(tas_init);
-module_exit(tas_exit);
diff --git a/sound/aoa/codecs/snd-aoa-codec-tas.h b/sound/aoa/codecs/snd-aoa-codec-tas.h
deleted file mode 100644 (file)
index ae177e3..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Apple Onboard Audio driver for tas codec (header)
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-#ifndef __SND_AOA_CODECTASH
-#define __SND_AOA_CODECTASH
-
-#define TAS_REG_MCS    0x01    /* main control */
-#      define TAS_MCS_FASTLOAD         (1<<7)
-#      define TAS_MCS_SCLK64           (1<<6)
-#      define TAS_MCS_SPORT_MODE_MASK  (3<<4)
-#      define TAS_MCS_SPORT_MODE_I2S   (2<<4)
-#      define TAS_MCS_SPORT_MODE_RJ    (1<<4)
-#      define TAS_MCS_SPORT_MODE_LJ    (0<<4)
-#      define TAS_MCS_SPORT_WL_MASK    (3<<0)
-#      define TAS_MCS_SPORT_WL_16BIT   (0<<0)
-#      define TAS_MCS_SPORT_WL_18BIT   (1<<0)
-#      define TAS_MCS_SPORT_WL_20BIT   (2<<0)
-#      define TAS_MCS_SPORT_WL_24BIT   (3<<0)
-
-#define TAS_REG_DRC    0x02
-#define TAS_REG_VOL    0x04
-#define TAS_REG_TREBLE 0x05
-#define TAS_REG_BASS   0x06
-#define TAS_REG_LMIX   0x07
-#define TAS_REG_RMIX   0x08
-
-#define TAS_REG_ACR    0x40    /* analog control */
-#      define TAS_ACR_B_MONAUREAL      (1<<7)
-#      define TAS_ACR_B_MON_SEL_RIGHT  (1<<6)
-#      define TAS_ACR_DEEMPH_MASK      (3<<2)
-#      define TAS_ACR_DEEMPH_OFF       (0<<2)
-#      define TAS_ACR_DEEMPH_48KHz     (1<<2)
-#      define TAS_ACR_DEEMPH_44KHz     (2<<2)
-#      define TAS_ACR_INPUT_B          (1<<1)
-#      define TAS_ACR_ANALOG_PDOWN     (1<<0)
-
-#define TAS_REG_MCS2   0x43    /* main control 2 */
-#      define TAS_MCS2_ALLPASS         (1<<1)
-
-#define TAS_REG_LEFT_BIQUAD6   0x10
-#define TAS_REG_RIGHT_BIQUAD6  0x19
-
-#define TAS_REG_LEFT_LOUDNESS          0x21
-#define TAS_REG_RIGHT_LOUDNESS         0x22
-#define TAS_REG_LEFT_LOUDNESS_GAIN     0x23
-#define TAS_REG_RIGHT_LOUDNESS_GAIN    0x24
-
-#define TAS3001_DRC_MAX                0x5f
-#define TAS3004_DRC_MAX                0xef
-
-#endif /* __SND_AOA_CODECTASH */
diff --git a/sound/aoa/codecs/snd-aoa-codec-toonie.c b/sound/aoa/codecs/snd-aoa-codec-toonie.c
deleted file mode 100644 (file)
index 3c7d1d8..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Apple Onboard Audio driver for Toonie codec
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- *
- *
- * This is a driver for the toonie codec chip. This chip is present
- * on the Mac Mini and is nothing but a DAC.
- */
-#include <linux/delay.h>
-#include <linux/module.h>
-MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("toonie codec driver for snd-aoa");
-
-#include "../aoa.h"
-#include "../soundbus/soundbus.h"
-
-
-#define PFX "snd-aoa-codec-toonie: "
-
-struct toonie {
-       struct aoa_codec        codec;
-};
-#define codec_to_toonie(c) container_of(c, struct toonie, codec)
-
-static int toonie_dev_register(struct snd_device *dev)
-{
-       return 0;
-}
-
-static struct snd_device_ops ops = {
-       .dev_register = toonie_dev_register,
-};
-
-static struct transfer_info toonie_transfers[] = {
-       /* This thing *only* has analog output,
-        * the rates are taken from Info.plist
-        * from Darwin. */
-       {
-               .formats = SNDRV_PCM_FMTBIT_S16_BE |
-                          SNDRV_PCM_FMTBIT_S24_BE,
-               .rates = SNDRV_PCM_RATE_32000 |
-                        SNDRV_PCM_RATE_44100 |
-                        SNDRV_PCM_RATE_48000 |
-                        SNDRV_PCM_RATE_88200 |
-                        SNDRV_PCM_RATE_96000,
-       },
-       {}
-};
-
-static int toonie_usable(struct codec_info_item *cii,
-                        struct transfer_info *ti,
-                        struct transfer_info *out)
-{
-       return 1;
-}
-
-#ifdef CONFIG_PM
-static int toonie_suspend(struct codec_info_item *cii, pm_message_t state)
-{
-       /* can we turn it off somehow? */
-       return 0;
-}
-
-static int toonie_resume(struct codec_info_item *cii)
-{
-       return 0;
-}
-#endif /* CONFIG_PM */
-
-static struct codec_info toonie_codec_info = {
-       .transfers = toonie_transfers,
-       .sysclock_factor = 256,
-       .bus_factor = 64,
-       .owner = THIS_MODULE,
-       .usable = toonie_usable,
-#ifdef CONFIG_PM
-       .suspend = toonie_suspend,
-       .resume = toonie_resume,
-#endif
-};
-
-static int toonie_init_codec(struct aoa_codec *codec)
-{
-       struct toonie *toonie = codec_to_toonie(codec);
-
-       /* nothing connected? what a joke! */
-       if (toonie->codec.connected != 1)
-               return -ENOTCONN;
-
-       if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, toonie, &ops)) {
-               printk(KERN_ERR PFX "failed to create toonie snd device!\n");
-               return -ENODEV;
-       }
-
-       if (toonie->codec.soundbus_dev->attach_codec(toonie->codec.soundbus_dev,
-                                                    aoa_get_card(),
-                                                    &toonie_codec_info, toonie)) {
-               printk(KERN_ERR PFX "error creating toonie pcm\n");
-               snd_device_free(aoa_get_card(), toonie);
-               return -ENODEV;
-       }
-
-       return 0;
-}
-
-static void toonie_exit_codec(struct aoa_codec *codec)
-{
-       struct toonie *toonie = codec_to_toonie(codec);
-
-       if (!toonie->codec.soundbus_dev) {
-               printk(KERN_ERR PFX "toonie_exit_codec called without soundbus_dev!\n");
-               return;
-       }
-       toonie->codec.soundbus_dev->detach_codec(toonie->codec.soundbus_dev, toonie);
-}
-
-static struct toonie *toonie;
-
-static int __init toonie_init(void)
-{
-       toonie = kzalloc(sizeof(struct toonie), GFP_KERNEL);
-
-       if (!toonie)
-               return -ENOMEM;
-
-       strlcpy(toonie->codec.name, "toonie", sizeof(toonie->codec.name));
-       toonie->codec.owner = THIS_MODULE;
-       toonie->codec.init = toonie_init_codec;
-       toonie->codec.exit = toonie_exit_codec;
-                                        
-       if (aoa_codec_register(&toonie->codec)) {
-               kfree(toonie);
-               return -EINVAL;
-       }
-
-       return 0;
-}
-
-static void __exit toonie_exit(void)
-{
-       aoa_codec_unregister(&toonie->codec);
-       kfree(toonie);
-}
-
-module_init(toonie_init);
-module_exit(toonie_exit);
diff --git a/sound/aoa/codecs/tas-basstreble.h b/sound/aoa/codecs/tas-basstreble.h
new file mode 100644 (file)
index 0000000..69b6113
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * This file is only included exactly once!
+ *
+ * The tables here are derived from the tas3004 datasheet,
+ * modulo typo corrections and some smoothing...
+ */
+
+#define TAS3004_TREBLE_MIN     0
+#define TAS3004_TREBLE_MAX     72
+#define TAS3004_BASS_MIN       0
+#define TAS3004_BASS_MAX       72
+#define TAS3004_TREBLE_ZERO    36
+#define TAS3004_BASS_ZERO      36
+
+static u8 tas3004_treble_table[] = {
+       150, /* -18 dB */
+       149,
+       148,
+       147,
+       146,
+       145,
+       144,
+       143,
+       142,
+       141,
+       140,
+       139,
+       138,
+       137,
+       136,
+       135,
+       134,
+       133,
+       132,
+       131,
+       130,
+       129,
+       128,
+       127,
+       126,
+       125,
+       124,
+       123,
+       122,
+       121,
+       120,
+       119,
+       118,
+       117,
+       116,
+       115,
+       114, /* 0 dB */
+       113,
+       112,
+       111,
+       109,
+       108,
+       107,
+       105,
+       104,
+       103,
+       101,
+       99,
+       98,
+       96,
+       93,
+       91,
+       89,
+       86,
+       83,
+       81,
+       77,
+       74,
+       71,
+       67,
+       63,
+       59,
+       54,
+       49,
+       44,
+       38,
+       32,
+       26,
+       19,
+       10,
+       4,
+       2,
+       1, /* +18 dB */
+};
+
+static inline u8 tas3004_treble(int idx)
+{
+       return tas3004_treble_table[idx];
+}
+
+/* I only save the difference here to the treble table
+ * so that the binary is smaller...
+ * I have also ignored completely differences of
+ * +/- 1
+ */
+static s8 tas3004_bass_diff_to_treble[] = {
+       2, /* 7 dB, offset 50 */
+       2,
+       2,
+       2,
+       2,
+       1,
+       2,
+       2,
+       2,
+       3,
+       4,
+       4,
+       5,
+       6,
+       7,
+       8,
+       9,
+       10,
+       11,
+       14,
+       13,
+       8,
+       1, /* 18 dB */
+};
+
+static inline u8 tas3004_bass(int idx)
+{
+       u8 result = tas3004_treble_table[idx];
+
+       if (idx >= 50)
+               result += tas3004_bass_diff_to_treble[idx-50];
+       return result;
+}
diff --git a/sound/aoa/codecs/tas-gain-table.h b/sound/aoa/codecs/tas-gain-table.h
new file mode 100644 (file)
index 0000000..4cfa675
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ This is the program used to generate below table.
+
+#include <stdio.h>
+#include <math.h>
+int main() {
+  int dB2;
+  printf("/" "* This file is only included exactly once!\n");
+  printf(" *\n");
+  printf(" * If they'd only tell us that generating this table was\n");
+  printf(" * as easy as calculating\n");
+  printf(" *      hwvalue = 1048576.0*exp(0.057564628*dB*2)\n");
+  printf(" * :) *" "/\n");
+  printf("static int tas_gaintable[] = {\n");
+  printf("     0x000000, /" "* -infinity dB *" "/\n");
+  for (dB2=-140;dB2<=36;dB2++)
+    printf("   0x%.6x, /" "* %-02.1f dB *" "/\n", (int)(1048576.0*exp(0.057564628*dB2)), dB2/2.0);
+  printf("};\n\n");
+}
+
+*/
+
+/* This file is only included exactly once!
+ *
+ * If they'd only tell us that generating this table was
+ * as easy as calculating
+ *      hwvalue = 1048576.0*exp(0.057564628*dB*2)
+ * :) */
+static int tas_gaintable[] = {
+       0x000000, /* -infinity dB */
+       0x00014b, /* -70.0 dB */
+       0x00015f, /* -69.5 dB */
+       0x000174, /* -69.0 dB */
+       0x00018a, /* -68.5 dB */
+       0x0001a1, /* -68.0 dB */
+       0x0001ba, /* -67.5 dB */
+       0x0001d4, /* -67.0 dB */
+       0x0001f0, /* -66.5 dB */
+       0x00020d, /* -66.0 dB */
+       0x00022c, /* -65.5 dB */
+       0x00024d, /* -65.0 dB */
+       0x000270, /* -64.5 dB */
+       0x000295, /* -64.0 dB */
+       0x0002bc, /* -63.5 dB */
+       0x0002e6, /* -63.0 dB */
+       0x000312, /* -62.5 dB */
+       0x000340, /* -62.0 dB */
+       0x000372, /* -61.5 dB */
+       0x0003a6, /* -61.0 dB */
+       0x0003dd, /* -60.5 dB */
+       0x000418, /* -60.0 dB */
+       0x000456, /* -59.5 dB */
+       0x000498, /* -59.0 dB */
+       0x0004de, /* -58.5 dB */
+       0x000528, /* -58.0 dB */
+       0x000576, /* -57.5 dB */
+       0x0005c9, /* -57.0 dB */
+       0x000620, /* -56.5 dB */
+       0x00067d, /* -56.0 dB */
+       0x0006e0, /* -55.5 dB */
+       0x000748, /* -55.0 dB */
+       0x0007b7, /* -54.5 dB */
+       0x00082c, /* -54.0 dB */
+       0x0008a8, /* -53.5 dB */
+       0x00092b, /* -53.0 dB */
+       0x0009b6, /* -52.5 dB */
+       0x000a49, /* -52.0 dB */
+       0x000ae5, /* -51.5 dB */
+       0x000b8b, /* -51.0 dB */
+       0x000c3a, /* -50.5 dB */
+       0x000cf3, /* -50.0 dB */
+       0x000db8, /* -49.5 dB */
+       0x000e88, /* -49.0 dB */
+       0x000f64, /* -48.5 dB */
+       0x00104e, /* -48.0 dB */
+       0x001145, /* -47.5 dB */
+       0x00124b, /* -47.0 dB */
+       0x001361, /* -46.5 dB */
+       0x001487, /* -46.0 dB */
+       0x0015be, /* -45.5 dB */
+       0x001708, /* -45.0 dB */
+       0x001865, /* -44.5 dB */
+       0x0019d8, /* -44.0 dB */
+       0x001b60, /* -43.5 dB */
+       0x001cff, /* -43.0 dB */
+       0x001eb7, /* -42.5 dB */
+       0x002089, /* -42.0 dB */
+       0x002276, /* -41.5 dB */
+       0x002481, /* -41.0 dB */
+       0x0026ab, /* -40.5 dB */
+       0x0028f5, /* -40.0 dB */
+       0x002b63, /* -39.5 dB */
+       0x002df5, /* -39.0 dB */
+       0x0030ae, /* -38.5 dB */
+       0x003390, /* -38.0 dB */
+       0x00369e, /* -37.5 dB */
+       0x0039db, /* -37.0 dB */
+       0x003d49, /* -36.5 dB */
+       0x0040ea, /* -36.0 dB */
+       0x0044c3, /* -35.5 dB */
+       0x0048d6, /* -35.0 dB */
+       0x004d27, /* -34.5 dB */
+       0x0051b9, /* -34.0 dB */
+       0x005691, /* -33.5 dB */
+       0x005bb2, /* -33.0 dB */
+       0x006121, /* -32.5 dB */
+       0x0066e3, /* -32.0 dB */
+       0x006cfb, /* -31.5 dB */
+       0x007370, /* -31.0 dB */
+       0x007a48, /* -30.5 dB */
+       0x008186, /* -30.0 dB */
+       0x008933, /* -29.5 dB */
+       0x009154, /* -29.0 dB */
+       0x0099f1, /* -28.5 dB */
+       0x00a310, /* -28.0 dB */
+       0x00acba, /* -27.5 dB */
+       0x00b6f6, /* -27.0 dB */
+       0x00c1cd, /* -26.5 dB */
+       0x00cd49, /* -26.0 dB */
+       0x00d973, /* -25.5 dB */
+       0x00e655, /* -25.0 dB */
+       0x00f3fb, /* -24.5 dB */
+       0x010270, /* -24.0 dB */
+       0x0111c0, /* -23.5 dB */
+       0x0121f9, /* -23.0 dB */
+       0x013328, /* -22.5 dB */
+       0x01455b, /* -22.0 dB */
+       0x0158a2, /* -21.5 dB */
+       0x016d0e, /* -21.0 dB */
+       0x0182af, /* -20.5 dB */
+       0x019999, /* -20.0 dB */
+       0x01b1de, /* -19.5 dB */
+       0x01cb94, /* -19.0 dB */
+       0x01e6cf, /* -18.5 dB */
+       0x0203a7, /* -18.0 dB */
+       0x022235, /* -17.5 dB */
+       0x024293, /* -17.0 dB */
+       0x0264db, /* -16.5 dB */
+       0x02892c, /* -16.0 dB */
+       0x02afa3, /* -15.5 dB */
+       0x02d862, /* -15.0 dB */
+       0x03038a, /* -14.5 dB */
+       0x033142, /* -14.0 dB */
+       0x0361af, /* -13.5 dB */
+       0x0394fa, /* -13.0 dB */
+       0x03cb50, /* -12.5 dB */
+       0x0404de, /* -12.0 dB */
+       0x0441d5, /* -11.5 dB */
+       0x048268, /* -11.0 dB */
+       0x04c6d0, /* -10.5 dB */
+       0x050f44, /* -10.0 dB */
+       0x055c04, /* -9.5 dB */
+       0x05ad50, /* -9.0 dB */
+       0x06036e, /* -8.5 dB */
+       0x065ea5, /* -8.0 dB */
+       0x06bf44, /* -7.5 dB */
+       0x07259d, /* -7.0 dB */
+       0x079207, /* -6.5 dB */
+       0x0804dc, /* -6.0 dB */
+       0x087e80, /* -5.5 dB */
+       0x08ff59, /* -5.0 dB */
+       0x0987d5, /* -4.5 dB */
+       0x0a1866, /* -4.0 dB */
+       0x0ab189, /* -3.5 dB */
+       0x0b53be, /* -3.0 dB */
+       0x0bff91, /* -2.5 dB */
+       0x0cb591, /* -2.0 dB */
+       0x0d765a, /* -1.5 dB */
+       0x0e4290, /* -1.0 dB */
+       0x0f1adf, /* -0.5 dB */
+       0x100000, /* 0.0 dB */
+       0x10f2b4, /* 0.5 dB */
+       0x11f3c9, /* 1.0 dB */
+       0x13041a, /* 1.5 dB */
+       0x14248e, /* 2.0 dB */
+       0x15561a, /* 2.5 dB */
+       0x1699c0, /* 3.0 dB */
+       0x17f094, /* 3.5 dB */
+       0x195bb8, /* 4.0 dB */
+       0x1adc61, /* 4.5 dB */
+       0x1c73d5, /* 5.0 dB */
+       0x1e236d, /* 5.5 dB */
+       0x1fec98, /* 6.0 dB */
+       0x21d0d9, /* 6.5 dB */
+       0x23d1cd, /* 7.0 dB */
+       0x25f125, /* 7.5 dB */
+       0x2830af, /* 8.0 dB */
+       0x2a9254, /* 8.5 dB */
+       0x2d1818, /* 9.0 dB */
+       0x2fc420, /* 9.5 dB */
+       0x3298b0, /* 10.0 dB */
+       0x35982f, /* 10.5 dB */
+       0x38c528, /* 11.0 dB */
+       0x3c224c, /* 11.5 dB */
+       0x3fb278, /* 12.0 dB */
+       0x4378b0, /* 12.5 dB */
+       0x477829, /* 13.0 dB */
+       0x4bb446, /* 13.5 dB */
+       0x5030a1, /* 14.0 dB */
+       0x54f106, /* 14.5 dB */
+       0x59f980, /* 15.0 dB */
+       0x5f4e52, /* 15.5 dB */
+       0x64f403, /* 16.0 dB */
+       0x6aef5e, /* 16.5 dB */
+       0x714575, /* 17.0 dB */
+       0x77fbaa, /* 17.5 dB */
+       0x7f17af, /* 18.0 dB */
+};
+
diff --git a/sound/aoa/codecs/tas.c b/sound/aoa/codecs/tas.c
new file mode 100644 (file)
index 0000000..008e0f8
--- /dev/null
@@ -0,0 +1,1012 @@
+/*
+ * Apple Onboard Audio driver for tas codec
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ * Open questions:
+ *  - How to distinguish between 3004 and versions?
+ *
+ * FIXMEs:
+ *  - This codec driver doesn't honour the 'connected'
+ *    property of the aoa_codec struct, hence if
+ *    it is used in machines where not everything is
+ *    connected it will display wrong mixer elements.
+ *  - Driver assumes that the microphone is always
+ *    monaureal and connected to the right channel of
+ *    the input. This should also be a codec-dependent
+ *    flag, maybe the codec should have 3 different
+ *    bits for the three different possibilities how
+ *    it can be hooked up...
+ *    But as long as I don't see any hardware hooked
+ *    up that way...
+ *  - As Apple notes in their code, the tas3004 seems
+ *    to delay the right channel by one sample. You can
+ *    see this when for example recording stereo in
+ *    audacity, or recording the tas output via cable
+ *    on another machine (use a sinus generator or so).
+ *    I tried programming the BiQuads but couldn't
+ *    make the delay work, maybe someone can read the
+ *    datasheet and fix it. The relevant Apple comment
+ *    is in AppleTAS3004Audio.cpp lines 1637 ff. Note
+ *    that their comment describing how they program
+ *    the filters sucks...
+ *
+ * Other things:
+ *  - this should actually register *two* aoa_codec
+ *    structs since it has two inputs. Then it must
+ *    use the prepare callback to forbid running the
+ *    secondary output on a different clock.
+ *    Also, whatever bus knows how to do this must
+ *    provide two soundbus_dev devices and the fabric
+ *    must be able to link them correctly.
+ *
+ *    I don't even know if Apple ever uses the second
+ *    port on the tas3004 though, I don't think their
+ *    i2s controllers can even do it. OTOH, they all
+ *    derive the clocks from common clocks, so it
+ *    might just be possible. The framework allows the
+ *    codec to refine the transfer_info items in the
+ *    usable callback, so we can simply remove the
+ *    rates the second instance is not using when it
+ *    actually is in use.
+ *    Maybe we'll need to make the sound busses have
+ *    a 'clock group id' value so the codec can
+ *    determine if the two outputs can be driven at
+ *    the same time. But that is likely overkill, up
+ *    to the fabric to not link them up incorrectly,
+ *    and up to the hardware designer to not wire
+ *    them up in some weird unusable way.
+ */
+#include <stddef.h>
+#include <linux/i2c.h>
+#include <asm/pmac_low_i2c.h>
+#include <asm/prom.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("tas codec driver for snd-aoa");
+
+#include "tas.h"
+#include "tas-gain-table.h"
+#include "tas-basstreble.h"
+#include "../aoa.h"
+#include "../soundbus/soundbus.h"
+
+#define PFX "snd-aoa-codec-tas: "
+
+
+struct tas {
+       struct aoa_codec        codec;
+       struct i2c_client       i2c;
+       u32                     mute_l:1, mute_r:1 ,
+                               controls_created:1 ,
+                               drc_enabled:1,
+                               hw_enabled:1;
+       u8                      cached_volume_l, cached_volume_r;
+       u8                      mixer_l[3], mixer_r[3];
+       u8                      bass, treble;
+       u8                      acr;
+       int                     drc_range;
+       /* protects hardware access against concurrency from
+        * userspace when hitting controls and during
+        * codec init/suspend/resume */
+       struct mutex            mtx;
+};
+
+static int tas_reset_init(struct tas *tas);
+
+static struct tas *codec_to_tas(struct aoa_codec *codec)
+{
+       return container_of(codec, struct tas, codec);
+}
+
+static inline int tas_write_reg(struct tas *tas, u8 reg, u8 len, u8 *data)
+{
+       if (len == 1)
+               return i2c_smbus_write_byte_data(&tas->i2c, reg, *data);
+       else
+               return i2c_smbus_write_i2c_block_data(&tas->i2c, reg, len, data);
+}
+
+static void tas3004_set_drc(struct tas *tas)
+{
+       unsigned char val[6];
+
+       if (tas->drc_enabled)
+               val[0] = 0x50; /* 3:1 above threshold */
+       else
+               val[0] = 0x51; /* disabled */
+       val[1] = 0x02; /* 1:1 below threshold */
+       if (tas->drc_range > 0xef)
+               val[2] = 0xef;
+       else if (tas->drc_range < 0)
+               val[2] = 0x00;
+       else
+               val[2] = tas->drc_range;
+       val[3] = 0xb0;
+       val[4] = 0x60;
+       val[5] = 0xa0;
+
+       tas_write_reg(tas, TAS_REG_DRC, 6, val);
+}
+
+static void tas_set_treble(struct tas *tas)
+{
+       u8 tmp;
+
+       tmp = tas3004_treble(tas->treble);
+       tas_write_reg(tas, TAS_REG_TREBLE, 1, &tmp);
+}
+
+static void tas_set_bass(struct tas *tas)
+{
+       u8 tmp;
+
+       tmp = tas3004_bass(tas->bass);
+       tas_write_reg(tas, TAS_REG_BASS, 1, &tmp);
+}
+
+static void tas_set_volume(struct tas *tas)
+{
+       u8 block[6];
+       int tmp;
+       u8 left, right;
+
+       left = tas->cached_volume_l;
+       right = tas->cached_volume_r;
+
+       if (left > 177) left = 177;
+       if (right > 177) right = 177;
+
+       if (tas->mute_l) left = 0;
+       if (tas->mute_r) right = 0;
+
+       /* analysing the volume and mixer tables shows
+        * that they are similar enough when we shift
+        * the mixer table down by 4 bits. The error
+        * is miniscule, in just one item the error
+        * is 1, at a value of 0x07f17b (mixer table
+        * value is 0x07f17a) */
+       tmp = tas_gaintable[left];
+       block[0] = tmp>>20;
+       block[1] = tmp>>12;
+       block[2] = tmp>>4;
+       tmp = tas_gaintable[right];
+       block[3] = tmp>>20;
+       block[4] = tmp>>12;
+       block[5] = tmp>>4;
+       tas_write_reg(tas, TAS_REG_VOL, 6, block);
+}
+
+static void tas_set_mixer(struct tas *tas)
+{
+       u8 block[9];
+       int tmp, i;
+       u8 val;
+
+       for (i=0;i<3;i++) {
+               val = tas->mixer_l[i];
+               if (val > 177) val = 177;
+               tmp = tas_gaintable[val];
+               block[3*i+0] = tmp>>16;
+               block[3*i+1] = tmp>>8;
+               block[3*i+2] = tmp;
+       }
+       tas_write_reg(tas, TAS_REG_LMIX, 9, block);
+
+       for (i=0;i<3;i++) {
+               val = tas->mixer_r[i];
+               if (val > 177) val = 177;
+               tmp = tas_gaintable[val];
+               block[3*i+0] = tmp>>16;
+               block[3*i+1] = tmp>>8;
+               block[3*i+2] = tmp;
+       }
+       tas_write_reg(tas, TAS_REG_RMIX, 9, block);
+}
+
+/* alsa stuff */
+
+static int tas_dev_register(struct snd_device *dev)
+{
+       return 0;
+}
+
+static struct snd_device_ops ops = {
+       .dev_register = tas_dev_register,
+};
+
+static int tas_snd_vol_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 2;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 177;
+       return 0;
+}
+
+static int tas_snd_vol_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->cached_volume_l;
+       ucontrol->value.integer.value[1] = tas->cached_volume_r;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_vol_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       if (ucontrol->value.integer.value[0] < 0 ||
+           ucontrol->value.integer.value[0] > 177)
+               return -EINVAL;
+       if (ucontrol->value.integer.value[1] < 0 ||
+           ucontrol->value.integer.value[1] > 177)
+               return -EINVAL;
+
+       mutex_lock(&tas->mtx);
+       if (tas->cached_volume_l == ucontrol->value.integer.value[0]
+        && tas->cached_volume_r == ucontrol->value.integer.value[1]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->cached_volume_l = ucontrol->value.integer.value[0];
+       tas->cached_volume_r = ucontrol->value.integer.value[1];
+       if (tas->hw_enabled)
+               tas_set_volume(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new volume_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Master Playback Volume",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_vol_info,
+       .get = tas_snd_vol_get,
+       .put = tas_snd_vol_put,
+};
+
+#define tas_snd_mute_info      snd_ctl_boolean_stereo_info
+
+static int tas_snd_mute_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = !tas->mute_l;
+       ucontrol->value.integer.value[1] = !tas->mute_r;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_mute_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       if (tas->mute_l == !ucontrol->value.integer.value[0]
+        && tas->mute_r == !ucontrol->value.integer.value[1]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->mute_l = !ucontrol->value.integer.value[0];
+       tas->mute_r = !ucontrol->value.integer.value[1];
+       if (tas->hw_enabled)
+               tas_set_volume(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new mute_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Master Playback Switch",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_mute_info,
+       .get = tas_snd_mute_get,
+       .put = tas_snd_mute_put,
+};
+
+static int tas_snd_mixer_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 2;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 177;
+       return 0;
+}
+
+static int tas_snd_mixer_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+       int idx = kcontrol->private_value;
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->mixer_l[idx];
+       ucontrol->value.integer.value[1] = tas->mixer_r[idx];
+       mutex_unlock(&tas->mtx);
+
+       return 0;
+}
+
+static int tas_snd_mixer_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+       int idx = kcontrol->private_value;
+
+       mutex_lock(&tas->mtx);
+       if (tas->mixer_l[idx] == ucontrol->value.integer.value[0]
+        && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->mixer_l[idx] = ucontrol->value.integer.value[0];
+       tas->mixer_r[idx] = ucontrol->value.integer.value[1];
+
+       if (tas->hw_enabled)
+               tas_set_mixer(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+#define MIXER_CONTROL(n,descr,idx)                     \
+static struct snd_kcontrol_new n##_control = {         \
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,            \
+       .name = descr " Playback Volume",               \
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,      \
+       .info = tas_snd_mixer_info,                     \
+       .get = tas_snd_mixer_get,                       \
+       .put = tas_snd_mixer_put,                       \
+       .private_value = idx,                           \
+}
+
+MIXER_CONTROL(pcm1, "PCM", 0);
+MIXER_CONTROL(monitor, "Monitor", 2);
+
+static int tas_snd_drc_range_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = TAS3004_DRC_MAX;
+       return 0;
+}
+
+static int tas_snd_drc_range_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->drc_range;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_drc_range_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       if (ucontrol->value.integer.value[0] < 0 ||
+           ucontrol->value.integer.value[0] > TAS3004_DRC_MAX)
+               return -EINVAL;
+
+       mutex_lock(&tas->mtx);
+       if (tas->drc_range == ucontrol->value.integer.value[0]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->drc_range = ucontrol->value.integer.value[0];
+       if (tas->hw_enabled)
+               tas3004_set_drc(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new drc_range_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "DRC Range",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_drc_range_info,
+       .get = tas_snd_drc_range_get,
+       .put = tas_snd_drc_range_put,
+};
+
+#define tas_snd_drc_switch_info                snd_ctl_boolean_mono_info
+
+static int tas_snd_drc_switch_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->drc_enabled;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_drc_switch_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       if (tas->drc_enabled == ucontrol->value.integer.value[0]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->drc_enabled = !!ucontrol->value.integer.value[0];
+       if (tas->hw_enabled)
+               tas3004_set_drc(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new drc_switch_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "DRC Range Switch",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_drc_switch_info,
+       .get = tas_snd_drc_switch_get,
+       .put = tas_snd_drc_switch_put,
+};
+
+static int tas_snd_capture_source_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       static char *texts[] = { "Line-In", "Microphone" };
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->count = 1;
+       uinfo->value.enumerated.items = 2;
+       if (uinfo->value.enumerated.item > 1)
+               uinfo->value.enumerated.item = 1;
+       strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+       return 0;
+}
+
+static int tas_snd_capture_source_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B);
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_capture_source_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+       int oldacr;
+
+       if (ucontrol->value.enumerated.item[0] > 1)
+               return -EINVAL;
+       mutex_lock(&tas->mtx);
+       oldacr = tas->acr;
+
+       /*
+        * Despite what the data sheet says in one place, the
+        * TAS_ACR_B_MONAUREAL bit forces mono output even when
+        * input A (line in) is selected.
+        */
+       tas->acr &= ~(TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL);
+       if (ucontrol->value.enumerated.item[0])
+               tas->acr |= TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL |
+                     TAS_ACR_B_MON_SEL_RIGHT;
+       if (oldacr == tas->acr) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+       if (tas->hw_enabled)
+               tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new capture_source_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       /* If we name this 'Input Source', it properly shows up in
+        * alsamixer as a selection, * but it's shown under the
+        * 'Playback' category.
+        * If I name it 'Capture Source', it shows up in strange
+        * ways (two bools of which one can be selected at a
+        * time) but at least it's shown in the 'Capture'
+        * category.
+        * I was told that this was due to backward compatibility,
+        * but I don't understand then why the mangling is *not*
+        * done when I name it "Input Source".....
+        */
+       .name = "Capture Source",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_capture_source_info,
+       .get = tas_snd_capture_source_get,
+       .put = tas_snd_capture_source_put,
+};
+
+static int tas_snd_treble_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = TAS3004_TREBLE_MIN;
+       uinfo->value.integer.max = TAS3004_TREBLE_MAX;
+       return 0;
+}
+
+static int tas_snd_treble_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->treble;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_treble_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       if (ucontrol->value.integer.value[0] < TAS3004_TREBLE_MIN ||
+           ucontrol->value.integer.value[0] > TAS3004_TREBLE_MAX)
+               return -EINVAL;
+       mutex_lock(&tas->mtx);
+       if (tas->treble == ucontrol->value.integer.value[0]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->treble = ucontrol->value.integer.value[0];
+       if (tas->hw_enabled)
+               tas_set_treble(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new treble_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Treble",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_treble_info,
+       .get = tas_snd_treble_get,
+       .put = tas_snd_treble_put,
+};
+
+static int tas_snd_bass_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = TAS3004_BASS_MIN;
+       uinfo->value.integer.max = TAS3004_BASS_MAX;
+       return 0;
+}
+
+static int tas_snd_bass_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->bass;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_bass_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       if (ucontrol->value.integer.value[0] < TAS3004_BASS_MIN ||
+           ucontrol->value.integer.value[0] > TAS3004_BASS_MAX)
+               return -EINVAL;
+       mutex_lock(&tas->mtx);
+       if (tas->bass == ucontrol->value.integer.value[0]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->bass = ucontrol->value.integer.value[0];
+       if (tas->hw_enabled)
+               tas_set_bass(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new bass_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Bass",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_bass_info,
+       .get = tas_snd_bass_get,
+       .put = tas_snd_bass_put,
+};
+
+static struct transfer_info tas_transfers[] = {
+       {
+               /* input */
+               .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE,
+               .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+               .transfer_in = 1,
+       },
+       {
+               /* output */
+               .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE,
+               .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+               .transfer_in = 0,
+       },
+       {}
+};
+
+static int tas_usable(struct codec_info_item *cii,
+                     struct transfer_info *ti,
+                     struct transfer_info *out)
+{
+       return 1;
+}
+
+static int tas_reset_init(struct tas *tas)
+{
+       u8 tmp;
+
+       tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
+       msleep(5);
+       tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
+       msleep(5);
+       tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 1);
+       msleep(20);
+       tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
+       msleep(10);
+       tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
+
+       tmp = TAS_MCS_SCLK64 | TAS_MCS_SPORT_MODE_I2S | TAS_MCS_SPORT_WL_24BIT;
+       if (tas_write_reg(tas, TAS_REG_MCS, 1, &tmp))
+               goto outerr;
+
+       tas->acr |= TAS_ACR_ANALOG_PDOWN;
+       if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
+               goto outerr;
+
+       tmp = 0;
+       if (tas_write_reg(tas, TAS_REG_MCS2, 1, &tmp))
+               goto outerr;
+
+       tas3004_set_drc(tas);
+
+       /* Set treble & bass to 0dB */
+       tas->treble = TAS3004_TREBLE_ZERO;
+       tas->bass = TAS3004_BASS_ZERO;
+       tas_set_treble(tas);
+       tas_set_bass(tas);
+
+       tas->acr &= ~TAS_ACR_ANALOG_PDOWN;
+       if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
+               goto outerr;
+
+       return 0;
+ outerr:
+       return -ENODEV;
+}
+
+static int tas_switch_clock(struct codec_info_item *cii, enum clock_switch clock)
+{
+       struct tas *tas = cii->codec_data;
+
+       switch(clock) {
+       case CLOCK_SWITCH_PREPARE_SLAVE:
+               /* Clocks are going away, mute mute mute */
+               tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
+               tas->hw_enabled = 0;
+               break;
+       case CLOCK_SWITCH_SLAVE:
+               /* Clocks are back, re-init the codec */
+               mutex_lock(&tas->mtx);
+               tas_reset_init(tas);
+               tas_set_volume(tas);
+               tas_set_mixer(tas);
+               tas->hw_enabled = 1;
+               tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
+               mutex_unlock(&tas->mtx);
+               break;
+       default:
+               /* doesn't happen as of now */
+               return -EINVAL;
+       }
+       return 0;
+}
+
+#ifdef CONFIG_PM
+/* we are controlled via i2c and assume that is always up
+ * If that wasn't the case, we'd have to suspend once
+ * our i2c device is suspended, and then take note of that! */
+static int tas_suspend(struct tas *tas)
+{
+       mutex_lock(&tas->mtx);
+       tas->hw_enabled = 0;
+       tas->acr |= TAS_ACR_ANALOG_PDOWN;
+       tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_resume(struct tas *tas)
+{
+       /* reset codec */
+       mutex_lock(&tas->mtx);
+       tas_reset_init(tas);
+       tas_set_volume(tas);
+       tas_set_mixer(tas);
+       tas->hw_enabled = 1;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int _tas_suspend(struct codec_info_item *cii, pm_message_t state)
+{
+       return tas_suspend(cii->codec_data);
+}
+
+static int _tas_resume(struct codec_info_item *cii)
+{
+       return tas_resume(cii->codec_data);
+}
+#else /* CONFIG_PM */
+#define _tas_suspend   NULL
+#define _tas_resume    NULL
+#endif /* CONFIG_PM */
+
+static struct codec_info tas_codec_info = {
+       .transfers = tas_transfers,
+       /* in theory, we can drive it at 512 too...
+        * but so far the framework doesn't allow
+        * for that and I don't see much point in it. */
+       .sysclock_factor = 256,
+       /* same here, could be 32 for just one 16 bit format */
+       .bus_factor = 64,
+       .owner = THIS_MODULE,
+       .usable = tas_usable,
+       .switch_clock = tas_switch_clock,
+       .suspend = _tas_suspend,
+       .resume = _tas_resume,
+};
+
+static int tas_init_codec(struct aoa_codec *codec)
+{
+       struct tas *tas = codec_to_tas(codec);
+       int err;
+
+       if (!tas->codec.gpio || !tas->codec.gpio->methods) {
+               printk(KERN_ERR PFX "gpios not assigned!!\n");
+               return -EINVAL;
+       }
+
+       mutex_lock(&tas->mtx);
+       if (tas_reset_init(tas)) {
+               printk(KERN_ERR PFX "tas failed to initialise\n");
+               mutex_unlock(&tas->mtx);
+               return -ENXIO;
+       }
+       tas->hw_enabled = 1;
+       mutex_unlock(&tas->mtx);
+
+       if (tas->codec.soundbus_dev->attach_codec(tas->codec.soundbus_dev,
+                                                  aoa_get_card(),
+                                                  &tas_codec_info, tas)) {
+               printk(KERN_ERR PFX "error attaching tas to soundbus\n");
+               return -ENODEV;
+       }
+
+       if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, tas, &ops)) {
+               printk(KERN_ERR PFX "failed to create tas snd device!\n");
+               return -ENODEV;
+       }
+       err = aoa_snd_ctl_add(snd_ctl_new1(&volume_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&mute_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&pcm1_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&monitor_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&capture_source_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&drc_range_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&drc_switch_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&treble_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&bass_control, tas));
+       if (err)
+               goto error;
+
+       return 0;
+ error:
+       tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
+       snd_device_free(aoa_get_card(), tas);
+       return err;
+}
+
+static void tas_exit_codec(struct aoa_codec *codec)
+{
+       struct tas *tas = codec_to_tas(codec);
+
+       if (!tas->codec.soundbus_dev)
+               return;
+       tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
+}
+
+
+static struct i2c_driver tas_driver;
+
+static int tas_create(struct i2c_adapter *adapter,
+                      struct device_node *node,
+                      int addr)
+{
+       struct tas *tas;
+
+       tas = kzalloc(sizeof(struct tas), GFP_KERNEL);
+
+       if (!tas)
+               return -ENOMEM;
+
+       mutex_init(&tas->mtx);
+       tas->i2c.driver = &tas_driver;
+       tas->i2c.adapter = adapter;
+       tas->i2c.addr = addr;
+       /* seems that half is a saner default */
+       tas->drc_range = TAS3004_DRC_MAX / 2;
+       strlcpy(tas->i2c.name, "tas audio codec", I2C_NAME_SIZE);
+
+       if (i2c_attach_client(&tas->i2c)) {
+               printk(KERN_ERR PFX "failed to attach to i2c\n");
+               goto fail;
+       }
+
+       strlcpy(tas->codec.name, "tas", MAX_CODEC_NAME_LEN);
+       tas->codec.owner = THIS_MODULE;
+       tas->codec.init = tas_init_codec;
+       tas->codec.exit = tas_exit_codec;
+       tas->codec.node = of_node_get(node);
+
+       if (aoa_codec_register(&tas->codec)) {
+               goto detach;
+       }
+       printk(KERN_DEBUG
+              "snd-aoa-codec-tas: tas found, addr 0x%02x on %s\n",
+              addr, node->full_name);
+       return 0;
+ detach:
+       i2c_detach_client(&tas->i2c);
+ fail:
+       mutex_destroy(&tas->mtx);
+       kfree(tas);
+       return -EINVAL;
+}
+
+static int tas_i2c_attach(struct i2c_adapter *adapter)
+{
+       struct device_node *busnode, *dev = NULL;
+       struct pmac_i2c_bus *bus;
+
+       bus = pmac_i2c_adapter_to_bus(adapter);
+       if (bus == NULL)
+               return -ENODEV;
+       busnode = pmac_i2c_get_bus_node(bus);
+
+       while ((dev = of_get_next_child(busnode, dev)) != NULL) {
+               if (of_device_is_compatible(dev, "tas3004")) {
+                       const u32 *addr;
+                       printk(KERN_DEBUG PFX "found tas3004\n");
+                       addr = of_get_property(dev, "reg", NULL);
+                       if (!addr)
+                               continue;
+                       return tas_create(adapter, dev, ((*addr) >> 1) & 0x7f);
+               }
+               /* older machines have no 'codec' node with a 'compatible'
+                * property that says 'tas3004', they just have a 'deq'
+                * node without any such property... */
+               if (strcmp(dev->name, "deq") == 0) {
+                       const u32 *_addr;
+                       u32 addr;
+                       printk(KERN_DEBUG PFX "found 'deq' node\n");
+                       _addr = of_get_property(dev, "i2c-address", NULL);
+                       if (!_addr)
+                               continue;
+                       addr = ((*_addr) >> 1) & 0x7f;
+                       /* now, if the address doesn't match any of the two
+                        * that a tas3004 can have, we cannot handle this.
+                        * I doubt it ever happens but hey. */
+                       if (addr != 0x34 && addr != 0x35)
+                               continue;
+                       return tas_create(adapter, dev, addr);
+               }
+       }
+       return -ENODEV;
+}
+
+static int tas_i2c_detach(struct i2c_client *client)
+{
+       struct tas *tas = container_of(client, struct tas, i2c);
+       int err;
+       u8 tmp = TAS_ACR_ANALOG_PDOWN;
+
+       if ((err = i2c_detach_client(client)))
+               return err;
+       aoa_codec_unregister(&tas->codec);
+       of_node_put(tas->codec.node);
+
+       /* power down codec chip */
+       tas_write_reg(tas, TAS_REG_ACR, 1, &tmp);
+
+       mutex_destroy(&tas->mtx);
+       kfree(tas);
+       return 0;
+}
+
+static struct i2c_driver tas_driver = {
+       .driver = {
+               .name = "aoa_codec_tas",
+               .owner = THIS_MODULE,
+       },
+       .attach_adapter = tas_i2c_attach,
+       .detach_client = tas_i2c_detach,
+};
+
+static int __init tas_init(void)
+{
+       return i2c_add_driver(&tas_driver);
+}
+
+static void __exit tas_exit(void)
+{
+       i2c_del_driver(&tas_driver);
+}
+
+module_init(tas_init);
+module_exit(tas_exit);
diff --git a/sound/aoa/codecs/tas.h b/sound/aoa/codecs/tas.h
new file mode 100644 (file)
index 0000000..ae177e3
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Apple Onboard Audio driver for tas codec (header)
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#ifndef __SND_AOA_CODECTASH
+#define __SND_AOA_CODECTASH
+
+#define TAS_REG_MCS    0x01    /* main control */
+#      define TAS_MCS_FASTLOAD         (1<<7)
+#      define TAS_MCS_SCLK64           (1<<6)
+#      define TAS_MCS_SPORT_MODE_MASK  (3<<4)
+#      define TAS_MCS_SPORT_MODE_I2S   (2<<4)
+#      define TAS_MCS_SPORT_MODE_RJ    (1<<4)
+#      define TAS_MCS_SPORT_MODE_LJ    (0<<4)
+#      define TAS_MCS_SPORT_WL_MASK    (3<<0)
+#      define TAS_MCS_SPORT_WL_16BIT   (0<<0)
+#      define TAS_MCS_SPORT_WL_18BIT   (1<<0)
+#      define TAS_MCS_SPORT_WL_20BIT   (2<<0)
+#      define TAS_MCS_SPORT_WL_24BIT   (3<<0)
+
+#define TAS_REG_DRC    0x02
+#define TAS_REG_VOL    0x04
+#define TAS_REG_TREBLE 0x05
+#define TAS_REG_BASS   0x06
+#define TAS_REG_LMIX   0x07
+#define TAS_REG_RMIX   0x08
+
+#define TAS_REG_ACR    0x40    /* analog control */
+#      define TAS_ACR_B_MONAUREAL      (1<<7)
+#      define TAS_ACR_B_MON_SEL_RIGHT  (1<<6)
+#      define TAS_ACR_DEEMPH_MASK      (3<<2)
+#      define TAS_ACR_DEEMPH_OFF       (0<<2)
+#      define TAS_ACR_DEEMPH_48KHz     (1<<2)
+#      define TAS_ACR_DEEMPH_44KHz     (2<<2)
+#      define TAS_ACR_INPUT_B          (1<<1)
+#      define TAS_ACR_ANALOG_PDOWN     (1<<0)
+
+#define TAS_REG_MCS2   0x43    /* main control 2 */
+#      define TAS_MCS2_ALLPASS         (1<<1)
+
+#define TAS_REG_LEFT_BIQUAD6   0x10
+#define TAS_REG_RIGHT_BIQUAD6  0x19
+
+#define TAS_REG_LEFT_LOUDNESS          0x21
+#define TAS_REG_RIGHT_LOUDNESS         0x22
+#define TAS_REG_LEFT_LOUDNESS_GAIN     0x23
+#define TAS_REG_RIGHT_LOUDNESS_GAIN    0x24
+
+#define TAS3001_DRC_MAX                0x5f
+#define TAS3004_DRC_MAX                0xef
+
+#endif /* __SND_AOA_CODECTASH */
diff --git a/sound/aoa/codecs/toonie.c b/sound/aoa/codecs/toonie.c
new file mode 100644 (file)
index 0000000..f13827e
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Apple Onboard Audio driver for Toonie codec
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ *
+ * This is a driver for the toonie codec chip. This chip is present
+ * on the Mac Mini and is nothing but a DAC.
+ */
+#include <linux/delay.h>
+#include <linux/module.h>
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("toonie codec driver for snd-aoa");
+
+#include "../aoa.h"
+#include "../soundbus/soundbus.h"
+
+
+#define PFX "snd-aoa-codec-toonie: "
+
+struct toonie {
+       struct aoa_codec        codec;
+};
+#define codec_to_toonie(c) container_of(c, struct toonie, codec)
+
+static int toonie_dev_register(struct snd_device *dev)
+{
+       return 0;
+}
+
+static struct snd_device_ops ops = {
+       .dev_register = toonie_dev_register,
+};
+
+static struct transfer_info toonie_transfers[] = {
+       /* This thing *only* has analog output,
+        * the rates are taken from Info.plist
+        * from Darwin. */
+       {
+               .formats = SNDRV_PCM_FMTBIT_S16_BE |
+                          SNDRV_PCM_FMTBIT_S24_BE,
+               .rates = SNDRV_PCM_RATE_32000 |
+                        SNDRV_PCM_RATE_44100 |
+                        SNDRV_PCM_RATE_48000 |
+                        SNDRV_PCM_RATE_88200 |
+                        SNDRV_PCM_RATE_96000,
+       },
+       {}
+};
+
+static int toonie_usable(struct codec_info_item *cii,
+                        struct transfer_info *ti,
+                        struct transfer_info *out)
+{
+       return 1;
+}
+
+#ifdef CONFIG_PM
+static int toonie_suspend(struct codec_info_item *cii, pm_message_t state)
+{
+       /* can we turn it off somehow? */
+       return 0;
+}
+
+static int toonie_resume(struct codec_info_item *cii)
+{
+       return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct codec_info toonie_codec_info = {
+       .transfers = toonie_transfers,
+       .sysclock_factor = 256,
+       .bus_factor = 64,
+       .owner = THIS_MODULE,
+       .usable = toonie_usable,
+#ifdef CONFIG_PM
+       .suspend = toonie_suspend,
+       .resume = toonie_resume,
+#endif
+};
+
+static int toonie_init_codec(struct aoa_codec *codec)
+{
+       struct toonie *toonie = codec_to_toonie(codec);
+
+       /* nothing connected? what a joke! */
+       if (toonie->codec.connected != 1)
+               return -ENOTCONN;
+
+       if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, toonie, &ops)) {
+               printk(KERN_ERR PFX "failed to create toonie snd device!\n");
+               return -ENODEV;
+       }
+
+       if (toonie->codec.soundbus_dev->attach_codec(toonie->codec.soundbus_dev,
+                                                    aoa_get_card(),
+                                                    &toonie_codec_info, toonie)) {
+               printk(KERN_ERR PFX "error creating toonie pcm\n");
+               snd_device_free(aoa_get_card(), toonie);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static void toonie_exit_codec(struct aoa_codec *codec)
+{
+       struct toonie *toonie = codec_to_toonie(codec);
+
+       if (!toonie->codec.soundbus_dev) {
+               printk(KERN_ERR PFX "toonie_exit_codec called without soundbus_dev!\n");
+               return;
+       }
+       toonie->codec.soundbus_dev->detach_codec(toonie->codec.soundbus_dev, toonie);
+}
+
+static struct toonie *toonie;
+
+static int __init toonie_init(void)
+{
+       toonie = kzalloc(sizeof(struct toonie), GFP_KERNEL);
+
+       if (!toonie)
+               return -ENOMEM;
+
+       strlcpy(toonie->codec.name, "toonie", sizeof(toonie->codec.name));
+       toonie->codec.owner = THIS_MODULE;
+       toonie->codec.init = toonie_init_codec;
+       toonie->codec.exit = toonie_exit_codec;
+
+       if (aoa_codec_register(&toonie->codec)) {
+               kfree(toonie);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void __exit toonie_exit(void)
+{
+       aoa_codec_unregister(&toonie->codec);
+       kfree(toonie);
+}
+
+module_init(toonie_init);
+module_exit(toonie_exit);
index 62dc728..a1596e8 100644 (file)
@@ -1,5 +1,5 @@
 obj-$(CONFIG_SND_AOA) += snd-aoa.o
-snd-aoa-objs := snd-aoa-core.o \
-               snd-aoa-alsa.o \
-               snd-aoa-gpio-pmf.o \
-               snd-aoa-gpio-feature.o
+snd-aoa-objs := core.o \
+               alsa.o \
+               gpio-pmf.o \
+               gpio-feature.o
diff --git a/sound/aoa/core/alsa.c b/sound/aoa/core/alsa.c
new file mode 100644 (file)
index 0000000..6178504
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Apple Onboard Audio Alsa helpers
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#include <linux/module.h>
+#include "alsa.h"
+
+static int index = -1;
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "index for AOA sound card.");
+
+static struct aoa_card *aoa_card;
+
+int aoa_alsa_init(char *name, struct module *mod, struct device *dev)
+{
+       struct snd_card *alsa_card;
+       int err;
+
+       if (aoa_card)
+               /* cannot be EEXIST due to usage in aoa_fabric_register */
+               return -EBUSY;
+
+       alsa_card = snd_card_new(index, name, mod, sizeof(struct aoa_card));
+       if (!alsa_card)
+               return -ENOMEM;
+       aoa_card = alsa_card->private_data;
+       aoa_card->alsa_card = alsa_card;
+       alsa_card->dev = dev;
+       strlcpy(alsa_card->driver, "AppleOnbdAudio", sizeof(alsa_card->driver));
+       strlcpy(alsa_card->shortname, name, sizeof(alsa_card->shortname));
+       strlcpy(alsa_card->longname, name, sizeof(alsa_card->longname));
+       strlcpy(alsa_card->mixername, name, sizeof(alsa_card->mixername));
+       err = snd_card_register(aoa_card->alsa_card);
+       if (err < 0) {
+               printk(KERN_ERR "snd-aoa: couldn't register alsa card\n");
+               snd_card_free(aoa_card->alsa_card);
+               aoa_card = NULL;
+               return err;
+       }
+       return 0;
+}
+
+struct snd_card *aoa_get_card(void)
+{
+       if (aoa_card)
+               return aoa_card->alsa_card;
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(aoa_get_card);
+
+void aoa_alsa_cleanup(void)
+{
+       if (aoa_card) {
+               snd_card_free(aoa_card->alsa_card);
+               aoa_card = NULL;
+       }
+}
+
+int aoa_snd_device_new(snd_device_type_t type,
+                      void * device_data, struct snd_device_ops * ops)
+{
+       struct snd_card *card = aoa_get_card();
+       int err;
+
+       if (!card) return -ENOMEM;
+
+       err = snd_device_new(card, type, device_data, ops);
+       if (err) {
+               printk(KERN_ERR "snd-aoa: failed to create snd device (%d)\n", err);
+               return err;
+       }
+       err = snd_device_register(card, device_data);
+       if (err) {
+               printk(KERN_ERR "snd-aoa: failed to register "
+                               "snd device (%d)\n", err);
+               printk(KERN_ERR "snd-aoa: have you forgotten the "
+                               "dev_register callback?\n");
+               snd_device_free(card, device_data);
+       }
+       return err;
+}
+EXPORT_SYMBOL_GPL(aoa_snd_device_new);
+
+int aoa_snd_ctl_add(struct snd_kcontrol* control)
+{
+       int err;
+
+       if (!aoa_card) return -ENODEV;
+
+       err = snd_ctl_add(aoa_card->alsa_card, control);
+       if (err)
+               printk(KERN_ERR "snd-aoa: failed to add alsa control (%d)\n",
+                      err);
+       return err;
+}
+EXPORT_SYMBOL_GPL(aoa_snd_ctl_add);
diff --git a/sound/aoa/core/alsa.h b/sound/aoa/core/alsa.h
new file mode 100644 (file)
index 0000000..9669e44
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * Apple Onboard Audio Alsa private helpers
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#ifndef __SND_AOA_ALSA_H
+#define __SND_AOA_ALSA_H
+#include "../aoa.h"
+
+extern int aoa_alsa_init(char *name, struct module *mod, struct device *dev);
+extern void aoa_alsa_cleanup(void);
+
+#endif /* __SND_AOA_ALSA_H */
diff --git a/sound/aoa/core/core.c b/sound/aoa/core/core.c
new file mode 100644 (file)
index 0000000..10bec6c
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * Apple Onboard Audio driver core
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include "../aoa.h"
+#include "alsa.h"
+
+MODULE_DESCRIPTION("Apple Onboard Audio Sound Driver");
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+
+/* We allow only one fabric. This simplifies things,
+ * and more don't really make that much sense */
+static struct aoa_fabric *fabric;
+static LIST_HEAD(codec_list);
+
+static int attach_codec_to_fabric(struct aoa_codec *c)
+{
+       int err;
+
+       if (!try_module_get(c->owner))
+               return -EBUSY;
+       /* found_codec has to be assigned */
+       err = -ENOENT;
+       if (fabric->found_codec)
+               err = fabric->found_codec(c);
+       if (err) {
+               module_put(c->owner);
+               printk(KERN_ERR "snd-aoa: fabric didn't like codec %s\n",
+                               c->name);
+               return err;
+       }
+       c->fabric = fabric;
+
+       err = 0;
+       if (c->init)
+               err = c->init(c);
+       if (err) {
+               printk(KERN_ERR "snd-aoa: codec %s didn't init\n", c->name);
+               c->fabric = NULL;
+               if (fabric->remove_codec)
+                       fabric->remove_codec(c);
+               module_put(c->owner);
+               return err;
+       }
+       if (fabric->attached_codec)
+               fabric->attached_codec(c);
+       return 0;
+}
+
+int aoa_codec_register(struct aoa_codec *codec)
+{
+       int err = 0;
+
+       /* if there's a fabric already, we can tell if we
+        * will want to have this codec, so propagate error
+        * through. Otherwise, this will happen later... */
+       if (fabric)
+               err = attach_codec_to_fabric(codec);
+       if (!err)
+               list_add(&codec->list, &codec_list);
+       return err;
+}
+EXPORT_SYMBOL_GPL(aoa_codec_register);
+
+void aoa_codec_unregister(struct aoa_codec *codec)
+{
+       list_del(&codec->list);
+       if (codec->fabric && codec->exit)
+               codec->exit(codec);
+       if (fabric && fabric->remove_codec)
+               fabric->remove_codec(codec);
+       codec->fabric = NULL;
+       module_put(codec->owner);
+}
+EXPORT_SYMBOL_GPL(aoa_codec_unregister);
+
+int aoa_fabric_register(struct aoa_fabric *new_fabric, struct device *dev)
+{
+       struct aoa_codec *c;
+       int err;
+
+       /* allow querying for presence of fabric
+        * (i.e. do this test first!) */
+       if (new_fabric == fabric) {
+               err = -EALREADY;
+               goto attach;
+       }
+       if (fabric)
+               return -EEXIST;
+       if (!new_fabric)
+               return -EINVAL;
+
+       err = aoa_alsa_init(new_fabric->name, new_fabric->owner, dev);
+       if (err)
+               return err;
+
+       fabric = new_fabric;
+
+ attach:
+       list_for_each_entry(c, &codec_list, list) {
+               if (c->fabric != fabric)
+                       attach_codec_to_fabric(c);
+       }
+       return err;
+}
+EXPORT_SYMBOL_GPL(aoa_fabric_register);
+
+void aoa_fabric_unregister(struct aoa_fabric *old_fabric)
+{
+       struct aoa_codec *c;
+
+       if (fabric != old_fabric)
+               return;
+
+       list_for_each_entry(c, &codec_list, list) {
+               if (c->fabric)
+                       aoa_fabric_unlink_codec(c);
+       }
+
+       aoa_alsa_cleanup();
+
+       fabric = NULL;
+}
+EXPORT_SYMBOL_GPL(aoa_fabric_unregister);
+
+void aoa_fabric_unlink_codec(struct aoa_codec *codec)
+{
+       if (!codec->fabric) {
+               printk(KERN_ERR "snd-aoa: fabric unassigned "
+                               "in aoa_fabric_unlink_codec\n");
+               dump_stack();
+               return;
+       }
+       if (codec->exit)
+               codec->exit(codec);
+       if (codec->fabric->remove_codec)
+               codec->fabric->remove_codec(codec);
+       codec->fabric = NULL;
+       module_put(codec->owner);
+}
+EXPORT_SYMBOL_GPL(aoa_fabric_unlink_codec);
+
+static int __init aoa_init(void)
+{
+       return 0;
+}
+
+static void __exit aoa_exit(void)
+{
+       aoa_alsa_cleanup();
+}
+
+module_init(aoa_init);
+module_exit(aoa_exit);
diff --git a/sound/aoa/core/gpio-feature.c b/sound/aoa/core/gpio-feature.c
new file mode 100644 (file)
index 0000000..c93ad5d
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ * Apple Onboard Audio feature call GPIO control
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ * This file contains the GPIO control routines for
+ * direct (through feature calls) access to the GPIO
+ * registers.
+ */
+
+#include <asm/pmac_feature.h>
+#include <linux/interrupt.h>
+#include "../aoa.h"
+
+/* TODO: these are 20 global variables
+ * that aren't used on most machines...
+ * Move them into a dynamically allocated
+ * structure and use that.
+ */
+
+/* these are the GPIO numbers (register addresses as offsets into
+ * the GPIO space) */
+static int headphone_mute_gpio;
+static int amp_mute_gpio;
+static int lineout_mute_gpio;
+static int hw_reset_gpio;
+static int lineout_detect_gpio;
+static int headphone_detect_gpio;
+static int linein_detect_gpio;
+
+/* see the SWITCH_GPIO macro */
+static int headphone_mute_gpio_activestate;
+static int amp_mute_gpio_activestate;
+static int lineout_mute_gpio_activestate;
+static int hw_reset_gpio_activestate;
+static int lineout_detect_gpio_activestate;
+static int headphone_detect_gpio_activestate;
+static int linein_detect_gpio_activestate;
+
+/* node pointers that we save when getting the GPIO number
+ * to get the interrupt later */
+static struct device_node *lineout_detect_node;
+static struct device_node *linein_detect_node;
+static struct device_node *headphone_detect_node;
+
+static int lineout_detect_irq;
+static int linein_detect_irq;
+static int headphone_detect_irq;
+
+static struct device_node *get_gpio(char *name,
+                                   char *altname,
+                                   int *gpioptr,
+                                   int *gpioactiveptr)
+{
+       struct device_node *np, *gpio;
+       const u32 *reg;
+       const char *audio_gpio;
+
+       *gpioptr = -1;
+
+       /* check if we can get it the easy way ... */
+       np = of_find_node_by_name(NULL, name);
+       if (!np) {
+               /* some machines have only gpioX/extint-gpioX nodes,
+                * and an audio-gpio property saying what it is ...
+                * So what we have to do is enumerate all children
+                * of the gpio node and check them all. */
+               gpio = of_find_node_by_name(NULL, "gpio");
+               if (!gpio)
+                       return NULL;
+               while ((np = of_get_next_child(gpio, np))) {
+                       audio_gpio = of_get_property(np, "audio-gpio", NULL);
+                       if (!audio_gpio)
+                               continue;
+                       if (strcmp(audio_gpio, name) == 0)
+                               break;
+                       if (altname && (strcmp(audio_gpio, altname) == 0))
+                               break;
+               }
+               /* still not found, assume not there */
+               if (!np)
+                       return NULL;
+       }
+
+       reg = of_get_property(np, "reg", NULL);
+       if (!reg)
+               return NULL;
+
+       *gpioptr = *reg;
+
+       /* this is a hack, usually the GPIOs 'reg' property
+        * should have the offset based from the GPIO space
+        * which is at 0x50, but apparently not always... */
+       if (*gpioptr < 0x50)
+               *gpioptr += 0x50;
+
+       reg = of_get_property(np, "audio-gpio-active-state", NULL);
+       if (!reg)
+               /* Apple seems to default to 1, but
+                * that doesn't seem right at least on most
+                * machines. So until proven that the opposite
+                * is necessary, we default to 0
+                * (which, incidentally, snd-powermac also does...) */
+               *gpioactiveptr = 0;
+       else
+               *gpioactiveptr = *reg;
+
+       return np;
+}
+
+static void get_irq(struct device_node * np, int *irqptr)
+{
+       if (np)
+               *irqptr = irq_of_parse_and_map(np, 0);
+       else
+               *irqptr = NO_IRQ;
+}
+
+/* 0x4 is outenable, 0x1 is out, thus 4 or 5 */
+#define SWITCH_GPIO(name, v, on)                               \
+       (((v)&~1) | ((on)?                                      \
+                       (name##_gpio_activestate==0?4:5):       \
+                       (name##_gpio_activestate==0?5:4)))
+
+#define FTR_GPIO(name, bit)                                    \
+static void ftr_gpio_set_##name(struct gpio_runtime *rt, int on)\
+{                                                              \
+       int v;                                                  \
+                                                               \
+       if (unlikely(!rt)) return;                              \
+                                                               \
+       if (name##_mute_gpio < 0)                               \
+               return;                                         \
+                                                               \
+       v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL,         \
+                             name##_mute_gpio,                 \
+                             0);                               \
+                                                               \
+       /* muted = !on... */                                    \
+       v = SWITCH_GPIO(name##_mute, v, !on);                   \
+                                                               \
+       pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL,            \
+                         name##_mute_gpio, v);                 \
+                                                               \
+       rt->implementation_private &= ~(1<<bit);                \
+       rt->implementation_private |= (!!on << bit);            \
+}                                                              \
+static int ftr_gpio_get_##name(struct gpio_runtime *rt)                \
+{                                                              \
+       if (unlikely(!rt)) return 0;                            \
+       return (rt->implementation_private>>bit)&1;             \
+}
+
+FTR_GPIO(headphone, 0);
+FTR_GPIO(amp, 1);
+FTR_GPIO(lineout, 2);
+
+static void ftr_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
+{
+       int v;
+
+       if (unlikely(!rt)) return;
+       if (hw_reset_gpio < 0)
+               return;
+
+       v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL,
+                             hw_reset_gpio, 0);
+       v = SWITCH_GPIO(hw_reset, v, on);
+       pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL,
+                         hw_reset_gpio, v);
+}
+
+static void ftr_gpio_all_amps_off(struct gpio_runtime *rt)
+{
+       int saved;
+
+       if (unlikely(!rt)) return;
+       saved = rt->implementation_private;
+       ftr_gpio_set_headphone(rt, 0);
+       ftr_gpio_set_amp(rt, 0);
+       ftr_gpio_set_lineout(rt, 0);
+       rt->implementation_private = saved;
+}
+
+static void ftr_gpio_all_amps_restore(struct gpio_runtime *rt)
+{
+       int s;
+
+       if (unlikely(!rt)) return;
+       s = rt->implementation_private;
+       ftr_gpio_set_headphone(rt, (s>>0)&1);
+       ftr_gpio_set_amp(rt, (s>>1)&1);
+       ftr_gpio_set_lineout(rt, (s>>2)&1);
+}
+
+static void ftr_handle_notify(struct work_struct *work)
+{
+       struct gpio_notification *notif =
+               container_of(work, struct gpio_notification, work.work);
+
+       mutex_lock(&notif->mutex);
+       if (notif->notify)
+               notif->notify(notif->data);
+       mutex_unlock(&notif->mutex);
+}
+
+static void gpio_enable_dual_edge(int gpio)
+{
+       int v;
+
+       if (gpio == -1)
+               return;
+       v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0);
+       v |= 0x80; /* enable dual edge */
+       pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio, v);
+}
+
+static void ftr_gpio_init(struct gpio_runtime *rt)
+{
+       get_gpio("headphone-mute", NULL,
+                &headphone_mute_gpio,
+                &headphone_mute_gpio_activestate);
+       get_gpio("amp-mute", NULL,
+                &amp_mute_gpio,
+                &amp_mute_gpio_activestate);
+       get_gpio("lineout-mute", NULL,
+                &lineout_mute_gpio,
+                &lineout_mute_gpio_activestate);
+       get_gpio("hw-reset", "audio-hw-reset",
+                &hw_reset_gpio,
+                &hw_reset_gpio_activestate);
+
+       headphone_detect_node = get_gpio("headphone-detect", NULL,
+                                        &headphone_detect_gpio,
+                                        &headphone_detect_gpio_activestate);
+       /* go Apple, and thanks for giving these different names
+        * across the board... */
+       lineout_detect_node = get_gpio("lineout-detect", "line-output-detect",
+                                      &lineout_detect_gpio,
+                                      &lineout_detect_gpio_activestate);
+       linein_detect_node = get_gpio("linein-detect", "line-input-detect",
+                                     &linein_detect_gpio,
+                                     &linein_detect_gpio_activestate);
+
+       gpio_enable_dual_edge(headphone_detect_gpio);
+       gpio_enable_dual_edge(lineout_detect_gpio);
+       gpio_enable_dual_edge(linein_detect_gpio);
+
+       get_irq(headphone_detect_node, &headphone_detect_irq);
+       get_irq(lineout_detect_node, &lineout_detect_irq);
+       get_irq(linein_detect_node, &linein_detect_irq);
+
+       ftr_gpio_all_amps_off(rt);
+       rt->implementation_private = 0;
+       INIT_DELAYED_WORK(&rt->headphone_notify.work, ftr_handle_notify);
+       INIT_DELAYED_WORK(&rt->line_in_notify.work, ftr_handle_notify);
+       INIT_DELAYED_WORK(&rt->line_out_notify.work, ftr_handle_notify);
+       mutex_init(&rt->headphone_notify.mutex);
+       mutex_init(&rt->line_in_notify.mutex);
+       mutex_init(&rt->line_out_notify.mutex);
+}
+
+static void ftr_gpio_exit(struct gpio_runtime *rt)
+{
+       ftr_gpio_all_amps_off(rt);
+       rt->implementation_private = 0;
+       if (rt->headphone_notify.notify)
+               free_irq(headphone_detect_irq, &rt->headphone_notify);
+       if (rt->line_in_notify.gpio_private)
+               free_irq(linein_detect_irq, &rt->line_in_notify);
+       if (rt->line_out_notify.gpio_private)
+               free_irq(lineout_detect_irq, &rt->line_out_notify);
+       cancel_delayed_work(&rt->headphone_notify.work);
+       cancel_delayed_work(&rt->line_in_notify.work);
+       cancel_delayed_work(&rt->line_out_notify.work);
+       flush_scheduled_work();
+       mutex_destroy(&rt->headphone_notify.mutex);
+       mutex_destroy(&rt->line_in_notify.mutex);
+       mutex_destroy(&rt->line_out_notify.mutex);
+}
+
+static irqreturn_t ftr_handle_notify_irq(int xx, void *data)
+{
+       struct gpio_notification *notif = data;
+
+       schedule_delayed_work(&notif->work, 0);
+
+       return IRQ_HANDLED;
+}
+
+static int ftr_set_notify(struct gpio_runtime *rt,
+                         enum notify_type type,
+                         notify_func_t notify,
+                         void *data)
+{
+       struct gpio_notification *notif;
+       notify_func_t old;
+       int irq;
+       char *name;
+       int err = -EBUSY;
+
+       switch (type) {
+       case AOA_NOTIFY_HEADPHONE:
+               notif = &rt->headphone_notify;
+               name = "headphone-detect";
+               irq = headphone_detect_irq;
+               break;
+       case AOA_NOTIFY_LINE_IN:
+               notif = &rt->line_in_notify;
+               name = "linein-detect";
+               irq = linein_detect_irq;
+               break;
+       case AOA_NOTIFY_LINE_OUT:
+               notif = &rt->line_out_notify;
+               name = "lineout-detect";
+               irq = lineout_detect_irq;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (irq == NO_IRQ)
+               return -ENODEV;
+
+       mutex_lock(&notif->mutex);
+
+       old = notif->notify;
+
+       if (!old && !notify) {
+               err = 0;
+               goto out_unlock;
+       }
+
+       if (old && notify) {
+               if (old == notify && notif->data == data)
+                       err = 0;
+               goto out_unlock;
+       }
+
+       if (old && !notify)
+               free_irq(irq, notif);
+
+       if (!old && notify) {
+               err = request_irq(irq, ftr_handle_notify_irq, 0, name, notif);
+               if (err)
+                       goto out_unlock;
+       }
+
+       notif->notify = notify;
+       notif->data = data;
+
+       err = 0;
+ out_unlock:
+       mutex_unlock(&notif->mutex);
+       return err;
+}
+
+static int ftr_get_detect(struct gpio_runtime *rt,
+                         enum notify_type type)
+{
+       int gpio, ret, active;
+
+       switch (type) {
+       case AOA_NOTIFY_HEADPHONE:
+               gpio = headphone_detect_gpio;
+               active = headphone_detect_gpio_activestate;
+               break;
+       case AOA_NOTIFY_LINE_IN:
+               gpio = linein_detect_gpio;
+               active = linein_detect_gpio_activestate;
+               break;
+       case AOA_NOTIFY_LINE_OUT:
+               gpio = lineout_detect_gpio;
+               active = lineout_detect_gpio_activestate;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (gpio == -1)
+               return -ENODEV;
+
+       ret = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0);
+       if (ret < 0)
+               return ret;
+       return ((ret >> 1) & 1) == active;
+}
+
+static struct gpio_methods methods = {
+       .init                   = ftr_gpio_init,
+       .exit                   = ftr_gpio_exit,
+       .all_amps_off           = ftr_gpio_all_amps_off,
+       .all_amps_restore       = ftr_gpio_all_amps_restore,
+       .set_headphone          = ftr_gpio_set_headphone,
+       .set_speakers           = ftr_gpio_set_amp,
+       .set_lineout            = ftr_gpio_set_lineout,
+       .set_hw_reset           = ftr_gpio_set_hw_reset,
+       .get_headphone          = ftr_gpio_get_headphone,
+       .get_speakers           = ftr_gpio_get_amp,
+       .get_lineout            = ftr_gpio_get_lineout,
+       .set_notify             = ftr_set_notify,
+       .get_detect             = ftr_get_detect,
+};
+
+struct gpio_methods *ftr_gpio_methods = &methods;
+EXPORT_SYMBOL_GPL(ftr_gpio_methods);
diff --git a/sound/aoa/core/gpio-pmf.c b/sound/aoa/core/gpio-pmf.c
new file mode 100644 (file)
index 0000000..5ca2220
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * Apple Onboard Audio pmf GPIOs
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <asm/pmac_feature.h>
+#include <asm/pmac_pfunc.h>
+#include "../aoa.h"
+
+#define PMF_GPIO(name, bit)                                    \
+static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\
+{                                                              \
+       struct pmf_args args = { .count = 1, .u[0].v = !on };   \
+       int rc;                                                 \
+                                                       \
+       if (unlikely(!rt)) return;                              \
+       rc = pmf_call_function(rt->node, #name "-mute", &args); \
+       if (rc && rc != -ENODEV)                                \
+               printk(KERN_WARNING "pmf_gpio_set_" #name       \
+               " failed, rc: %d\n", rc);                       \
+       rt->implementation_private &= ~(1<<bit);                \
+       rt->implementation_private |= (!!on << bit);            \
+}                                                              \
+static int pmf_gpio_get_##name(struct gpio_runtime *rt)                \
+{                                                              \
+       if (unlikely(!rt)) return 0;                            \
+       return (rt->implementation_private>>bit)&1;             \
+}
+
+PMF_GPIO(headphone, 0);
+PMF_GPIO(amp, 1);
+PMF_GPIO(lineout, 2);
+
+static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
+{
+       struct pmf_args args = { .count = 1, .u[0].v = !!on };
+       int rc;
+
+       if (unlikely(!rt)) return;
+       rc = pmf_call_function(rt->node, "hw-reset", &args);
+       if (rc)
+               printk(KERN_WARNING "pmf_gpio_set_hw_reset"
+                      " failed, rc: %d\n", rc);
+}
+
+static void pmf_gpio_all_amps_off(struct gpio_runtime *rt)
+{
+       int saved;
+
+       if (unlikely(!rt)) return;
+       saved = rt->implementation_private;
+       pmf_gpio_set_headphone(rt, 0);
+       pmf_gpio_set_amp(rt, 0);
+       pmf_gpio_set_lineout(rt, 0);
+       rt->implementation_private = saved;
+}
+
+static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt)
+{
+       int s;
+
+       if (unlikely(!rt)) return;
+       s = rt->implementation_private;
+       pmf_gpio_set_headphone(rt, (s>>0)&1);
+       pmf_gpio_set_amp(rt, (s>>1)&1);
+       pmf_gpio_set_lineout(rt, (s>>2)&1);
+}
+
+static void pmf_handle_notify(struct work_struct *work)
+{
+       struct gpio_notification *notif =
+               container_of(work, struct gpio_notification, work.work);
+
+       mutex_lock(&notif->mutex);
+       if (notif->notify)
+               notif->notify(notif->data);
+       mutex_unlock(&notif->mutex);
+}
+
+static void pmf_gpio_init(struct gpio_runtime *rt)
+{
+       pmf_gpio_all_amps_off(rt);
+       rt->implementation_private = 0;
+       INIT_DELAYED_WORK(&rt->headphone_notify.work, pmf_handle_notify);
+       INIT_DELAYED_WORK(&rt->line_in_notify.work, pmf_handle_notify);
+       INIT_DELAYED_WORK(&rt->line_out_notify.work, pmf_handle_notify);
+       mutex_init(&rt->headphone_notify.mutex);
+       mutex_init(&rt->line_in_notify.mutex);
+       mutex_init(&rt->line_out_notify.mutex);
+}
+
+static void pmf_gpio_exit(struct gpio_runtime *rt)
+{
+       pmf_gpio_all_amps_off(rt);
+       rt->implementation_private = 0;
+
+       if (rt->headphone_notify.gpio_private)
+               pmf_unregister_irq_client(rt->headphone_notify.gpio_private);
+       if (rt->line_in_notify.gpio_private)
+               pmf_unregister_irq_client(rt->line_in_notify.gpio_private);
+       if (rt->line_out_notify.gpio_private)
+               pmf_unregister_irq_client(rt->line_out_notify.gpio_private);
+
+       /* make sure no work is pending before freeing
+        * all things */
+       cancel_delayed_work(&rt->headphone_notify.work);
+       cancel_delayed_work(&rt->line_in_notify.work);
+       cancel_delayed_work(&rt->line_out_notify.work);
+       flush_scheduled_work();
+
+       mutex_destroy(&rt->headphone_notify.mutex);
+       mutex_destroy(&rt->line_in_notify.mutex);
+       mutex_destroy(&rt->line_out_notify.mutex);
+
+       if (rt->headphone_notify.gpio_private)
+               kfree(rt->headphone_notify.gpio_private);
+       if (rt->line_in_notify.gpio_private)
+               kfree(rt->line_in_notify.gpio_private);
+       if (rt->line_out_notify.gpio_private)
+               kfree(rt->line_out_notify.gpio_private);
+}
+
+static void pmf_handle_notify_irq(void *data)
+{
+       struct gpio_notification *notif = data;
+
+       schedule_delayed_work(&notif->work, 0);
+}
+
+static int pmf_set_notify(struct gpio_runtime *rt,
+                         enum notify_type type,
+                         notify_func_t notify,
+                         void *data)
+{
+       struct gpio_notification *notif;
+       notify_func_t old;
+       struct pmf_irq_client *irq_client;
+       char *name;
+       int err = -EBUSY;
+
+       switch (type) {
+       case AOA_NOTIFY_HEADPHONE:
+               notif = &rt->headphone_notify;
+               name = "headphone-detect";
+               break;
+       case AOA_NOTIFY_LINE_IN:
+               notif = &rt->line_in_notify;
+               name = "linein-detect";
+               break;
+       case AOA_NOTIFY_LINE_OUT:
+               notif = &rt->line_out_notify;
+               name = "lineout-detect";
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       mutex_lock(&notif->mutex);
+
+       old = notif->notify;
+
+       if (!old && !notify) {
+               err = 0;
+               goto out_unlock;
+       }
+
+       if (old && notify) {
+               if (old == notify && notif->data == data)
+                       err = 0;
+               goto out_unlock;
+       }
+
+       if (old && !notify) {
+               irq_client = notif->gpio_private;
+               pmf_unregister_irq_client(irq_client);
+               kfree(irq_client);
+               notif->gpio_private = NULL;
+       }
+       if (!old && notify) {
+               irq_client = kzalloc(sizeof(struct pmf_irq_client),
+                                    GFP_KERNEL);
+               irq_client->data = notif;
+               irq_client->handler = pmf_handle_notify_irq;
+               irq_client->owner = THIS_MODULE;
+               err = pmf_register_irq_client(rt->node,
+                                             name,
+                                             irq_client);
+               if (err) {
+                       printk(KERN_ERR "snd-aoa: gpio layer failed to"
+                                       " register %s irq (%d)\n", name, err);
+                       kfree(irq_client);
+                       goto out_unlock;
+               }
+               notif->gpio_private = irq_client;
+       }
+       notif->notify = notify;
+       notif->data = data;
+
+       err = 0;
+ out_unlock:
+       mutex_unlock(&notif->mutex);
+       return err;
+}
+
+static int pmf_get_detect(struct gpio_runtime *rt,
+                         enum notify_type type)
+{
+       char *name;
+       int err = -EBUSY, ret;
+       struct pmf_args args = { .count = 1, .u[0].p = &ret };
+
+       switch (type) {
+       case AOA_NOTIFY_HEADPHONE:
+               name = "headphone-detect";
+               break;
+       case AOA_NOTIFY_LINE_IN:
+               name = "linein-detect";
+               break;
+       case AOA_NOTIFY_LINE_OUT:
+               name = "lineout-detect";
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       err = pmf_call_function(rt->node, name, &args);
+       if (err)
+               return err;
+       return ret;
+}
+
+static struct gpio_methods methods = {
+       .init                   = pmf_gpio_init,
+       .exit                   = pmf_gpio_exit,
+       .all_amps_off           = pmf_gpio_all_amps_off,
+       .all_amps_restore       = pmf_gpio_all_amps_restore,
+       .set_headphone          = pmf_gpio_set_headphone,
+       .set_speakers           = pmf_gpio_set_amp,
+       .set_lineout            = pmf_gpio_set_lineout,
+       .set_hw_reset           = pmf_gpio_set_hw_reset,
+       .get_headphone          = pmf_gpio_get_headphone,
+       .get_speakers           = pmf_gpio_get_amp,
+       .get_lineout            = pmf_gpio_get_lineout,
+       .set_notify             = pmf_set_notify,
+       .get_detect             = pmf_get_detect,
+};
+
+struct gpio_methods *pmf_gpio_methods = &methods;
+EXPORT_SYMBOL_GPL(pmf_gpio_methods);
diff --git a/sound/aoa/core/snd-aoa-alsa.c b/sound/aoa/core/snd-aoa-alsa.c
deleted file mode 100644 (file)
index 17fe689..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Apple Onboard Audio Alsa helpers
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-#include <linux/module.h>
-#include "snd-aoa-alsa.h"
-
-static int index = -1;
-module_param(index, int, 0444);
-MODULE_PARM_DESC(index, "index for AOA sound card.");
-
-static struct aoa_card *aoa_card;
-
-int aoa_alsa_init(char *name, struct module *mod, struct device *dev)
-{
-       struct snd_card *alsa_card;
-       int err;
-
-       if (aoa_card)
-               /* cannot be EEXIST due to usage in aoa_fabric_register */
-               return -EBUSY;
-
-       alsa_card = snd_card_new(index, name, mod, sizeof(struct aoa_card));
-       if (!alsa_card)
-               return -ENOMEM;
-       aoa_card = alsa_card->private_data;
-       aoa_card->alsa_card = alsa_card;
-       alsa_card->dev = dev;
-       strlcpy(alsa_card->driver, "AppleOnbdAudio", sizeof(alsa_card->driver));
-       strlcpy(alsa_card->shortname, name, sizeof(alsa_card->shortname));
-       strlcpy(alsa_card->longname, name, sizeof(alsa_card->longname));
-       strlcpy(alsa_card->mixername, name, sizeof(alsa_card->mixername));
-       err = snd_card_register(aoa_card->alsa_card);
-       if (err < 0) {
-               printk(KERN_ERR "snd-aoa: couldn't register alsa card\n");
-               snd_card_free(aoa_card->alsa_card);
-               aoa_card = NULL;
-               return err;
-       }
-       return 0;
-}
-
-struct snd_card *aoa_get_card(void)
-{
-       if (aoa_card)
-               return aoa_card->alsa_card;
-       return NULL;
-}
-EXPORT_SYMBOL_GPL(aoa_get_card);
-
-void aoa_alsa_cleanup(void)
-{
-       if (aoa_card) {
-               snd_card_free(aoa_card->alsa_card);
-               aoa_card = NULL;
-       }
-}
-
-int aoa_snd_device_new(snd_device_type_t type,
-                      void * device_data, struct snd_device_ops * ops)
-{
-       struct snd_card *card = aoa_get_card();
-       int err;
-       
-       if (!card) return -ENOMEM;
-
-       err = snd_device_new(card, type, device_data, ops);
-       if (err) {
-               printk(KERN_ERR "snd-aoa: failed to create snd device (%d)\n", err);
-               return err;
-       }
-       err = snd_device_register(card, device_data);
-       if (err) {
-               printk(KERN_ERR "snd-aoa: failed to register "
-                               "snd device (%d)\n", err);
-               printk(KERN_ERR "snd-aoa: have you forgotten the "
-                               "dev_register callback?\n");
-               snd_device_free(card, device_data);
-       }
-       return err;
-}
-EXPORT_SYMBOL_GPL(aoa_snd_device_new);
-
-int aoa_snd_ctl_add(struct snd_kcontrol* control)
-{
-       int err;
-
-       if (!aoa_card) return -ENODEV;
-
-       err = snd_ctl_add(aoa_card->alsa_card, control);
-       if (err)
-               printk(KERN_ERR "snd-aoa: failed to add alsa control (%d)\n",
-                      err);
-       return err;
-}
-EXPORT_SYMBOL_GPL(aoa_snd_ctl_add);
diff --git a/sound/aoa/core/snd-aoa-alsa.h b/sound/aoa/core/snd-aoa-alsa.h
deleted file mode 100644 (file)
index 9669e44..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Apple Onboard Audio Alsa private helpers
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-
-#ifndef __SND_AOA_ALSA_H
-#define __SND_AOA_ALSA_H
-#include "../aoa.h"
-
-extern int aoa_alsa_init(char *name, struct module *mod, struct device *dev);
-extern void aoa_alsa_cleanup(void);
-
-#endif /* __SND_AOA_ALSA_H */
diff --git a/sound/aoa/core/snd-aoa-core.c b/sound/aoa/core/snd-aoa-core.c
deleted file mode 100644 (file)
index 19fdae4..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Apple Onboard Audio driver core
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/list.h>
-#include "../aoa.h"
-#include "snd-aoa-alsa.h"
-
-MODULE_DESCRIPTION("Apple Onboard Audio Sound Driver");
-MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
-MODULE_LICENSE("GPL");
-
-/* We allow only one fabric. This simplifies things,
- * and more don't really make that much sense */
-static struct aoa_fabric *fabric;
-static LIST_HEAD(codec_list);
-
-static int attach_codec_to_fabric(struct aoa_codec *c)
-{
-       int err;
-
-       if (!try_module_get(c->owner))
-               return -EBUSY;
-       /* found_codec has to be assigned */
-       err = -ENOENT;
-       if (fabric->found_codec)
-               err = fabric->found_codec(c);
-       if (err) {
-               module_put(c->owner);
-               printk(KERN_ERR "snd-aoa: fabric didn't like codec %s\n",
-                               c->name);
-               return err;
-       }
-       c->fabric = fabric;
-
-       err = 0;
-       if (c->init)
-               err = c->init(c);
-       if (err) {
-               printk(KERN_ERR "snd-aoa: codec %s didn't init\n", c->name);
-               c->fabric = NULL;
-               if (fabric->remove_codec)
-                       fabric->remove_codec(c);
-               module_put(c->owner);
-               return err;
-       }
-       if (fabric->attached_codec)
-               fabric->attached_codec(c);
-       return 0;
-}
-
-int aoa_codec_register(struct aoa_codec *codec)
-{
-       int err = 0;
-
-       /* if there's a fabric already, we can tell if we
-        * will want to have this codec, so propagate error
-        * through. Otherwise, this will happen later... */
-       if (fabric)
-               err = attach_codec_to_fabric(codec);
-       if (!err)
-               list_add(&codec->list, &codec_list);
-       return err;
-}
-EXPORT_SYMBOL_GPL(aoa_codec_register);
-
-void aoa_codec_unregister(struct aoa_codec *codec)
-{
-       list_del(&codec->list);
-       if (codec->fabric && codec->exit)
-               codec->exit(codec);
-       if (fabric && fabric->remove_codec)
-               fabric->remove_codec(codec);
-       codec->fabric = NULL;
-       module_put(codec->owner);
-}
-EXPORT_SYMBOL_GPL(aoa_codec_unregister);
-
-int aoa_fabric_register(struct aoa_fabric *new_fabric, struct device *dev)
-{
-       struct aoa_codec *c;
-       int err;
-
-       /* allow querying for presence of fabric
-        * (i.e. do this test first!) */
-       if (new_fabric == fabric) {
-               err = -EALREADY;
-               goto attach;
-       }
-       if (fabric)
-               return -EEXIST;
-       if (!new_fabric)
-               return -EINVAL;
-
-       err = aoa_alsa_init(new_fabric->name, new_fabric->owner, dev);
-       if (err)
-               return err;
-
-       fabric = new_fabric;
-
- attach:
-       list_for_each_entry(c, &codec_list, list) {
-               if (c->fabric != fabric)
-                       attach_codec_to_fabric(c);
-       }
-       return err;
-}
-EXPORT_SYMBOL_GPL(aoa_fabric_register);
-
-void aoa_fabric_unregister(struct aoa_fabric *old_fabric)
-{
-       struct aoa_codec *c;
-
-       if (fabric != old_fabric)
-               return;
-
-       list_for_each_entry(c, &codec_list, list) {
-               if (c->fabric)
-                       aoa_fabric_unlink_codec(c);
-       }
-
-       aoa_alsa_cleanup();
-
-       fabric = NULL;
-}
-EXPORT_SYMBOL_GPL(aoa_fabric_unregister);
-
-void aoa_fabric_unlink_codec(struct aoa_codec *codec)
-{
-       if (!codec->fabric) {
-               printk(KERN_ERR "snd-aoa: fabric unassigned "
-                               "in aoa_fabric_unlink_codec\n");
-               dump_stack();
-               return;
-       }
-       if (codec->exit)
-               codec->exit(codec);
-       if (codec->fabric->remove_codec)
-               codec->fabric->remove_codec(codec);
-       codec->fabric = NULL;
-       module_put(codec->owner);
-}
-EXPORT_SYMBOL_GPL(aoa_fabric_unlink_codec);
-
-static int __init aoa_init(void)
-{
-       return 0;
-}
-
-static void __exit aoa_exit(void)
-{
-       aoa_alsa_cleanup();
-}
-
-module_init(aoa_init);
-module_exit(aoa_exit);
diff --git a/sound/aoa/core/snd-aoa-gpio-feature.c b/sound/aoa/core/snd-aoa-gpio-feature.c
deleted file mode 100644 (file)
index 805dcbf..0000000
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Apple Onboard Audio feature call GPIO control
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- *
- * This file contains the GPIO control routines for 
- * direct (through feature calls) access to the GPIO
- * registers.
- */
-
-#include <asm/pmac_feature.h>
-#include <linux/interrupt.h>
-#include "../aoa.h"
-
-/* TODO: these are 20 global variables
- * that aren't used on most machines...
- * Move them into a dynamically allocated
- * structure and use that.
- */
-
-/* these are the GPIO numbers (register addresses as offsets into
- * the GPIO space) */
-static int headphone_mute_gpio;
-static int amp_mute_gpio;
-static int lineout_mute_gpio;
-static int hw_reset_gpio;
-static int lineout_detect_gpio;
-static int headphone_detect_gpio;
-static int linein_detect_gpio;
-
-/* see the SWITCH_GPIO macro */
-static int headphone_mute_gpio_activestate;
-static int amp_mute_gpio_activestate;
-static int lineout_mute_gpio_activestate;
-static int hw_reset_gpio_activestate;
-static int lineout_detect_gpio_activestate;
-static int headphone_detect_gpio_activestate;
-static int linein_detect_gpio_activestate;
-
-/* node pointers that we save when getting the GPIO number
- * to get the interrupt later */
-static struct device_node *lineout_detect_node;
-static struct device_node *linein_detect_node;
-static struct device_node *headphone_detect_node;
-
-static int lineout_detect_irq;
-static int linein_detect_irq;
-static int headphone_detect_irq;
-
-static struct device_node *get_gpio(char *name,
-                                   char *altname,
-                                   int *gpioptr,
-                                   int *gpioactiveptr)
-{
-       struct device_node *np, *gpio;
-       const u32 *reg;
-       const char *audio_gpio;
-
-       *gpioptr = -1;
-
-       /* check if we can get it the easy way ... */
-       np = of_find_node_by_name(NULL, name);
-       if (!np) {
-               /* some machines have only gpioX/extint-gpioX nodes,
-                * and an audio-gpio property saying what it is ...
-                * So what we have to do is enumerate all children
-                * of the gpio node and check them all. */
-               gpio = of_find_node_by_name(NULL, "gpio");
-               if (!gpio)
-                       return NULL;
-               while ((np = of_get_next_child(gpio, np))) {
-                       audio_gpio = of_get_property(np, "audio-gpio", NULL);
-                       if (!audio_gpio)
-                               continue;
-                       if (strcmp(audio_gpio, name) == 0)
-                               break;
-                       if (altname && (strcmp(audio_gpio, altname) == 0))
-                               break;
-               }
-               /* still not found, assume not there */
-               if (!np)
-                       return NULL;
-       }
-
-       reg = of_get_property(np, "reg", NULL);
-       if (!reg)
-               return NULL;
-
-       *gpioptr = *reg;
-
-       /* this is a hack, usually the GPIOs 'reg' property
-        * should have the offset based from the GPIO space
-        * which is at 0x50, but apparently not always... */
-       if (*gpioptr < 0x50)
-               *gpioptr += 0x50;
-
-       reg = of_get_property(np, "audio-gpio-active-state", NULL);
-       if (!reg)
-               /* Apple seems to default to 1, but
-                * that doesn't seem right at least on most
-                * machines. So until proven that the opposite
-                * is necessary, we default to 0
-                * (which, incidentally, snd-powermac also does...) */
-               *gpioactiveptr = 0;
-       else
-               *gpioactiveptr = *reg;
-
-       return np;
-}
-
-static void get_irq(struct device_node * np, int *irqptr)
-{
-       if (np)
-               *irqptr = irq_of_parse_and_map(np, 0);
-       else
-               *irqptr = NO_IRQ;
-}
-
-/* 0x4 is outenable, 0x1 is out, thus 4 or 5 */
-#define SWITCH_GPIO(name, v, on)                               \
-       (((v)&~1) | ((on)?                                      \
-                       (name##_gpio_activestate==0?4:5):       \
-                       (name##_gpio_activestate==0?5:4)))
-
-#define FTR_GPIO(name, bit)                                    \
-static void ftr_gpio_set_##name(struct gpio_runtime *rt, int on)\
-{                                                              \
-       int v;                                                  \
-                                                               \
-       if (unlikely(!rt)) return;                              \
-                                                               \
-       if (name##_mute_gpio < 0)                               \
-               return;                                         \
-                                                               \
-       v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL,         \
-                             name##_mute_gpio,                 \
-                             0);                               \
-                                                               \
-       /* muted = !on... */                                    \
-       v = SWITCH_GPIO(name##_mute, v, !on);                   \
-                                                               \
-       pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL,            \
-                         name##_mute_gpio, v);                 \
-                                                               \
-       rt->implementation_private &= ~(1<<bit);                \
-       rt->implementation_private |= (!!on << bit);            \
-}                                                              \
-static int ftr_gpio_get_##name(struct gpio_runtime *rt)                \
-{                                                              \
-       if (unlikely(!rt)) return 0;                            \
-       return (rt->implementation_private>>bit)&1;             \
-}
-
-FTR_GPIO(headphone, 0);
-FTR_GPIO(amp, 1);
-FTR_GPIO(lineout, 2);
-
-static void ftr_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
-{
-       int v;
-
-       if (unlikely(!rt)) return;
-       if (hw_reset_gpio < 0)
-               return;
-
-       v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL,
-                             hw_reset_gpio, 0);
-       v = SWITCH_GPIO(hw_reset, v, on);
-       pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL,
-                         hw_reset_gpio, v);
-}
-
-static void ftr_gpio_all_amps_off(struct gpio_runtime *rt)
-{
-       int saved;
-
-       if (unlikely(!rt)) return;
-       saved = rt->implementation_private;
-       ftr_gpio_set_headphone(rt, 0);
-       ftr_gpio_set_amp(rt, 0);
-       ftr_gpio_set_lineout(rt, 0);
-       rt->implementation_private = saved;
-}
-
-static void ftr_gpio_all_amps_restore(struct gpio_runtime *rt)
-{
-       int s;
-
-       if (unlikely(!rt)) return;
-       s = rt->implementation_private;
-       ftr_gpio_set_headphone(rt, (s>>0)&1);
-       ftr_gpio_set_amp(rt, (s>>1)&1);
-       ftr_gpio_set_lineout(rt, (s>>2)&1);
-}
-
-static void ftr_handle_notify(struct work_struct *work)
-{
-       struct gpio_notification *notif =
-               container_of(work, struct gpio_notification, work.work);
-
-       mutex_lock(&notif->mutex);
-       if (notif->notify)
-               notif->notify(notif->data);
-       mutex_unlock(&notif->mutex);
-}
-
-static void gpio_enable_dual_edge(int gpio)
-{
-       int v;
-
-       if (gpio == -1)
-               return;
-       v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0);
-       v |= 0x80; /* enable dual edge */
-       pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio, v);
-}
-
-static void ftr_gpio_init(struct gpio_runtime *rt)
-{
-       get_gpio("headphone-mute", NULL,
-                &headphone_mute_gpio,
-                &headphone_mute_gpio_activestate);
-       get_gpio("amp-mute", NULL,
-                &amp_mute_gpio,
-                &amp_mute_gpio_activestate);
-       get_gpio("lineout-mute", NULL,
-                &lineout_mute_gpio,
-                &lineout_mute_gpio_activestate);
-       get_gpio("hw-reset", "audio-hw-reset",
-                &hw_reset_gpio,
-                &hw_reset_gpio_activestate);
-
-       headphone_detect_node = get_gpio("headphone-detect", NULL,
-                                        &headphone_detect_gpio,
-                                        &headphone_detect_gpio_activestate);
-       /* go Apple, and thanks for giving these different names
-        * across the board... */
-       lineout_detect_node = get_gpio("lineout-detect", "line-output-detect",
-                                      &lineout_detect_gpio,
-                                      &lineout_detect_gpio_activestate);
-       linein_detect_node = get_gpio("linein-detect", "line-input-detect",
-                                     &linein_detect_gpio,
-                                     &linein_detect_gpio_activestate);
-
-       gpio_enable_dual_edge(headphone_detect_gpio);
-       gpio_enable_dual_edge(lineout_detect_gpio);
-       gpio_enable_dual_edge(linein_detect_gpio);
-
-       get_irq(headphone_detect_node, &headphone_detect_irq);
-       get_irq(lineout_detect_node, &lineout_detect_irq);
-       get_irq(linein_detect_node, &linein_detect_irq);
-
-       ftr_gpio_all_amps_off(rt);
-       rt->implementation_private = 0;
-       INIT_DELAYED_WORK(&rt->headphone_notify.work, ftr_handle_notify);
-       INIT_DELAYED_WORK(&rt->line_in_notify.work, ftr_handle_notify);
-       INIT_DELAYED_WORK(&rt->line_out_notify.work, ftr_handle_notify);
-       mutex_init(&rt->headphone_notify.mutex);
-       mutex_init(&rt->line_in_notify.mutex);
-       mutex_init(&rt->line_out_notify.mutex);
-}
-
-static void ftr_gpio_exit(struct gpio_runtime *rt)
-{
-       ftr_gpio_all_amps_off(rt);
-       rt->implementation_private = 0;
-       if (rt->headphone_notify.notify)
-               free_irq(headphone_detect_irq, &rt->headphone_notify);
-       if (rt->line_in_notify.gpio_private)
-               free_irq(linein_detect_irq, &rt->line_in_notify);
-       if (rt->line_out_notify.gpio_private)
-               free_irq(lineout_detect_irq, &rt->line_out_notify);
-       cancel_delayed_work(&rt->headphone_notify.work);
-       cancel_delayed_work(&rt->line_in_notify.work);
-       cancel_delayed_work(&rt->line_out_notify.work);
-       flush_scheduled_work();
-       mutex_destroy(&rt->headphone_notify.mutex);
-       mutex_destroy(&rt->line_in_notify.mutex);
-       mutex_destroy(&rt->line_out_notify.mutex);
-}
-
-static irqreturn_t ftr_handle_notify_irq(int xx, void *data)
-{
-       struct gpio_notification *notif = data;
-
-       schedule_delayed_work(&notif->work, 0);
-
-       return IRQ_HANDLED;
-}
-
-static int ftr_set_notify(struct gpio_runtime *rt,
-                         enum notify_type type,
-                         notify_func_t notify,
-                         void *data)
-{
-       struct gpio_notification *notif;
-       notify_func_t old;
-       int irq;
-       char *name;
-       int err = -EBUSY;
-
-       switch (type) {
-       case AOA_NOTIFY_HEADPHONE:
-               notif = &rt->headphone_notify;
-               name = "headphone-detect";
-               irq = headphone_detect_irq;
-               break;
-       case AOA_NOTIFY_LINE_IN:
-               notif = &rt->line_in_notify;
-               name = "linein-detect";
-               irq = linein_detect_irq;
-               break;
-       case AOA_NOTIFY_LINE_OUT:
-               notif = &rt->line_out_notify;
-               name = "lineout-detect";
-               irq = lineout_detect_irq;
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       if (irq == NO_IRQ)
-               return -ENODEV;
-
-       mutex_lock(&notif->mutex);
-
-       old = notif->notify;
-
-       if (!old && !notify) {
-               err = 0;
-               goto out_unlock;
-       }
-
-       if (old && notify) {
-               if (old == notify && notif->data == data)
-                       err = 0;
-               goto out_unlock;
-       }
-
-       if (old && !notify)
-               free_irq(irq, notif);
-
-       if (!old && notify) {
-               err = request_irq(irq, ftr_handle_notify_irq, 0, name, notif);
-               if (err)
-                       goto out_unlock;
-       }
-
-       notif->notify = notify;
-       notif->data = data;
-
-       err = 0;
- out_unlock:
-       mutex_unlock(&notif->mutex);
-       return err;
-}
-
-static int ftr_get_detect(struct gpio_runtime *rt,
-                         enum notify_type type)
-{
-       int gpio, ret, active;
-
-       switch (type) {
-       case AOA_NOTIFY_HEADPHONE:
-               gpio = headphone_detect_gpio;
-               active = headphone_detect_gpio_activestate;
-               break;
-       case AOA_NOTIFY_LINE_IN:
-               gpio = linein_detect_gpio;
-               active = linein_detect_gpio_activestate;
-               break;
-       case AOA_NOTIFY_LINE_OUT:
-               gpio = lineout_detect_gpio;
-               active = lineout_detect_gpio_activestate;
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       if (gpio == -1)
-               return -ENODEV;
-
-       ret = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0);
-       if (ret < 0)
-               return ret;
-       return ((ret >> 1) & 1) == active;
-}
-
-static struct gpio_methods methods = {
-       .init                   = ftr_gpio_init,
-       .exit                   = ftr_gpio_exit,
-       .all_amps_off           = ftr_gpio_all_amps_off,
-       .all_amps_restore       = ftr_gpio_all_amps_restore,
-       .set_headphone          = ftr_gpio_set_headphone,
-       .set_speakers           = ftr_gpio_set_amp,
-       .set_lineout            = ftr_gpio_set_lineout,
-       .set_hw_reset           = ftr_gpio_set_hw_reset,
-       .get_headphone          = ftr_gpio_get_headphone,
-       .get_speakers           = ftr_gpio_get_amp,
-       .get_lineout            = ftr_gpio_get_lineout,
-       .set_notify             = ftr_set_notify,
-       .get_detect             = ftr_get_detect,
-};
-
-struct gpio_methods *ftr_gpio_methods = &methods;
-EXPORT_SYMBOL_GPL(ftr_gpio_methods);
diff --git a/sound/aoa/core/snd-aoa-gpio-pmf.c b/sound/aoa/core/snd-aoa-gpio-pmf.c
deleted file mode 100644 (file)
index 5ca2220..0000000
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Apple Onboard Audio pmf GPIOs
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-
-#include <asm/pmac_feature.h>
-#include <asm/pmac_pfunc.h>
-#include "../aoa.h"
-
-#define PMF_GPIO(name, bit)                                    \
-static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\
-{                                                              \
-       struct pmf_args args = { .count = 1, .u[0].v = !on };   \
-       int rc;                                                 \
-                                                       \
-       if (unlikely(!rt)) return;                              \
-       rc = pmf_call_function(rt->node, #name "-mute", &args); \
-       if (rc && rc != -ENODEV)                                \
-               printk(KERN_WARNING "pmf_gpio_set_" #name       \
-               " failed, rc: %d\n", rc);                       \
-       rt->implementation_private &= ~(1<<bit);                \
-       rt->implementation_private |= (!!on << bit);            \
-}                                                              \
-static int pmf_gpio_get_##name(struct gpio_runtime *rt)                \
-{                                                              \
-       if (unlikely(!rt)) return 0;                            \
-       return (rt->implementation_private>>bit)&1;             \
-}
-
-PMF_GPIO(headphone, 0);
-PMF_GPIO(amp, 1);
-PMF_GPIO(lineout, 2);
-
-static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
-{
-       struct pmf_args args = { .count = 1, .u[0].v = !!on };
-       int rc;
-
-       if (unlikely(!rt)) return;
-       rc = pmf_call_function(rt->node, "hw-reset", &args);
-       if (rc)
-               printk(KERN_WARNING "pmf_gpio_set_hw_reset"
-                      " failed, rc: %d\n", rc);
-}
-
-static void pmf_gpio_all_amps_off(struct gpio_runtime *rt)
-{
-       int saved;
-
-       if (unlikely(!rt)) return;
-       saved = rt->implementation_private;
-       pmf_gpio_set_headphone(rt, 0);
-       pmf_gpio_set_amp(rt, 0);
-       pmf_gpio_set_lineout(rt, 0);
-       rt->implementation_private = saved;
-}
-
-static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt)
-{
-       int s;
-
-       if (unlikely(!rt)) return;
-       s = rt->implementation_private;
-       pmf_gpio_set_headphone(rt, (s>>0)&1);
-       pmf_gpio_set_amp(rt, (s>>1)&1);
-       pmf_gpio_set_lineout(rt, (s>>2)&1);
-}
-
-static void pmf_handle_notify(struct work_struct *work)
-{
-       struct gpio_notification *notif =
-               container_of(work, struct gpio_notification, work.work);
-
-       mutex_lock(&notif->mutex);
-       if (notif->notify)
-               notif->notify(notif->data);
-       mutex_unlock(&notif->mutex);
-}
-
-static void pmf_gpio_init(struct gpio_runtime *rt)
-{
-       pmf_gpio_all_amps_off(rt);
-       rt->implementation_private = 0;
-       INIT_DELAYED_WORK(&rt->headphone_notify.work, pmf_handle_notify);
-       INIT_DELAYED_WORK(&rt->line_in_notify.work, pmf_handle_notify);
-       INIT_DELAYED_WORK(&rt->line_out_notify.work, pmf_handle_notify);
-       mutex_init(&rt->headphone_notify.mutex);
-       mutex_init(&rt->line_in_notify.mutex);
-       mutex_init(&rt->line_out_notify.mutex);
-}
-
-static void pmf_gpio_exit(struct gpio_runtime *rt)
-{
-       pmf_gpio_all_amps_off(rt);
-       rt->implementation_private = 0;
-
-       if (rt->headphone_notify.gpio_private)
-               pmf_unregister_irq_client(rt->headphone_notify.gpio_private);
-       if (rt->line_in_notify.gpio_private)
-               pmf_unregister_irq_client(rt->line_in_notify.gpio_private);
-       if (rt->line_out_notify.gpio_private)
-               pmf_unregister_irq_client(rt->line_out_notify.gpio_private);
-
-       /* make sure no work is pending before freeing
-        * all things */
-       cancel_delayed_work(&rt->headphone_notify.work);
-       cancel_delayed_work(&rt->line_in_notify.work);
-       cancel_delayed_work(&rt->line_out_notify.work);
-       flush_scheduled_work();
-
-       mutex_destroy(&rt->headphone_notify.mutex);
-       mutex_destroy(&rt->line_in_notify.mutex);
-       mutex_destroy(&rt->line_out_notify.mutex);
-
-       if (rt->headphone_notify.gpio_private)
-               kfree(rt->headphone_notify.gpio_private);
-       if (rt->line_in_notify.gpio_private)
-               kfree(rt->line_in_notify.gpio_private);
-       if (rt->line_out_notify.gpio_private)
-               kfree(rt->line_out_notify.gpio_private);
-}
-
-static void pmf_handle_notify_irq(void *data)
-{
-       struct gpio_notification *notif = data;
-
-       schedule_delayed_work(&notif->work, 0);
-}
-
-static int pmf_set_notify(struct gpio_runtime *rt,
-                         enum notify_type type,
-                         notify_func_t notify,
-                         void *data)
-{
-       struct gpio_notification *notif;
-       notify_func_t old;
-       struct pmf_irq_client *irq_client;
-       char *name;
-       int err = -EBUSY;
-
-       switch (type) {
-       case AOA_NOTIFY_HEADPHONE:
-               notif = &rt->headphone_notify;
-               name = "headphone-detect";
-               break;
-       case AOA_NOTIFY_LINE_IN:
-               notif = &rt->line_in_notify;
-               name = "linein-detect";
-               break;
-       case AOA_NOTIFY_LINE_OUT:
-               notif = &rt->line_out_notify;
-               name = "lineout-detect";
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       mutex_lock(&notif->mutex);
-
-       old = notif->notify;
-
-       if (!old && !notify) {
-               err = 0;
-               goto out_unlock;
-       }
-
-       if (old && notify) {
-               if (old == notify && notif->data == data)
-                       err = 0;
-               goto out_unlock;
-       }
-
-       if (old && !notify) {
-               irq_client = notif->gpio_private;
-               pmf_unregister_irq_client(irq_client);
-               kfree(irq_client);
-               notif->gpio_private = NULL;
-       }
-       if (!old && notify) {
-               irq_client = kzalloc(sizeof(struct pmf_irq_client),
-                                    GFP_KERNEL);
-               irq_client->data = notif;
-               irq_client->handler = pmf_handle_notify_irq;
-               irq_client->owner = THIS_MODULE;
-               err = pmf_register_irq_client(rt->node,
-                                             name,
-                                             irq_client);
-               if (err) {
-                       printk(KERN_ERR "snd-aoa: gpio layer failed to"
-                                       " register %s irq (%d)\n", name, err);
-                       kfree(irq_client);
-                       goto out_unlock;
-               }
-               notif->gpio_private = irq_client;
-       }
-       notif->notify = notify;
-       notif->data = data;
-
-       err = 0;
- out_unlock:
-       mutex_unlock(&notif->mutex);
-       return err;
-}
-
-static int pmf_get_detect(struct gpio_runtime *rt,
-                         enum notify_type type)
-{
-       char *name;
-       int err = -EBUSY, ret;
-       struct pmf_args args = { .count = 1, .u[0].p = &ret };
-
-       switch (type) {
-       case AOA_NOTIFY_HEADPHONE:
-               name = "headphone-detect";
-               break;
-       case AOA_NOTIFY_LINE_IN:
-               name = "linein-detect";
-               break;
-       case AOA_NOTIFY_LINE_OUT:
-               name = "lineout-detect";
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       err = pmf_call_function(rt->node, name, &args);
-       if (err)
-               return err;
-       return ret;
-}
-
-static struct gpio_methods methods = {
-       .init                   = pmf_gpio_init,
-       .exit                   = pmf_gpio_exit,
-       .all_amps_off           = pmf_gpio_all_amps_off,
-       .all_amps_restore       = pmf_gpio_all_amps_restore,
-       .set_headphone          = pmf_gpio_set_headphone,
-       .set_speakers           = pmf_gpio_set_amp,
-       .set_lineout            = pmf_gpio_set_lineout,
-       .set_hw_reset           = pmf_gpio_set_hw_reset,
-       .get_headphone          = pmf_gpio_get_headphone,
-       .get_speakers           = pmf_gpio_get_amp,
-       .get_lineout            = pmf_gpio_get_lineout,
-       .set_notify             = pmf_set_notify,
-       .get_detect             = pmf_get_detect,
-};
-
-struct gpio_methods *pmf_gpio_methods = &methods;
-EXPORT_SYMBOL_GPL(pmf_gpio_methods);
index 55fc5e7..da37c10 100644 (file)
@@ -1 +1,3 @@
+snd-aoa-fabric-layout-objs += layout.o
+
 obj-$(CONFIG_SND_AOA_FABRIC_LAYOUT) += snd-aoa-fabric-layout.o
diff --git a/sound/aoa/fabrics/layout.c b/sound/aoa/fabrics/layout.c
new file mode 100644 (file)
index 0000000..ad60f5d
--- /dev/null
@@ -0,0 +1,1120 @@
+/*
+ * Apple Onboard Audio driver -- layout fabric
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ *
+ * This fabric module looks for sound codecs
+ * based on the layout-id property in the device tree.
+ *
+ */
+
+#include <asm/prom.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include "../aoa.h"
+#include "../soundbus/soundbus.h"
+
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Layout-ID fabric for snd-aoa");
+
+#define MAX_CODECS_PER_BUS     2
+
+/* These are the connections the layout fabric
+ * knows about. It doesn't really care about the
+ * input ones, but I thought I'd separate them
+ * to give them proper names. The thing is that
+ * Apple usually will distinguish the active output
+ * by GPIOs, while the active input is set directly
+ * on the codec. Hence we here tell the codec what
+ * we think is connected. This information is hard-
+ * coded below ... */
+#define CC_SPEAKERS    (1<<0)
+#define CC_HEADPHONE   (1<<1)
+#define CC_LINEOUT     (1<<2)
+#define CC_DIGITALOUT  (1<<3)
+#define CC_LINEIN      (1<<4)
+#define CC_MICROPHONE  (1<<5)
+#define CC_DIGITALIN   (1<<6)
+/* pretty bogus but users complain...
+ * This is a flag saying that the LINEOUT
+ * should be renamed to HEADPHONE.
+ * be careful with input detection! */
+#define CC_LINEOUT_LABELLED_HEADPHONE  (1<<7)
+
+struct codec_connection {
+       /* CC_ flags from above */
+       int connected;
+       /* codec dependent bit to be set in the aoa_codec.connected field.
+        * This intentionally doesn't have any generic flags because the
+        * fabric has to know the codec anyway and all codecs might have
+        * different connectors */
+       int codec_bit;
+};
+
+struct codec_connect_info {
+       char *name;
+       struct codec_connection *connections;
+};
+
+#define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF        (1<<0)
+
+struct layout {
+       unsigned int layout_id;
+       struct codec_connect_info codecs[MAX_CODECS_PER_BUS];
+       int flags;
+
+       /* if busname is not assigned, we use 'Master' below,
+        * so that our layout table doesn't need to be filled
+        * too much.
+        * We only assign these two if we expect to find more
+        * than one soundbus, i.e. on those machines with
+        * multiple layout-ids */
+       char *busname;
+       int pcmid;
+};
+
+MODULE_ALIAS("sound-layout-36");
+MODULE_ALIAS("sound-layout-41");
+MODULE_ALIAS("sound-layout-45");
+MODULE_ALIAS("sound-layout-47");
+MODULE_ALIAS("sound-layout-48");
+MODULE_ALIAS("sound-layout-49");
+MODULE_ALIAS("sound-layout-50");
+MODULE_ALIAS("sound-layout-51");
+MODULE_ALIAS("sound-layout-56");
+MODULE_ALIAS("sound-layout-57");
+MODULE_ALIAS("sound-layout-58");
+MODULE_ALIAS("sound-layout-60");
+MODULE_ALIAS("sound-layout-61");
+MODULE_ALIAS("sound-layout-62");
+MODULE_ALIAS("sound-layout-64");
+MODULE_ALIAS("sound-layout-65");
+MODULE_ALIAS("sound-layout-66");
+MODULE_ALIAS("sound-layout-67");
+MODULE_ALIAS("sound-layout-68");
+MODULE_ALIAS("sound-layout-69");
+MODULE_ALIAS("sound-layout-70");
+MODULE_ALIAS("sound-layout-72");
+MODULE_ALIAS("sound-layout-76");
+MODULE_ALIAS("sound-layout-80");
+MODULE_ALIAS("sound-layout-82");
+MODULE_ALIAS("sound-layout-84");
+MODULE_ALIAS("sound-layout-86");
+MODULE_ALIAS("sound-layout-90");
+MODULE_ALIAS("sound-layout-92");
+MODULE_ALIAS("sound-layout-94");
+MODULE_ALIAS("sound-layout-96");
+MODULE_ALIAS("sound-layout-98");
+MODULE_ALIAS("sound-layout-100");
+
+/* onyx with all but microphone connected */
+static struct codec_connection onyx_connections_nomic[] = {
+       {
+               .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
+               .codec_bit = 0,
+       },
+       {
+               .connected = CC_DIGITALOUT,
+               .codec_bit = 1,
+       },
+       {
+               .connected = CC_LINEIN,
+               .codec_bit = 2,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+/* onyx on machines without headphone */
+static struct codec_connection onyx_connections_noheadphones[] = {
+       {
+               .connected = CC_SPEAKERS | CC_LINEOUT |
+                            CC_LINEOUT_LABELLED_HEADPHONE,
+               .codec_bit = 0,
+       },
+       {
+               .connected = CC_DIGITALOUT,
+               .codec_bit = 1,
+       },
+       /* FIXME: are these correct? probably not for all the machines
+        * below ... If not this will need separating. */
+       {
+               .connected = CC_LINEIN,
+               .codec_bit = 2,
+       },
+       {
+               .connected = CC_MICROPHONE,
+               .codec_bit = 3,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+/* onyx on machines with real line-out */
+static struct codec_connection onyx_connections_reallineout[] = {
+       {
+               .connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE,
+               .codec_bit = 0,
+       },
+       {
+               .connected = CC_DIGITALOUT,
+               .codec_bit = 1,
+       },
+       {
+               .connected = CC_LINEIN,
+               .codec_bit = 2,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+/* tas on machines without line out */
+static struct codec_connection tas_connections_nolineout[] = {
+       {
+               .connected = CC_SPEAKERS | CC_HEADPHONE,
+               .codec_bit = 0,
+       },
+       {
+               .connected = CC_LINEIN,
+               .codec_bit = 2,
+       },
+       {
+               .connected = CC_MICROPHONE,
+               .codec_bit = 3,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+/* tas on machines with neither line out nor line in */
+static struct codec_connection tas_connections_noline[] = {
+       {
+               .connected = CC_SPEAKERS | CC_HEADPHONE,
+               .codec_bit = 0,
+       },
+       {
+               .connected = CC_MICROPHONE,
+               .codec_bit = 3,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+/* tas on machines without microphone */
+static struct codec_connection tas_connections_nomic[] = {
+       {
+               .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
+               .codec_bit = 0,
+       },
+       {
+               .connected = CC_LINEIN,
+               .codec_bit = 2,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+/* tas on machines with everything connected */
+static struct codec_connection tas_connections_all[] = {
+       {
+               .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
+               .codec_bit = 0,
+       },
+       {
+               .connected = CC_LINEIN,
+               .codec_bit = 2,
+       },
+       {
+               .connected = CC_MICROPHONE,
+               .codec_bit = 3,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+static struct codec_connection toonie_connections[] = {
+       {
+               .connected = CC_SPEAKERS | CC_HEADPHONE,
+               .codec_bit = 0,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+static struct codec_connection topaz_input[] = {
+       {
+               .connected = CC_DIGITALIN,
+               .codec_bit = 0,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+static struct codec_connection topaz_output[] = {
+       {
+               .connected = CC_DIGITALOUT,
+               .codec_bit = 1,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+static struct codec_connection topaz_inout[] = {
+       {
+               .connected = CC_DIGITALIN,
+               .codec_bit = 0,
+       },
+       {
+               .connected = CC_DIGITALOUT,
+               .codec_bit = 1,
+       },
+       {} /* terminate array by .connected == 0 */
+};
+
+static struct layout layouts[] = {
+       /* last PowerBooks (15" Oct 2005) */
+       { .layout_id = 82,
+         .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+         .codecs[1] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       /* PowerMac9,1 */
+       { .layout_id = 60,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_reallineout,
+         },
+       },
+       /* PowerMac9,1 */
+       { .layout_id = 61,
+         .codecs[0] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       /* PowerBook5,7 */
+       { .layout_id = 64,
+         .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+       },
+       /* PowerBook5,7 */
+       { .layout_id = 65,
+         .codecs[0] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       /* PowerBook5,9 [17" Oct 2005] */
+       { .layout_id = 84,
+         .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+         .codecs[1] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       /* PowerMac8,1 */
+       { .layout_id = 45,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+         .codecs[1] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       /* Quad PowerMac (analog in, analog/digital out) */
+       { .layout_id = 68,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_nomic,
+         },
+       },
+       /* Quad PowerMac (digital in) */
+       { .layout_id = 69,
+         .codecs[0] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+         .busname = "digital in", .pcmid = 1 },
+       /* Early 2005 PowerBook (PowerBook 5,6) */
+       { .layout_id = 70,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_nolineout,
+         },
+       },
+       /* PowerBook 5,4 */
+       { .layout_id = 51,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_nolineout,
+         },
+       },
+       /* PowerBook6,7 */
+       { .layout_id = 80,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_noline,
+         },
+       },
+       /* PowerBook6,8 */
+       { .layout_id = 72,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_nolineout,
+         },
+       },
+       /* PowerMac8,2 */
+       { .layout_id = 86,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_nomic,
+         },
+         .codecs[1] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       /* PowerBook6,7 */
+       { .layout_id = 92,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_nolineout,
+         },
+       },
+       /* PowerMac10,1 (Mac Mini) */
+       { .layout_id = 58,
+         .codecs[0] = {
+               .name = "toonie",
+               .connections = toonie_connections,
+         },
+       },
+       {
+         .layout_id = 96,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+       },
+       /* unknown, untested, but this comes from Apple */
+       { .layout_id = 41,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_all,
+         },
+       },
+       { .layout_id = 36,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_nomic,
+         },
+         .codecs[1] = {
+               .name = "topaz",
+               .connections = topaz_inout,
+         },
+       },
+       { .layout_id = 47,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+       },
+       { .layout_id = 48,
+         .codecs[0] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       { .layout_id = 49,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_nomic,
+         },
+       },
+       { .layout_id = 50,
+         .codecs[0] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       { .layout_id = 56,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+       },
+       { .layout_id = 57,
+         .codecs[0] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       { .layout_id = 62,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+         .codecs[1] = {
+               .name = "topaz",
+               .connections = topaz_output,
+         },
+       },
+       { .layout_id = 66,
+         .codecs[0] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+       },
+       { .layout_id = 67,
+         .codecs[0] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+       },
+       { .layout_id = 76,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_nomic,
+         },
+         .codecs[1] = {
+               .name = "topaz",
+               .connections = topaz_inout,
+         },
+       },
+       { .layout_id = 90,
+         .codecs[0] = {
+               .name = "tas",
+               .connections = tas_connections_noline,
+         },
+       },
+       { .layout_id = 94,
+         .codecs[0] = {
+               .name = "onyx",
+               /* but it has an external mic?? how to select? */
+               .connections = onyx_connections_noheadphones,
+         },
+       },
+       { .layout_id = 98,
+         .codecs[0] = {
+               .name = "toonie",
+               .connections = toonie_connections,
+         },
+       },
+       { .layout_id = 100,
+         .codecs[0] = {
+               .name = "topaz",
+               .connections = topaz_input,
+         },
+         .codecs[1] = {
+               .name = "onyx",
+               .connections = onyx_connections_noheadphones,
+         },
+       },
+       {}
+};
+
+static struct layout *find_layout_by_id(unsigned int id)
+{
+       struct layout *l;
+
+       l = layouts;
+       while (l->layout_id) {
+               if (l->layout_id == id)
+                       return l;
+               l++;
+       }
+       return NULL;
+}
+
+static void use_layout(struct layout *l)
+{
+       int i;
+
+       for (i=0; i<MAX_CODECS_PER_BUS; i++) {
+               if (l->codecs[i].name) {
+                       request_module("snd-aoa-codec-%s", l->codecs[i].name);
+               }
+       }
+       /* now we wait for the codecs to call us back */
+}
+
+struct layout_dev;
+
+struct layout_dev_ptr {
+       struct layout_dev *ptr;
+};
+
+struct layout_dev {
+       struct list_head list;
+       struct soundbus_dev *sdev;
+       struct device_node *sound;
+       struct aoa_codec *codecs[MAX_CODECS_PER_BUS];
+       struct layout *layout;
+       struct gpio_runtime gpio;
+
+       /* we need these for headphone/lineout detection */
+       struct snd_kcontrol *headphone_ctrl;
+       struct snd_kcontrol *lineout_ctrl;
+       struct snd_kcontrol *speaker_ctrl;
+       struct snd_kcontrol *headphone_detected_ctrl;
+       struct snd_kcontrol *lineout_detected_ctrl;
+
+       struct layout_dev_ptr selfptr_headphone;
+       struct layout_dev_ptr selfptr_lineout;
+
+       u32 have_lineout_detect:1,
+           have_headphone_detect:1,
+           switch_on_headphone:1,
+           switch_on_lineout:1;
+};
+
+static LIST_HEAD(layouts_list);
+static int layouts_list_items;
+/* this can go away but only if we allow multiple cards,
+ * make the fabric handle all the card stuff, etc... */
+static struct layout_dev *layout_device;
+
+#define control_info   snd_ctl_boolean_mono_info
+
+#define AMP_CONTROL(n, description)                                    \
+static int n##_control_get(struct snd_kcontrol *kcontrol,              \
+                          struct snd_ctl_elem_value *ucontrol)         \
+{                                                                      \
+       struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);        \
+       if (gpio->methods && gpio->methods->get_##n)                    \
+               ucontrol->value.integer.value[0] =                      \
+                       gpio->methods->get_##n(gpio);                   \
+       return 0;                                                       \
+}                                                                      \
+static int n##_control_put(struct snd_kcontrol *kcontrol,              \
+                          struct snd_ctl_elem_value *ucontrol)         \
+{                                                                      \
+       struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);        \
+       if (gpio->methods && gpio->methods->get_##n)                    \
+               gpio->methods->set_##n(gpio,                            \
+                       !!ucontrol->value.integer.value[0]);            \
+       return 1;                                                       \
+}                                                                      \
+static struct snd_kcontrol_new n##_ctl = {                             \
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,                            \
+       .name = description,                                            \
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,                      \
+       .info = control_info,                                           \
+       .get = n##_control_get,                                         \
+       .put = n##_control_put,                                         \
+}
+
+AMP_CONTROL(headphone, "Headphone Switch");
+AMP_CONTROL(speakers, "Speakers Switch");
+AMP_CONTROL(lineout, "Line-Out Switch");
+
+static int detect_choice_get(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
+
+       switch (kcontrol->private_value) {
+       case 0:
+               ucontrol->value.integer.value[0] = ldev->switch_on_headphone;
+               break;
+       case 1:
+               ucontrol->value.integer.value[0] = ldev->switch_on_lineout;
+               break;
+       default:
+               return -ENODEV;
+       }
+       return 0;
+}
+
+static int detect_choice_put(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
+
+       switch (kcontrol->private_value) {
+       case 0:
+               ldev->switch_on_headphone = !!ucontrol->value.integer.value[0];
+               break;
+       case 1:
+               ldev->switch_on_lineout = !!ucontrol->value.integer.value[0];
+               break;
+       default:
+               return -ENODEV;
+       }
+       return 1;
+}
+
+static struct snd_kcontrol_new headphone_detect_choice = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Headphone Detect Autoswitch",
+       .info = control_info,
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .get = detect_choice_get,
+       .put = detect_choice_put,
+       .private_value = 0,
+};
+
+static struct snd_kcontrol_new lineout_detect_choice = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Line-Out Detect Autoswitch",
+       .info = control_info,
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .get = detect_choice_get,
+       .put = detect_choice_put,
+       .private_value = 1,
+};
+
+static int detected_get(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
+       int v;
+
+       switch (kcontrol->private_value) {
+       case 0:
+               v = ldev->gpio.methods->get_detect(&ldev->gpio,
+                                                  AOA_NOTIFY_HEADPHONE);
+               break;
+       case 1:
+               v = ldev->gpio.methods->get_detect(&ldev->gpio,
+                                                  AOA_NOTIFY_LINE_OUT);
+               break;
+       default:
+               return -ENODEV;
+       }
+       ucontrol->value.integer.value[0] = v;
+       return 0;
+}
+
+static struct snd_kcontrol_new headphone_detected = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Headphone Detected",
+       .info = control_info,
+       .access = SNDRV_CTL_ELEM_ACCESS_READ,
+       .get = detected_get,
+       .private_value = 0,
+};
+
+static struct snd_kcontrol_new lineout_detected = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Line-Out Detected",
+       .info = control_info,
+       .access = SNDRV_CTL_ELEM_ACCESS_READ,
+       .get = detected_get,
+       .private_value = 1,
+};
+
+static int check_codec(struct aoa_codec *codec,
+                      struct layout_dev *ldev,
+                      struct codec_connect_info *cci)
+{
+       const u32 *ref;
+       char propname[32];
+       struct codec_connection *cc;
+
+       /* if the codec has a 'codec' node, we require a reference */
+       if (codec->node && (strcmp(codec->node->name, "codec") == 0)) {
+               snprintf(propname, sizeof(propname),
+                        "platform-%s-codec-ref", codec->name);
+               ref = of_get_property(ldev->sound, propname, NULL);
+               if (!ref) {
+                       printk(KERN_INFO "snd-aoa-fabric-layout: "
+                               "required property %s not present\n", propname);
+                       return -ENODEV;
+               }
+               if (*ref != codec->node->linux_phandle) {
+                       printk(KERN_INFO "snd-aoa-fabric-layout: "
+                               "%s doesn't match!\n", propname);
+                       return -ENODEV;
+               }
+       } else {
+               if (layouts_list_items != 1) {
+                       printk(KERN_INFO "snd-aoa-fabric-layout: "
+                               "more than one soundbus, but no references.\n");
+                       return -ENODEV;
+               }
+       }
+       codec->soundbus_dev = ldev->sdev;
+       codec->gpio = &ldev->gpio;
+
+       cc = cci->connections;
+       if (!cc)
+               return -EINVAL;
+
+       printk(KERN_INFO "snd-aoa-fabric-layout: can use this codec\n");
+
+       codec->connected = 0;
+       codec->fabric_data = cc;
+
+       while (cc->connected) {
+               codec->connected |= 1<<cc->codec_bit;
+               cc++;
+       }
+
+       return 0;
+}
+
+static int layout_found_codec(struct aoa_codec *codec)
+{
+       struct layout_dev *ldev;
+       int i;
+
+       list_for_each_entry(ldev, &layouts_list, list) {
+               for (i=0; i<MAX_CODECS_PER_BUS; i++) {
+                       if (!ldev->layout->codecs[i].name)
+                               continue;
+                       if (strcmp(ldev->layout->codecs[i].name, codec->name) == 0) {
+                               if (check_codec(codec,
+                                               ldev,
+                                               &ldev->layout->codecs[i]) == 0)
+                                       return 0;
+                       }
+               }
+       }
+       return -ENODEV;
+}
+
+static void layout_remove_codec(struct aoa_codec *codec)
+{
+       int i;
+       /* here remove the codec from the layout dev's
+        * codec reference */
+
+       codec->soundbus_dev = NULL;
+       codec->gpio = NULL;
+       for (i=0; i<MAX_CODECS_PER_BUS; i++) {
+       }
+}
+
+static void layout_notify(void *data)
+{
+       struct layout_dev_ptr *dptr = data;
+       struct layout_dev *ldev;
+       int v, update;
+       struct snd_kcontrol *detected, *c;
+       struct snd_card *card = aoa_get_card();
+
+       ldev = dptr->ptr;
+       if (data == &ldev->selfptr_headphone) {
+               v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_HEADPHONE);
+               detected = ldev->headphone_detected_ctrl;
+               update = ldev->switch_on_headphone;
+               if (update) {
+                       ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
+                       ldev->gpio.methods->set_headphone(&ldev->gpio, v);
+                       ldev->gpio.methods->set_lineout(&ldev->gpio, 0);
+               }
+       } else if (data == &ldev->selfptr_lineout) {
+               v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_LINE_OUT);
+               detected = ldev->lineout_detected_ctrl;
+               update = ldev->switch_on_lineout;
+               if (update) {
+                       ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
+                       ldev->gpio.methods->set_headphone(&ldev->gpio, 0);
+                       ldev->gpio.methods->set_lineout(&ldev->gpio, v);
+               }
+       } else
+               return;
+
+       if (detected)
+               snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &detected->id);
+       if (update) {
+               c = ldev->headphone_ctrl;
+               if (c)
+                       snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
+               c = ldev->speaker_ctrl;
+               if (c)
+                       snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
+               c = ldev->lineout_ctrl;
+               if (c)
+                       snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
+       }
+}
+
+static void layout_attached_codec(struct aoa_codec *codec)
+{
+       struct codec_connection *cc;
+       struct snd_kcontrol *ctl;
+       int headphones, lineout;
+       struct layout_dev *ldev = layout_device;
+
+       /* need to add this codec to our codec array! */
+
+       cc = codec->fabric_data;
+
+       headphones = codec->gpio->methods->get_detect(codec->gpio,
+                                                     AOA_NOTIFY_HEADPHONE);
+       lineout = codec->gpio->methods->get_detect(codec->gpio,
+                                                  AOA_NOTIFY_LINE_OUT);
+
+       while (cc->connected) {
+               if (cc->connected & CC_SPEAKERS) {
+                       if (headphones <= 0 && lineout <= 0)
+                               ldev->gpio.methods->set_speakers(codec->gpio, 1);
+                       ctl = snd_ctl_new1(&speakers_ctl, codec->gpio);
+                       ldev->speaker_ctrl = ctl;
+                       aoa_snd_ctl_add(ctl);
+               }
+               if (cc->connected & CC_HEADPHONE) {
+                       if (headphones == 1)
+                               ldev->gpio.methods->set_headphone(codec->gpio, 1);
+                       ctl = snd_ctl_new1(&headphone_ctl, codec->gpio);
+                       ldev->headphone_ctrl = ctl;
+                       aoa_snd_ctl_add(ctl);
+                       ldev->have_headphone_detect =
+                               !ldev->gpio.methods
+                                       ->set_notify(&ldev->gpio,
+                                                    AOA_NOTIFY_HEADPHONE,
+                                                    layout_notify,
+                                                    &ldev->selfptr_headphone);
+                       if (ldev->have_headphone_detect) {
+                               ctl = snd_ctl_new1(&headphone_detect_choice,
+                                                  ldev);
+                               aoa_snd_ctl_add(ctl);
+                               ctl = snd_ctl_new1(&headphone_detected,
+                                                  ldev);
+                               ldev->headphone_detected_ctrl = ctl;
+                               aoa_snd_ctl_add(ctl);
+                       }
+               }
+               if (cc->connected & CC_LINEOUT) {
+                       if (lineout == 1)
+                               ldev->gpio.methods->set_lineout(codec->gpio, 1);
+                       ctl = snd_ctl_new1(&lineout_ctl, codec->gpio);
+                       if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+                               strlcpy(ctl->id.name,
+                                       "Headphone Switch", sizeof(ctl->id.name));
+                       ldev->lineout_ctrl = ctl;
+                       aoa_snd_ctl_add(ctl);
+                       ldev->have_lineout_detect =
+                               !ldev->gpio.methods
+                                       ->set_notify(&ldev->gpio,
+                                                    AOA_NOTIFY_LINE_OUT,
+                                                    layout_notify,
+                                                    &ldev->selfptr_lineout);
+                       if (ldev->have_lineout_detect) {
+                               ctl = snd_ctl_new1(&lineout_detect_choice,
+                                                  ldev);
+                               if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+                                       strlcpy(ctl->id.name,
+                                               "Headphone Detect Autoswitch",
+                                               sizeof(ctl->id.name));
+                               aoa_snd_ctl_add(ctl);
+                               ctl = snd_ctl_new1(&lineout_detected,
+                                                  ldev);
+                               if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+                                       strlcpy(ctl->id.name,
+                                               "Headphone Detected",
+                                               sizeof(ctl->id.name));
+                               ldev->lineout_detected_ctrl = ctl;
+                               aoa_snd_ctl_add(ctl);
+                       }
+               }
+               cc++;
+       }
+       /* now update initial state */
+       if (ldev->have_headphone_detect)
+               layout_notify(&ldev->selfptr_headphone);
+       if (ldev->have_lineout_detect)
+               layout_notify(&ldev->selfptr_lineout);
+}
+
+static struct aoa_fabric layout_fabric = {
+       .name = "SoundByLayout",
+       .owner = THIS_MODULE,
+       .found_codec = layout_found_codec,
+       .remove_codec = layout_remove_codec,
+       .attached_codec = layout_attached_codec,
+};
+
+static int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
+{
+       struct device_node *sound = NULL;
+       const unsigned int *layout_id;
+       struct layout *layout;
+       struct layout_dev *ldev = NULL;
+       int err;
+
+       /* hm, currently we can only have one ... */
+       if (layout_device)
+               return -ENODEV;
+
+       /* by breaking out we keep a reference */
+       while ((sound = of_get_next_child(sdev->ofdev.node, sound))) {
+               if (sound->type && strcasecmp(sound->type, "soundchip") == 0)
+                       break;
+       }
+       if (!sound) return -ENODEV;
+
+       layout_id = of_get_property(sound, "layout-id", NULL);
+       if (!layout_id)
+               goto outnodev;
+       printk(KERN_INFO "snd-aoa-fabric-layout: found bus with layout %d\n",
+              *layout_id);
+
+       layout = find_layout_by_id(*layout_id);
+       if (!layout) {
+               printk(KERN_ERR "snd-aoa-fabric-layout: unknown layout\n");
+               goto outnodev;
+       }
+
+       ldev = kzalloc(sizeof(struct layout_dev), GFP_KERNEL);
+       if (!ldev)
+               goto outnodev;
+
+       layout_device = ldev;
+       ldev->sdev = sdev;
+       ldev->sound = sound;
+       ldev->layout = layout;
+       ldev->gpio.node = sound->parent;
+       switch (layout->layout_id) {
+       case 41: /* that unknown machine no one seems to have */
+       case 51: /* PowerBook5,4 */
+       case 58: /* Mac Mini */
+               ldev->gpio.methods = ftr_gpio_methods;
+               printk(KERN_DEBUG
+                      "snd-aoa-fabric-layout: Using direct GPIOs\n");
+               break;
+       default:
+               ldev->gpio.methods = pmf_gpio_methods;
+               printk(KERN_DEBUG
+                      "snd-aoa-fabric-layout: Using PMF GPIOs\n");
+       }
+       ldev->selfptr_headphone.ptr = ldev;
+       ldev->selfptr_lineout.ptr = ldev;
+       sdev->ofdev.dev.driver_data = ldev;
+       list_add(&ldev->list, &layouts_list);
+       layouts_list_items++;
+
+       /* assign these before registering ourselves, so
+        * callbacks that are done during registration
+        * already have the values */
+       sdev->pcmid = ldev->layout->pcmid;
+       if (ldev->layout->busname) {
+               sdev->pcmname = ldev->layout->busname;
+       } else {
+               sdev->pcmname = "Master";
+       }
+
+       ldev->gpio.methods->init(&ldev->gpio);
+
+       err = aoa_fabric_register(&layout_fabric, &sdev->ofdev.dev);
+       if (err && err != -EALREADY) {
+               printk(KERN_INFO "snd-aoa-fabric-layout: can't use,"
+                                " another fabric is active!\n");
+               goto outlistdel;
+       }
+
+       use_layout(layout);
+       ldev->switch_on_headphone = 1;
+       ldev->switch_on_lineout = 1;
+       return 0;
+ outlistdel:
+       /* we won't be using these then... */
+       ldev->gpio.methods->exit(&ldev->gpio);
+       /* reset if we didn't use it */
+       sdev->pcmname = NULL;
+       sdev->pcmid = -1;
+       list_del(&ldev->list);
+       layouts_list_items--;
+ outnodev:
+       of_node_put(sound);
+       layout_device = NULL;
+       kfree(ldev);
+       return -ENODEV;
+}
+
+static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
+{
+       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
+       int i;
+
+       for (i=0; i<MAX_CODECS_PER_BUS; i++) {
+               if (ldev->codecs[i]) {
+                       aoa_fabric_unlink_codec(ldev->codecs[i]);
+               }
+               ldev->codecs[i] = NULL;
+       }
+       list_del(&ldev->list);
+       layouts_list_items--;
+       of_node_put(ldev->sound);
+
+       ldev->gpio.methods->set_notify(&ldev->gpio,
+                                      AOA_NOTIFY_HEADPHONE,
+                                      NULL,
+                                      NULL);
+       ldev->gpio.methods->set_notify(&ldev->gpio,
+                                      AOA_NOTIFY_LINE_OUT,
+                                      NULL,
+                                      NULL);
+
+       ldev->gpio.methods->exit(&ldev->gpio);
+       layout_device = NULL;
+       kfree(ldev);
+       sdev->pcmid = -1;
+       sdev->pcmname = NULL;
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state)
+{
+       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
+
+       if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
+               ldev->gpio.methods->all_amps_off(&ldev->gpio);
+
+       return 0;
+}
+
+static int aoa_fabric_layout_resume(struct soundbus_dev *sdev)
+{
+       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
+
+       if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
+               ldev->gpio.methods->all_amps_restore(&ldev->gpio);
+
+       return 0;
+}
+#endif
+
+static struct soundbus_driver aoa_soundbus_driver = {
+       .name = "snd_aoa_soundbus_drv",
+       .owner = THIS_MODULE,
+       .probe = aoa_fabric_layout_probe,
+       .remove = aoa_fabric_layout_remove,
+#ifdef CONFIG_PM
+       .suspend = aoa_fabric_layout_suspend,
+       .resume = aoa_fabric_layout_resume,
+#endif
+       .driver = {
+               .owner = THIS_MODULE,
+       }
+};
+
+static int __init aoa_fabric_layout_init(void)
+{
+       int err;
+
+       err = soundbus_register_driver(&aoa_soundbus_driver);
+       if (err)
+               return err;
+       return 0;
+}
+
+static void __exit aoa_fabric_layout_exit(void)
+{
+       soundbus_unregister_driver(&aoa_soundbus_driver);
+       aoa_fabric_unregister(&layout_fabric);
+}
+
+module_init(aoa_fabric_layout_init);
+module_exit(aoa_fabric_layout_exit);
diff --git a/sound/aoa/fabrics/snd-aoa-fabric-layout.c b/sound/aoa/fabrics/snd-aoa-fabric-layout.c
deleted file mode 100644 (file)
index dea7abb..0000000
+++ /dev/null
@@ -1,1120 +0,0 @@
-/*
- * Apple Onboard Audio driver -- layout fabric
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- *
- *
- * This fabric module looks for sound codecs
- * based on the layout-id property in the device tree.
- *
- */
-
-#include <asm/prom.h>
-#include <linux/list.h>
-#include <linux/module.h>
-#include "../aoa.h"
-#include "../soundbus/soundbus.h"
-
-MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("Layout-ID fabric for snd-aoa");
-
-#define MAX_CODECS_PER_BUS     2
-
-/* These are the connections the layout fabric
- * knows about. It doesn't really care about the
- * input ones, but I thought I'd separate them
- * to give them proper names. The thing is that
- * Apple usually will distinguish the active output
- * by GPIOs, while the active input is set directly
- * on the codec. Hence we here tell the codec what
- * we think is connected. This information is hard-
- * coded below ... */
-#define CC_SPEAKERS    (1<<0)
-#define CC_HEADPHONE   (1<<1)
-#define CC_LINEOUT     (1<<2)
-#define CC_DIGITALOUT  (1<<3)
-#define CC_LINEIN      (1<<4)
-#define CC_MICROPHONE  (1<<5)
-#define CC_DIGITALIN   (1<<6)
-/* pretty bogus but users complain...
- * This is a flag saying that the LINEOUT
- * should be renamed to HEADPHONE.
- * be careful with input detection! */
-#define CC_LINEOUT_LABELLED_HEADPHONE  (1<<7)
-
-struct codec_connection {
-       /* CC_ flags from above */
-       int connected;
-       /* codec dependent bit to be set in the aoa_codec.connected field.
-        * This intentionally doesn't have any generic flags because the
-        * fabric has to know the codec anyway and all codecs might have
-        * different connectors */
-       int codec_bit;
-};
-
-struct codec_connect_info {
-       char *name;
-       struct codec_connection *connections;
-};
-
-#define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF        (1<<0)
-
-struct layout {
-       unsigned int layout_id;
-       struct codec_connect_info codecs[MAX_CODECS_PER_BUS];
-       int flags;
-       
-       /* if busname is not assigned, we use 'Master' below,
-        * so that our layout table doesn't need to be filled
-        * too much.
-        * We only assign these two if we expect to find more
-        * than one soundbus, i.e. on those machines with
-        * multiple layout-ids */
-       char *busname;
-       int pcmid;
-};
-
-MODULE_ALIAS("sound-layout-36");
-MODULE_ALIAS("sound-layout-41");
-MODULE_ALIAS("sound-layout-45");
-MODULE_ALIAS("sound-layout-47");
-MODULE_ALIAS("sound-layout-48");
-MODULE_ALIAS("sound-layout-49");
-MODULE_ALIAS("sound-layout-50");
-MODULE_ALIAS("sound-layout-51");
-MODULE_ALIAS("sound-layout-56");
-MODULE_ALIAS("sound-layout-57");
-MODULE_ALIAS("sound-layout-58");
-MODULE_ALIAS("sound-layout-60");
-MODULE_ALIAS("sound-layout-61");
-MODULE_ALIAS("sound-layout-62");
-MODULE_ALIAS("sound-layout-64");
-MODULE_ALIAS("sound-layout-65");
-MODULE_ALIAS("sound-layout-66");
-MODULE_ALIAS("sound-layout-67");
-MODULE_ALIAS("sound-layout-68");
-MODULE_ALIAS("sound-layout-69");
-MODULE_ALIAS("sound-layout-70");
-MODULE_ALIAS("sound-layout-72");
-MODULE_ALIAS("sound-layout-76");
-MODULE_ALIAS("sound-layout-80");
-MODULE_ALIAS("sound-layout-82");
-MODULE_ALIAS("sound-layout-84");
-MODULE_ALIAS("sound-layout-86");
-MODULE_ALIAS("sound-layout-90");
-MODULE_ALIAS("sound-layout-92");
-MODULE_ALIAS("sound-layout-94");
-MODULE_ALIAS("sound-layout-96");
-MODULE_ALIAS("sound-layout-98");
-MODULE_ALIAS("sound-layout-100");
-
-/* onyx with all but microphone connected */
-static struct codec_connection onyx_connections_nomic[] = {
-       {
-               .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
-               .codec_bit = 0,
-       },
-       {
-               .connected = CC_DIGITALOUT,
-               .codec_bit = 1,
-       },
-       {
-               .connected = CC_LINEIN,
-               .codec_bit = 2,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-/* onyx on machines without headphone */
-static struct codec_connection onyx_connections_noheadphones[] = {
-       {
-               .connected = CC_SPEAKERS | CC_LINEOUT |
-                            CC_LINEOUT_LABELLED_HEADPHONE,
-               .codec_bit = 0,
-       },
-       {
-               .connected = CC_DIGITALOUT,
-               .codec_bit = 1,
-       },
-       /* FIXME: are these correct? probably not for all the machines
-        * below ... If not this will need separating. */
-       {
-               .connected = CC_LINEIN,
-               .codec_bit = 2,
-       },
-       {
-               .connected = CC_MICROPHONE,
-               .codec_bit = 3,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-/* onyx on machines with real line-out */
-static struct codec_connection onyx_connections_reallineout[] = {
-       {
-               .connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE,
-               .codec_bit = 0,
-       },
-       {
-               .connected = CC_DIGITALOUT,
-               .codec_bit = 1,
-       },
-       {
-               .connected = CC_LINEIN,
-               .codec_bit = 2,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-/* tas on machines without line out */
-static struct codec_connection tas_connections_nolineout[] = {
-       {
-               .connected = CC_SPEAKERS | CC_HEADPHONE,
-               .codec_bit = 0,
-       },
-       {
-               .connected = CC_LINEIN,
-               .codec_bit = 2,
-       },
-       {
-               .connected = CC_MICROPHONE,
-               .codec_bit = 3,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-/* tas on machines with neither line out nor line in */
-static struct codec_connection tas_connections_noline[] = {
-       {
-               .connected = CC_SPEAKERS | CC_HEADPHONE,
-               .codec_bit = 0,
-       },
-       {
-               .connected = CC_MICROPHONE,
-               .codec_bit = 3,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-/* tas on machines without microphone */
-static struct codec_connection tas_connections_nomic[] = {
-       {
-               .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
-               .codec_bit = 0,
-       },
-       {
-               .connected = CC_LINEIN,
-               .codec_bit = 2,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-/* tas on machines with everything connected */
-static struct codec_connection tas_connections_all[] = {
-       {
-               .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
-               .codec_bit = 0,
-       },
-       {
-               .connected = CC_LINEIN,
-               .codec_bit = 2,
-       },
-       {
-               .connected = CC_MICROPHONE,
-               .codec_bit = 3,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-static struct codec_connection toonie_connections[] = {
-       {
-               .connected = CC_SPEAKERS | CC_HEADPHONE,
-               .codec_bit = 0,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-static struct codec_connection topaz_input[] = {
-       {
-               .connected = CC_DIGITALIN,
-               .codec_bit = 0,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-static struct codec_connection topaz_output[] = {
-       {
-               .connected = CC_DIGITALOUT,
-               .codec_bit = 1,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-static struct codec_connection topaz_inout[] = {
-       {
-               .connected = CC_DIGITALIN,
-               .codec_bit = 0,
-       },
-       {
-               .connected = CC_DIGITALOUT,
-               .codec_bit = 1,
-       },
-       {} /* terminate array by .connected == 0 */
-};
-
-static struct layout layouts[] = {
-       /* last PowerBooks (15" Oct 2005) */
-       { .layout_id = 82,
-         .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-         .codecs[1] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       /* PowerMac9,1 */
-       { .layout_id = 60,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_reallineout,
-         },
-       },
-       /* PowerMac9,1 */
-       { .layout_id = 61,
-         .codecs[0] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       /* PowerBook5,7 */
-       { .layout_id = 64,
-         .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-       },
-       /* PowerBook5,7 */
-       { .layout_id = 65,
-         .codecs[0] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       /* PowerBook5,9 [17" Oct 2005] */
-       { .layout_id = 84,
-         .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-         .codecs[1] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       /* PowerMac8,1 */
-       { .layout_id = 45,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-         .codecs[1] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       /* Quad PowerMac (analog in, analog/digital out) */
-       { .layout_id = 68,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_nomic,
-         },
-       },
-       /* Quad PowerMac (digital in) */
-       { .layout_id = 69,
-         .codecs[0] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-         .busname = "digital in", .pcmid = 1 },
-       /* Early 2005 PowerBook (PowerBook 5,6) */
-       { .layout_id = 70,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_nolineout,
-         },
-       },
-       /* PowerBook 5,4 */
-       { .layout_id = 51,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_nolineout,
-         },
-       },
-       /* PowerBook6,7 */
-       { .layout_id = 80,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_noline,
-         },
-       },
-       /* PowerBook6,8 */
-       { .layout_id = 72,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_nolineout,
-         },
-       },
-       /* PowerMac8,2 */
-       { .layout_id = 86,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_nomic,
-         },
-         .codecs[1] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       /* PowerBook6,7 */
-       { .layout_id = 92,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_nolineout,
-         },
-       },
-       /* PowerMac10,1 (Mac Mini) */
-       { .layout_id = 58,
-         .codecs[0] = {
-               .name = "toonie",
-               .connections = toonie_connections,
-         },
-       },
-       {
-         .layout_id = 96,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-       },
-       /* unknown, untested, but this comes from Apple */
-       { .layout_id = 41,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_all,
-         },
-       },
-       { .layout_id = 36,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_nomic,
-         },
-         .codecs[1] = {
-               .name = "topaz",
-               .connections = topaz_inout,
-         },
-       },
-       { .layout_id = 47,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-       },
-       { .layout_id = 48,
-         .codecs[0] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       { .layout_id = 49,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_nomic,
-         },
-       },
-       { .layout_id = 50,
-         .codecs[0] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       { .layout_id = 56,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-       },
-       { .layout_id = 57,
-         .codecs[0] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       { .layout_id = 62,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-         .codecs[1] = {
-               .name = "topaz",
-               .connections = topaz_output,
-         },
-       },
-       { .layout_id = 66,
-         .codecs[0] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-       },
-       { .layout_id = 67,
-         .codecs[0] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-       },
-       { .layout_id = 76,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_nomic,
-         },
-         .codecs[1] = {
-               .name = "topaz",
-               .connections = topaz_inout,
-         },
-       },
-       { .layout_id = 90,
-         .codecs[0] = {
-               .name = "tas",
-               .connections = tas_connections_noline,
-         },
-       },
-       { .layout_id = 94,
-         .codecs[0] = {
-               .name = "onyx",
-               /* but it has an external mic?? how to select? */
-               .connections = onyx_connections_noheadphones,
-         },
-       },
-       { .layout_id = 98,
-         .codecs[0] = {
-               .name = "toonie",
-               .connections = toonie_connections,
-         },
-       },
-       { .layout_id = 100,
-         .codecs[0] = {
-               .name = "topaz",
-               .connections = topaz_input,
-         },
-         .codecs[1] = {
-               .name = "onyx",
-               .connections = onyx_connections_noheadphones,
-         },
-       },
-       {}
-};
-
-static struct layout *find_layout_by_id(unsigned int id)
-{
-       struct layout *l;
-
-       l = layouts;
-       while (l->layout_id) {
-               if (l->layout_id == id)
-                       return l;
-               l++;
-       }
-       return NULL;
-}
-
-static void use_layout(struct layout *l)
-{
-       int i;
-
-       for (i=0; i<MAX_CODECS_PER_BUS; i++) {
-               if (l->codecs[i].name) {
-                       request_module("snd-aoa-codec-%s", l->codecs[i].name);
-               }
-       }
-       /* now we wait for the codecs to call us back */
-}
-
-struct layout_dev;
-
-struct layout_dev_ptr {
-       struct layout_dev *ptr;
-};
-
-struct layout_dev {
-       struct list_head list;
-       struct soundbus_dev *sdev;
-       struct device_node *sound;
-       struct aoa_codec *codecs[MAX_CODECS_PER_BUS];
-       struct layout *layout;
-       struct gpio_runtime gpio;
-
-       /* we need these for headphone/lineout detection */
-       struct snd_kcontrol *headphone_ctrl;
-       struct snd_kcontrol *lineout_ctrl;
-       struct snd_kcontrol *speaker_ctrl;
-       struct snd_kcontrol *headphone_detected_ctrl;
-       struct snd_kcontrol *lineout_detected_ctrl;
-
-       struct layout_dev_ptr selfptr_headphone;
-       struct layout_dev_ptr selfptr_lineout;
-
-       u32 have_lineout_detect:1,
-           have_headphone_detect:1,
-           switch_on_headphone:1,
-           switch_on_lineout:1;
-};
-
-static LIST_HEAD(layouts_list);
-static int layouts_list_items;
-/* this can go away but only if we allow multiple cards,
- * make the fabric handle all the card stuff, etc... */
-static struct layout_dev *layout_device;
-
-#define control_info   snd_ctl_boolean_mono_info
-
-#define AMP_CONTROL(n, description)                                    \
-static int n##_control_get(struct snd_kcontrol *kcontrol,              \
-                          struct snd_ctl_elem_value *ucontrol)         \
-{                                                                      \
-       struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);        \
-       if (gpio->methods && gpio->methods->get_##n)                    \
-               ucontrol->value.integer.value[0] =                      \
-                       gpio->methods->get_##n(gpio);                   \
-       return 0;                                                       \
-}                                                                      \
-static int n##_control_put(struct snd_kcontrol *kcontrol,              \
-                          struct snd_ctl_elem_value *ucontrol)         \
-{                                                                      \
-       struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);        \
-       if (gpio->methods && gpio->methods->get_##n)                    \
-               gpio->methods->set_##n(gpio,                            \
-                       !!ucontrol->value.integer.value[0]);            \
-       return 1;                                                       \
-}                                                                      \
-static struct snd_kcontrol_new n##_ctl = {                             \
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,                            \
-       .name = description,                                            \
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,                      \
-       .info = control_info,                                           \
-       .get = n##_control_get,                                         \
-       .put = n##_control_put,                                         \
-}
-
-AMP_CONTROL(headphone, "Headphone Switch");
-AMP_CONTROL(speakers, "Speakers Switch");
-AMP_CONTROL(lineout, "Line-Out Switch");
-
-static int detect_choice_get(struct snd_kcontrol *kcontrol,
-                            struct snd_ctl_elem_value *ucontrol)
-{
-       struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
-
-       switch (kcontrol->private_value) {
-       case 0:
-               ucontrol->value.integer.value[0] = ldev->switch_on_headphone;
-               break;
-       case 1:
-               ucontrol->value.integer.value[0] = ldev->switch_on_lineout;
-               break;
-       default:
-               return -ENODEV;
-       }
-       return 0;
-}
-
-static int detect_choice_put(struct snd_kcontrol *kcontrol,
-                            struct snd_ctl_elem_value *ucontrol)
-{
-       struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
-
-       switch (kcontrol->private_value) {
-       case 0:
-               ldev->switch_on_headphone = !!ucontrol->value.integer.value[0];
-               break;
-       case 1:
-               ldev->switch_on_lineout = !!ucontrol->value.integer.value[0];
-               break;
-       default:
-               return -ENODEV;
-       }
-       return 1;
-}
-
-static struct snd_kcontrol_new headphone_detect_choice = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Headphone Detect Autoswitch",
-       .info = control_info,
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .get = detect_choice_get,
-       .put = detect_choice_put,
-       .private_value = 0,
-};
-
-static struct snd_kcontrol_new lineout_detect_choice = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Line-Out Detect Autoswitch",
-       .info = control_info,
-       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
-       .get = detect_choice_get,
-       .put = detect_choice_put,
-       .private_value = 1,
-};
-
-static int detected_get(struct snd_kcontrol *kcontrol,
-                       struct snd_ctl_elem_value *ucontrol)
-{
-       struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
-       int v;
-
-       switch (kcontrol->private_value) {
-       case 0:
-               v = ldev->gpio.methods->get_detect(&ldev->gpio,
-                                                  AOA_NOTIFY_HEADPHONE);
-               break;
-       case 1:
-               v = ldev->gpio.methods->get_detect(&ldev->gpio,
-                                                  AOA_NOTIFY_LINE_OUT);
-               break;
-       default:
-               return -ENODEV;
-       }
-       ucontrol->value.integer.value[0] = v;
-       return 0;
-}
-
-static struct snd_kcontrol_new headphone_detected = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Headphone Detected",
-       .info = control_info,
-       .access = SNDRV_CTL_ELEM_ACCESS_READ,
-       .get = detected_get,
-       .private_value = 0,
-};
-
-static struct snd_kcontrol_new lineout_detected = {
-       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-       .name = "Line-Out Detected",
-       .info = control_info,
-       .access = SNDRV_CTL_ELEM_ACCESS_READ,
-       .get = detected_get,
-       .private_value = 1,
-};
-
-static int check_codec(struct aoa_codec *codec,
-                      struct layout_dev *ldev,
-                      struct codec_connect_info *cci)
-{
-       const u32 *ref;
-       char propname[32];
-       struct codec_connection *cc;
-
-       /* if the codec has a 'codec' node, we require a reference */
-       if (codec->node && (strcmp(codec->node->name, "codec") == 0)) {
-               snprintf(propname, sizeof(propname),
-                        "platform-%s-codec-ref", codec->name);
-               ref = of_get_property(ldev->sound, propname, NULL);
-               if (!ref) {
-                       printk(KERN_INFO "snd-aoa-fabric-layout: "
-                               "required property %s not present\n", propname);
-                       return -ENODEV;
-               }
-               if (*ref != codec->node->linux_phandle) {
-                       printk(KERN_INFO "snd-aoa-fabric-layout: "
-                               "%s doesn't match!\n", propname);
-                       return -ENODEV;
-               }
-       } else {
-               if (layouts_list_items != 1) {
-                       printk(KERN_INFO "snd-aoa-fabric-layout: "
-                               "more than one soundbus, but no references.\n");
-                       return -ENODEV;
-               }
-       }
-       codec->soundbus_dev = ldev->sdev;
-       codec->gpio = &ldev->gpio;
-
-       cc = cci->connections;
-       if (!cc)
-               return -EINVAL;
-
-       printk(KERN_INFO "snd-aoa-fabric-layout: can use this codec\n");
-
-       codec->connected = 0;
-       codec->fabric_data = cc;
-
-       while (cc->connected) {
-               codec->connected |= 1<<cc->codec_bit;
-               cc++;
-       }
-
-       return 0;
-}
-
-static int layout_found_codec(struct aoa_codec *codec)
-{
-       struct layout_dev *ldev;
-       int i;
-
-       list_for_each_entry(ldev, &layouts_list, list) {
-               for (i=0; i<MAX_CODECS_PER_BUS; i++) {
-                       if (!ldev->layout->codecs[i].name)
-                               continue;
-                       if (strcmp(ldev->layout->codecs[i].name, codec->name) == 0) {
-                               if (check_codec(codec,
-                                               ldev,
-                                               &ldev->layout->codecs[i]) == 0)
-                                       return 0;
-                       }
-               }
-       }
-       return -ENODEV;
-}
-
-static void layout_remove_codec(struct aoa_codec *codec)
-{
-       int i;
-       /* here remove the codec from the layout dev's
-        * codec reference */
-
-       codec->soundbus_dev = NULL;
-       codec->gpio = NULL;
-       for (i=0; i<MAX_CODECS_PER_BUS; i++) {
-       }
-}
-
-static void layout_notify(void *data)
-{
-       struct layout_dev_ptr *dptr = data;
-       struct layout_dev *ldev;
-       int v, update;
-       struct snd_kcontrol *detected, *c;
-       struct snd_card *card = aoa_get_card();
-
-       ldev = dptr->ptr;
-       if (data == &ldev->selfptr_headphone) {
-               v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_HEADPHONE);
-               detected = ldev->headphone_detected_ctrl;
-               update = ldev->switch_on_headphone;
-               if (update) {
-                       ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
-                       ldev->gpio.methods->set_headphone(&ldev->gpio, v);
-                       ldev->gpio.methods->set_lineout(&ldev->gpio, 0);
-               }
-       } else if (data == &ldev->selfptr_lineout) {
-               v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_LINE_OUT);
-               detected = ldev->lineout_detected_ctrl;
-               update = ldev->switch_on_lineout;
-               if (update) {
-                       ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
-                       ldev->gpio.methods->set_headphone(&ldev->gpio, 0);
-                       ldev->gpio.methods->set_lineout(&ldev->gpio, v);
-               }
-       } else
-               return;
-
-       if (detected)
-               snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &detected->id);
-       if (update) {
-               c = ldev->headphone_ctrl;
-               if (c)
-                       snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
-               c = ldev->speaker_ctrl;
-               if (c)
-                       snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
-               c = ldev->lineout_ctrl;
-               if (c)
-                       snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
-       }
-}
-
-static void layout_attached_codec(struct aoa_codec *codec)
-{
-       struct codec_connection *cc;
-       struct snd_kcontrol *ctl;
-       int headphones, lineout;
-       struct layout_dev *ldev = layout_device;
-
-       /* need to add this codec to our codec array! */
-
-       cc = codec->fabric_data;
-
-       headphones = codec->gpio->methods->get_detect(codec->gpio,
-                                                     AOA_NOTIFY_HEADPHONE);
-       lineout = codec->gpio->methods->get_detect(codec->gpio,
-                                                  AOA_NOTIFY_LINE_OUT);
-
-       while (cc->connected) {
-               if (cc->connected & CC_SPEAKERS) {
-                       if (headphones <= 0 && lineout <= 0)
-                               ldev->gpio.methods->set_speakers(codec->gpio, 1);
-                       ctl = snd_ctl_new1(&speakers_ctl, codec->gpio);
-                       ldev->speaker_ctrl = ctl;
-                       aoa_snd_ctl_add(ctl);
-               }
-               if (cc->connected & CC_HEADPHONE) {
-                       if (headphones == 1)
-                               ldev->gpio.methods->set_headphone(codec->gpio, 1);
-                       ctl = snd_ctl_new1(&headphone_ctl, codec->gpio);
-                       ldev->headphone_ctrl = ctl;
-                       aoa_snd_ctl_add(ctl);
-                       ldev->have_headphone_detect =
-                               !ldev->gpio.methods
-                                       ->set_notify(&ldev->gpio,
-                                                    AOA_NOTIFY_HEADPHONE,
-                                                    layout_notify,
-                                                    &ldev->selfptr_headphone);
-                       if (ldev->have_headphone_detect) {
-                               ctl = snd_ctl_new1(&headphone_detect_choice,
-                                                  ldev);
-                               aoa_snd_ctl_add(ctl);
-                               ctl = snd_ctl_new1(&headphone_detected,
-                                                  ldev);
-                               ldev->headphone_detected_ctrl = ctl;
-                               aoa_snd_ctl_add(ctl);
-                       }
-               }
-               if (cc->connected & CC_LINEOUT) {
-                       if (lineout == 1)
-                               ldev->gpio.methods->set_lineout(codec->gpio, 1);
-                       ctl = snd_ctl_new1(&lineout_ctl, codec->gpio);
-                       if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
-                               strlcpy(ctl->id.name,
-                                       "Headphone Switch", sizeof(ctl->id.name));
-                       ldev->lineout_ctrl = ctl;
-                       aoa_snd_ctl_add(ctl);
-                       ldev->have_lineout_detect =
-                               !ldev->gpio.methods
-                                       ->set_notify(&ldev->gpio,
-                                                    AOA_NOTIFY_LINE_OUT,
-                                                    layout_notify,
-                                                    &ldev->selfptr_lineout);
-                       if (ldev->have_lineout_detect) {
-                               ctl = snd_ctl_new1(&lineout_detect_choice,
-                                                  ldev);
-                               if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
-                                       strlcpy(ctl->id.name,
-                                               "Headphone Detect Autoswitch",
-                                               sizeof(ctl->id.name));
-                               aoa_snd_ctl_add(ctl);
-                               ctl = snd_ctl_new1(&lineout_detected,
-                                                  ldev);
-                               if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
-                                       strlcpy(ctl->id.name,
-                                               "Headphone Detected",
-                                               sizeof(ctl->id.name));
-                               ldev->lineout_detected_ctrl = ctl;
-                               aoa_snd_ctl_add(ctl);
-                       }
-               }
-               cc++;
-       }
-       /* now update initial state */
-       if (ldev->have_headphone_detect)
-               layout_notify(&ldev->selfptr_headphone);
-       if (ldev->have_lineout_detect)
-               layout_notify(&ldev->selfptr_lineout);
-}
-
-static struct aoa_fabric layout_fabric = {
-       .name = "SoundByLayout",
-       .owner = THIS_MODULE,
-       .found_codec = layout_found_codec,
-       .remove_codec = layout_remove_codec,
-       .attached_codec = layout_attached_codec,
-};
-
-static int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
-{
-       struct device_node *sound = NULL;
-       const unsigned int *layout_id;
-       struct layout *layout;
-       struct layout_dev *ldev = NULL;
-       int err;
-
-       /* hm, currently we can only have one ... */
-       if (layout_device)
-               return -ENODEV;
-
-       /* by breaking out we keep a reference */
-       while ((sound = of_get_next_child(sdev->ofdev.node, sound))) {
-               if (sound->type && strcasecmp(sound->type, "soundchip") == 0)
-                       break;
-       }
-       if (!sound) return -ENODEV;
-
-       layout_id = of_get_property(sound, "layout-id", NULL);
-       if (!layout_id)
-               goto outnodev;
-       printk(KERN_INFO "snd-aoa-fabric-layout: found bus with layout %d\n",
-              *layout_id);
-
-       layout = find_layout_by_id(*layout_id);
-       if (!layout) {
-               printk(KERN_ERR "snd-aoa-fabric-layout: unknown layout\n");
-               goto outnodev;
-       }
-
-       ldev = kzalloc(sizeof(struct layout_dev), GFP_KERNEL);
-       if (!ldev)
-               goto outnodev;
-
-       layout_device = ldev;
-       ldev->sdev = sdev;
-       ldev->sound = sound;
-       ldev->layout = layout;
-       ldev->gpio.node = sound->parent;
-       switch (layout->layout_id) {
-       case 41: /* that unknown machine no one seems to have */
-       case 51: /* PowerBook5,4 */
-       case 58: /* Mac Mini */
-               ldev->gpio.methods = ftr_gpio_methods;
-               printk(KERN_DEBUG
-                      "snd-aoa-fabric-layout: Using direct GPIOs\n");
-               break;
-       default:
-               ldev->gpio.methods = pmf_gpio_methods;
-               printk(KERN_DEBUG
-                      "snd-aoa-fabric-layout: Using PMF GPIOs\n");
-       }
-       ldev->selfptr_headphone.ptr = ldev;
-       ldev->selfptr_lineout.ptr = ldev;
-       sdev->ofdev.dev.driver_data = ldev;
-       list_add(&ldev->list, &layouts_list);
-       layouts_list_items++;
-
-       /* assign these before registering ourselves, so
-        * callbacks that are done during registration
-        * already have the values */
-       sdev->pcmid = ldev->layout->pcmid;
-       if (ldev->layout->busname) {
-               sdev->pcmname = ldev->layout->busname;
-       } else {
-               sdev->pcmname = "Master";
-       }
-
-       ldev->gpio.methods->init(&ldev->gpio);
-
-       err = aoa_fabric_register(&layout_fabric, &sdev->ofdev.dev);
-       if (err && err != -EALREADY) {
-               printk(KERN_INFO "snd-aoa-fabric-layout: can't use,"
-                                " another fabric is active!\n");
-               goto outlistdel;
-       }
-
-       use_layout(layout);
-       ldev->switch_on_headphone = 1;
-       ldev->switch_on_lineout = 1;
-       return 0;
- outlistdel:
-       /* we won't be using these then... */
-       ldev->gpio.methods->exit(&ldev->gpio);
-       /* reset if we didn't use it */
-       sdev->pcmname = NULL;
-       sdev->pcmid = -1;
-       list_del(&ldev->list);
-       layouts_list_items--;
- outnodev:
-       of_node_put(sound);
-       layout_device = NULL;
-       kfree(ldev);
-       return -ENODEV;
-}
-
-static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
-{
-       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
-       int i;
-
-       for (i=0; i<MAX_CODECS_PER_BUS; i++) {
-               if (ldev->codecs[i]) {
-                       aoa_fabric_unlink_codec(ldev->codecs[i]);
-               }
-               ldev->codecs[i] = NULL;
-       }
-       list_del(&ldev->list);
-       layouts_list_items--;
-       of_node_put(ldev->sound);
-
-       ldev->gpio.methods->set_notify(&ldev->gpio,
-                                      AOA_NOTIFY_HEADPHONE,
-                                      NULL,
-                                      NULL);
-       ldev->gpio.methods->set_notify(&ldev->gpio,
-                                      AOA_NOTIFY_LINE_OUT,
-                                      NULL,
-                                      NULL);
-
-       ldev->gpio.methods->exit(&ldev->gpio);
-       layout_device = NULL;
-       kfree(ldev);
-       sdev->pcmid = -1;
-       sdev->pcmname = NULL;
-       return 0;
-}
-
-#ifdef CONFIG_PM
-static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state)
-{
-       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
-
-       if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
-               ldev->gpio.methods->all_amps_off(&ldev->gpio);
-
-       return 0;
-}
-
-static int aoa_fabric_layout_resume(struct soundbus_dev *sdev)
-{
-       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
-
-       if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
-               ldev->gpio.methods->all_amps_restore(&ldev->gpio);
-
-       return 0;
-}
-#endif
-
-static struct soundbus_driver aoa_soundbus_driver = {
-       .name = "snd_aoa_soundbus_drv",
-       .owner = THIS_MODULE,
-       .probe = aoa_fabric_layout_probe,
-       .remove = aoa_fabric_layout_remove,
-#ifdef CONFIG_PM
-       .suspend = aoa_fabric_layout_suspend,
-       .resume = aoa_fabric_layout_resume,
-#endif
-       .driver = {
-               .owner = THIS_MODULE,
-       }
-};
-
-static int __init aoa_fabric_layout_init(void)
-{
-       int err;
-
-       err = soundbus_register_driver(&aoa_soundbus_driver);
-       if (err)
-               return err;
-       return 0;
-}
-
-static void __exit aoa_fabric_layout_exit(void)
-{
-       soundbus_unregister_driver(&aoa_soundbus_driver);
-       aoa_fabric_unregister(&layout_fabric);
-}
-
-module_init(aoa_fabric_layout_init);
-module_exit(aoa_fabric_layout_exit);
index e57a5cf..1b949b2 100644 (file)
@@ -1,2 +1,2 @@
 obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += snd-aoa-i2sbus.o
-snd-aoa-i2sbus-objs := i2sbus-core.o i2sbus-pcm.o i2sbus-control.o
+snd-aoa-i2sbus-objs := core.o pcm.o control.o
diff --git a/sound/aoa/soundbus/i2sbus/control.c b/sound/aoa/soundbus/i2sbus/control.c
new file mode 100644 (file)
index 0000000..87beb4a
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * i2sbus driver -- bus control routines
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+#include <asm/prom.h>
+#include <asm/macio.h>
+#include <asm/pmac_feature.h>
+#include <asm/pmac_pfunc.h>
+#include <asm/keylargo.h>
+
+#include "i2sbus.h"
+
+int i2sbus_control_init(struct macio_dev* dev, struct i2sbus_control **c)
+{
+       *c = kzalloc(sizeof(struct i2sbus_control), GFP_KERNEL);
+       if (!*c)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&(*c)->list);
+
+       (*c)->macio = dev->bus->chip;
+       return 0;
+}
+
+void i2sbus_control_destroy(struct i2sbus_control *c)
+{
+       kfree(c);
+}
+
+/* this is serialised externally */
+int i2sbus_control_add_dev(struct i2sbus_control *c,
+                          struct i2sbus_dev *i2sdev)
+{
+       struct device_node *np;
+
+       np = i2sdev->sound.ofdev.node;
+       i2sdev->enable = pmf_find_function(np, "enable");
+       i2sdev->cell_enable = pmf_find_function(np, "cell-enable");
+       i2sdev->clock_enable = pmf_find_function(np, "clock-enable");
+       i2sdev->cell_disable = pmf_find_function(np, "cell-disable");
+       i2sdev->clock_disable = pmf_find_function(np, "clock-disable");
+
+       /* if the bus number is not 0 or 1 we absolutely need to use
+        * the platform functions -- there's nothing in Darwin that
+        * would allow seeing a system behind what the FCRs are then,
+        * and I don't want to go parsing a bunch of platform functions
+        * by hand to try finding a system... */
+       if (i2sdev->bus_number != 0 && i2sdev->bus_number != 1 &&
+           (!i2sdev->enable ||
+            !i2sdev->cell_enable || !i2sdev->clock_enable ||
+            !i2sdev->cell_disable || !i2sdev->clock_disable)) {
+               pmf_put_function(i2sdev->enable);
+               pmf_put_function(i2sdev->cell_enable);
+               pmf_put_function(i2sdev->clock_enable);
+               pmf_put_function(i2sdev->cell_disable);
+               pmf_put_function(i2sdev->clock_disable);
+               return -ENODEV;
+       }
+
+       list_add(&i2sdev->item, &c->list);
+
+       return 0;
+}
+
+void i2sbus_control_remove_dev(struct i2sbus_control *c,
+                              struct i2sbus_dev *i2sdev)
+{
+       /* this is serialised externally */
+       list_del(&i2sdev->item);
+       if (list_empty(&c->list))
+               i2sbus_control_destroy(c);
+}
+
+int i2sbus_control_enable(struct i2sbus_control *c,
+                         struct i2sbus_dev *i2sdev)
+{
+       struct pmf_args args = { .count = 0 };
+       struct macio_chip *macio = c->macio;
+
+       if (i2sdev->enable)
+               return pmf_call_one(i2sdev->enable, &args);
+
+       if (macio == NULL || macio->base == NULL)
+               return -ENODEV;
+
+       switch (i2sdev->bus_number) {
+       case 0:
+               /* these need to be locked or done through
+                * newly created feature calls! */
+               MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_ENABLE);
+               break;
+       case 1:
+               MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_ENABLE);
+               break;
+       default:
+               return -ENODEV;
+       }
+       return 0;
+}
+
+int i2sbus_control_cell(struct i2sbus_control *c,
+                       struct i2sbus_dev *i2sdev,
+                       int enable)
+{
+       struct pmf_args args = { .count = 0 };
+       struct macio_chip *macio = c->macio;
+
+       switch (enable) {
+       case 0:
+               if (i2sdev->cell_disable)
+                       return pmf_call_one(i2sdev->cell_disable, &args);
+               break;
+       case 1:
+               if (i2sdev->cell_enable)
+                       return pmf_call_one(i2sdev->cell_enable, &args);
+               break;
+       default:
+               printk(KERN_ERR "i2sbus: INVALID CELL ENABLE VALUE\n");
+               return -ENODEV;
+       }
+
+       if (macio == NULL || macio->base == NULL)
+               return -ENODEV;
+
+       switch (i2sdev->bus_number) {
+       case 0:
+               if (enable)
+                       MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE);
+               else
+                       MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE);
+               break;
+       case 1:
+               if (enable)
+                       MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE);
+               else
+                       MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE);
+               break;
+       default:
+               return -ENODEV;
+       }
+       return 0;
+}
+
+int i2sbus_control_clock(struct i2sbus_control *c,
+                        struct i2sbus_dev *i2sdev,
+                        int enable)
+{
+       struct pmf_args args = { .count = 0 };
+       struct macio_chip *macio = c->macio;
+
+       switch (enable) {
+       case 0:
+               if (i2sdev->clock_disable)
+                       return pmf_call_one(i2sdev->clock_disable, &args);
+               break;
+       case 1:
+               if (i2sdev->clock_enable)
+                       return pmf_call_one(i2sdev->clock_enable, &args);
+               break;
+       default:
+               printk(KERN_ERR "i2sbus: INVALID CLOCK ENABLE VALUE\n");
+               return -ENODEV;
+       }
+
+       if (macio == NULL || macio->base == NULL)
+               return -ENODEV;
+
+       switch (i2sdev->bus_number) {
+       case 0:
+               if (enable)
+                       MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
+               else
+                       MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
+               break;
+       case 1:
+               if (enable)
+                       MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT);
+               else
+                       MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT);
+               break;
+       default:
+               return -ENODEV;
+       }
+       return 0;
+}
diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c
new file mode 100644 (file)
index 0000000..be468ed
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * i2sbus driver
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+
+#include <asm/macio.h>
+#include <asm/dbdma.h>
+
+#include "../soundbus.h"
+#include "i2sbus.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_DESCRIPTION("Apple Soundbus: I2S support");
+
+static int force;
+module_param(force, int, 0444);
+MODULE_PARM_DESC(force, "Force loading i2sbus even when"
+                       " no layout-id property is present");
+
+static struct of_device_id i2sbus_match[] = {
+       { .name = "i2s" },
+       { }
+};
+
+MODULE_DEVICE_TABLE(of, i2sbus_match);
+
+static int alloc_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev,
+                                      struct dbdma_command_mem *r,
+                                      int numcmds)
+{
+       /* one more for rounding, one for branch back, one for stop command */
+       r->size = (numcmds + 3) * sizeof(struct dbdma_cmd);
+       /* We use the PCI APIs for now until the generic one gets fixed
+        * enough or until we get some macio-specific versions
+        */
+       r->space = dma_alloc_coherent(
+                       &macio_get_pci_dev(i2sdev->macio)->dev,
+                       r->size,
+                       &r->bus_addr,
+                       GFP_KERNEL);
+
+       if (!r->space) return -ENOMEM;
+
+       memset(r->space, 0, r->size);
+       r->cmds = (void*)DBDMA_ALIGN(r->space);
+       r->bus_cmd_start = r->bus_addr +
+                          (dma_addr_t)((char*)r->cmds - (char*)r->space);
+
+       return 0;
+}
+
+static void free_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev,
+                                      struct dbdma_command_mem *r)
+{
+       if (!r->space) return;
+
+       dma_free_coherent(&macio_get_pci_dev(i2sdev->macio)->dev,
+                           r->size, r->space, r->bus_addr);
+}
+
+static void i2sbus_release_dev(struct device *dev)
+{
+       struct i2sbus_dev *i2sdev;
+       int i;
+
+       i2sdev = container_of(dev, struct i2sbus_dev, sound.ofdev.dev);
+
+       if (i2sdev->intfregs) iounmap(i2sdev->intfregs);
+       if (i2sdev->out.dbdma) iounmap(i2sdev->out.dbdma);
+       if (i2sdev->in.dbdma) iounmap(i2sdev->in.dbdma);
+       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
+               if (i2sdev->allocated_resource[i])
+                       release_and_free_resource(i2sdev->allocated_resource[i]);
+       free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring);
+       free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring);
+       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
+               free_irq(i2sdev->interrupts[i], i2sdev);
+       i2sbus_control_remove_dev(i2sdev->control, i2sdev);
+       mutex_destroy(&i2sdev->lock);
+       kfree(i2sdev);
+}
+
+static irqreturn_t i2sbus_bus_intr(int irq, void *devid)
+{
+       struct i2sbus_dev *dev = devid;
+       u32 intreg;
+
+       spin_lock(&dev->low_lock);
+       intreg = in_le32(&dev->intfregs->intr_ctl);
+
+       /* acknowledge interrupt reasons */
+       out_le32(&dev->intfregs->intr_ctl, intreg);
+
+       spin_unlock(&dev->low_lock);
+
+       return IRQ_HANDLED;
+}
+
+
+/*
+ * XXX FIXME: We test the layout_id's here to get the proper way of
+ * mapping in various registers, thanks to bugs in Apple device-trees.
+ * We could instead key off the machine model and the name of the i2s
+ * node (i2s-a). This we'll do when we move it all to macio_asic.c
+ * and have that export items for each sub-node too.
+ */
+static int i2sbus_get_and_fixup_rsrc(struct device_node *np, int index,
+                                    int layout, struct resource *res)
+{
+       struct device_node *parent;
+       int pindex, rc = -ENXIO;
+       const u32 *reg;
+
+       /* Machines with layout 76 and 36 (K2 based) have a weird device
+        * tree what we need to special case.
+        * Normal machines just fetch the resource from the i2s-X node.
+        * Darwin further divides normal machines into old and new layouts
+        * with a subtely different code path but that doesn't seem necessary
+        * in practice, they just bloated it. In addition, even on our K2
+        * case the i2s-modem node, if we ever want to handle it, uses the
+        * normal layout
+        */
+       if (layout != 76 && layout != 36)
+               return of_address_to_resource(np, index, res);
+
+       parent = of_get_parent(np);
+       pindex = (index == aoa_resource_i2smmio) ? 0 : 1;
+       rc = of_address_to_resource(parent, pindex, res);
+       if (rc)
+               goto bail;
+       reg = of_get_property(np, "reg", NULL);
+       if (reg == NULL) {
+               rc = -ENXIO;
+               goto bail;
+       }
+       res->start += reg[index * 2];
+       res->end = res->start + reg[index * 2 + 1] - 1;
+ bail:
+       of_node_put(parent);
+       return rc;
+}
+
+/* FIXME: look at device node refcounting */
+static int i2sbus_add_dev(struct macio_dev *macio,
+                         struct i2sbus_control *control,
+                         struct device_node *np)
+{
+       struct i2sbus_dev *dev;
+       struct device_node *child = NULL, *sound = NULL;
+       struct resource *r;
+       int i, layout = 0, rlen, ok = force;
+       static const char *rnames[] = { "i2sbus: %s (control)",
+                                       "i2sbus: %s (tx)",
+                                       "i2sbus: %s (rx)" };
+       static irq_handler_t ints[] = {
+               i2sbus_bus_intr,
+               i2sbus_tx_intr,
+               i2sbus_rx_intr
+       };
+
+       if (strlen(np->name) != 5)
+               return 0;
+       if (strncmp(np->name, "i2s-", 4))
+               return 0;
+
+       dev = kzalloc(sizeof(struct i2sbus_dev), GFP_KERNEL);
+       if (!dev)
+               return 0;
+
+       i = 0;
+       while ((child = of_get_next_child(np, child))) {
+               if (strcmp(child->name, "sound") == 0) {
+                       i++;
+                       sound = child;
+               }
+       }
+       if (i == 1) {
+               const u32 *layout_id =
+                       of_get_property(sound, "layout-id", NULL);
+               if (layout_id) {
+                       layout = *layout_id;
+                       snprintf(dev->sound.modalias, 32,
+                                "sound-layout-%d", layout);
+                       ok = 1;
+               }
+       }
+       /* for the time being, until we can handle non-layout-id
+        * things in some fabric, refuse to attach if there is no
+        * layout-id property or we haven't been forced to attach.
+        * When there are two i2s busses and only one has a layout-id,
+        * then this depends on the order, but that isn't important
+        * either as the second one in that case is just a modem. */
+       if (!ok) {
+               kfree(dev);
+               return -ENODEV;
+       }
+
+       mutex_init(&dev->lock);
+       spin_lock_init(&dev->low_lock);
+       dev->sound.ofdev.node = np;
+       dev->sound.ofdev.dma_mask = macio->ofdev.dma_mask;
+       dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.dma_mask;
+       dev->sound.ofdev.dev.parent = &macio->ofdev.dev;
+       dev->sound.ofdev.dev.release = i2sbus_release_dev;
+       dev->sound.attach_codec = i2sbus_attach_codec;
+       dev->sound.detach_codec = i2sbus_detach_codec;
+       dev->sound.pcmid = -1;
+       dev->macio = macio;
+       dev->control = control;
+       dev->bus_number = np->name[4] - 'a';
+       INIT_LIST_HEAD(&dev->sound.codec_list);
+
+       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
+               dev->interrupts[i] = -1;
+               snprintf(dev->rnames[i], sizeof(dev->rnames[i]),
+                        rnames[i], np->name);
+       }
+       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
+               int irq = irq_of_parse_and_map(np, i);
+               if (request_irq(irq, ints[i], 0, dev->rnames[i], dev))
+                       goto err;
+               dev->interrupts[i] = irq;
+       }
+
+
+       /* Resource handling is problematic as some device-trees contain
+        * useless crap (ugh ugh ugh). We work around that here by calling
+        * specific functions for calculating the appropriate resources.
+        *
+        * This will all be moved to macio_asic.c at one point
+        */
+       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
+               if (i2sbus_get_and_fixup_rsrc(np,i,layout,&dev->resources[i]))
+                       goto err;
+               /* If only we could use our resource dev->resources[i]...
+                * but request_resource doesn't know about parents and
+                * contained resources...
+                */
+               dev->allocated_resource[i] =
+                       request_mem_region(dev->resources[i].start,
+                                          dev->resources[i].end -
+                                          dev->resources[i].start + 1,
+                                          dev->rnames[i]);
+               if (!dev->allocated_resource[i]) {
+                       printk(KERN_ERR "i2sbus: failed to claim resource %d!\n", i);
+                       goto err;
+               }
+       }
+
+       r = &dev->resources[aoa_resource_i2smmio];
+       rlen = r->end - r->start + 1;
+       if (rlen < sizeof(struct i2s_interface_regs))
+               goto err;
+       dev->intfregs = ioremap(r->start, rlen);
+
+       r = &dev->resources[aoa_resource_txdbdma];
+       rlen = r->end - r->start + 1;
+       if (rlen < sizeof(struct dbdma_regs))
+               goto err;
+       dev->out.dbdma = ioremap(r->start, rlen);
+
+       r = &dev->resources[aoa_resource_rxdbdma];
+       rlen = r->end - r->start + 1;
+       if (rlen < sizeof(struct dbdma_regs))
+               goto err;
+       dev->in.dbdma = ioremap(r->start, rlen);
+
+       if (!dev->intfregs || !dev->out.dbdma || !dev->in.dbdma)
+               goto err;
+
+       if (alloc_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring,
+                                       MAX_DBDMA_COMMANDS))
+               goto err;
+       if (alloc_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring,
+                                       MAX_DBDMA_COMMANDS))
+               goto err;
+
+       if (i2sbus_control_add_dev(dev->control, dev)) {
+               printk(KERN_ERR "i2sbus: control layer didn't like bus\n");
+               goto err;
+       }
+
+       if (soundbus_add_one(&dev->sound)) {
+               printk(KERN_DEBUG "i2sbus: device registration error!\n");
+               goto err;
+       }
+
+       /* enable this cell */
+       i2sbus_control_cell(dev->control, dev, 1);
+       i2sbus_control_enable(dev->control, dev);
+       i2sbus_control_clock(dev->control, dev, 1);
+
+       return 1;
+ err:
+       for (i=0;i<3;i++)
+               if (dev->interrupts[i] != -1)
+                       free_irq(dev->interrupts[i], dev);
+       free_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring);
+       free_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring);
+       if (dev->intfregs) iounmap(dev->intfregs);
+       if (dev->out.dbdma) iounmap(dev->out.dbdma);
+       if (dev->in.dbdma) iounmap(dev->in.dbdma);
+       for (i=0;i<3;i++)
+               if (dev->allocated_resource[i])
+                       release_and_free_resource(dev->allocated_resource[i]);
+       mutex_destroy(&dev->lock);
+       kfree(dev);
+       return 0;
+}
+
+static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match)
+{
+       struct device_node *np = NULL;
+       int got = 0, err;
+       struct i2sbus_control *control = NULL;
+
+       err = i2sbus_control_init(dev, &control);
+       if (err)
+               return err;
+       if (!control) {
+               printk(KERN_ERR "i2sbus_control_init API breakage\n");
+               return -ENODEV;
+       }
+
+       while ((np = of_get_next_child(dev->ofdev.node, np))) {
+               if (of_device_is_compatible(np, "i2sbus") ||
+                   of_device_is_compatible(np, "i2s-modem")) {
+                       got += i2sbus_add_dev(dev, control, np);
+               }
+       }
+
+       if (!got) {
+               /* found none, clean up */
+               i2sbus_control_destroy(control);
+               return -ENODEV;
+       }
+
+       dev->ofdev.dev.driver_data = control;
+
+       return 0;
+}
+
+static int i2sbus_remove(struct macio_dev* dev)
+{
+       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
+       struct i2sbus_dev *i2sdev, *tmp;
+
+       list_for_each_entry_safe(i2sdev, tmp, &control->list, item)
+               soundbus_remove_one(&i2sdev->sound);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
+{
+       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
+       struct codec_info_item *cii;
+       struct i2sbus_dev* i2sdev;
+       int err, ret = 0;
+
+       list_for_each_entry(i2sdev, &control->list, item) {
+               /* Notify Alsa */
+               if (i2sdev->sound.pcm) {
+                       /* Suspend PCM streams */
+                       snd_pcm_suspend_all(i2sdev->sound.pcm);
+               }
+
+               /* Notify codecs */
+               list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+                       err = 0;
+                       if (cii->codec->suspend)
+                               err = cii->codec->suspend(cii, state);
+                       if (err)
+                               ret = err;
+               }
+
+               /* wait until streams are stopped */
+               i2sbus_wait_for_stop_both(i2sdev);
+       }
+
+       return ret;
+}
+
+static int i2sbus_resume(struct macio_dev* dev)
+{
+       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
+       struct codec_info_item *cii;
+       struct i2sbus_dev* i2sdev;
+       int err, ret = 0;
+
+       list_for_each_entry(i2sdev, &control->list, item) {
+               /* reset i2s bus format etc. */
+               i2sbus_pcm_prepare_both(i2sdev);
+
+               /* Notify codecs so they can re-initialize */
+               list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+                       err = 0;
+                       if (cii->codec->resume)
+                               err = cii->codec->resume(cii);
+                       if (err)
+                               ret = err;
+               }
+       }
+
+       return ret;
+}
+#endif /* CONFIG_PM */
+
+static int i2sbus_shutdown(struct macio_dev* dev)
+{
+       return 0;
+}
+
+static struct macio_driver i2sbus_drv = {
+       .name = "soundbus-i2s",
+       .owner = THIS_MODULE,
+       .match_table = i2sbus_match,
+       .probe = i2sbus_probe,
+       .remove = i2sbus_remove,
+#ifdef CONFIG_PM
+       .suspend = i2sbus_suspend,
+       .resume = i2sbus_resume,
+#endif
+       .shutdown = i2sbus_shutdown,
+};
+
+static int __init soundbus_i2sbus_init(void)
+{
+       return macio_register_driver(&i2sbus_drv);
+}
+
+static void __exit soundbus_i2sbus_exit(void)
+{
+       macio_unregister_driver(&i2sbus_drv);
+}
+
+module_init(soundbus_i2sbus_init);
+module_exit(soundbus_i2sbus_exit);
diff --git a/sound/aoa/soundbus/i2sbus/i2sbus-control.c b/sound/aoa/soundbus/i2sbus/i2sbus-control.c
deleted file mode 100644 (file)
index 87beb4a..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * i2sbus driver -- bus control routines
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-
-#include <linux/kernel.h>
-#include <linux/delay.h>
-
-#include <asm/io.h>
-#include <asm/prom.h>
-#include <asm/macio.h>
-#include <asm/pmac_feature.h>
-#include <asm/pmac_pfunc.h>
-#include <asm/keylargo.h>
-
-#include "i2sbus.h"
-
-int i2sbus_control_init(struct macio_dev* dev, struct i2sbus_control **c)
-{
-       *c = kzalloc(sizeof(struct i2sbus_control), GFP_KERNEL);
-       if (!*c)
-               return -ENOMEM;
-
-       INIT_LIST_HEAD(&(*c)->list);
-
-       (*c)->macio = dev->bus->chip;
-       return 0;
-}
-
-void i2sbus_control_destroy(struct i2sbus_control *c)
-{
-       kfree(c);
-}
-
-/* this is serialised externally */
-int i2sbus_control_add_dev(struct i2sbus_control *c,
-                          struct i2sbus_dev *i2sdev)
-{
-       struct device_node *np;
-
-       np = i2sdev->sound.ofdev.node;
-       i2sdev->enable = pmf_find_function(np, "enable");
-       i2sdev->cell_enable = pmf_find_function(np, "cell-enable");
-       i2sdev->clock_enable = pmf_find_function(np, "clock-enable");
-       i2sdev->cell_disable = pmf_find_function(np, "cell-disable");
-       i2sdev->clock_disable = pmf_find_function(np, "clock-disable");
-
-       /* if the bus number is not 0 or 1 we absolutely need to use
-        * the platform functions -- there's nothing in Darwin that
-        * would allow seeing a system behind what the FCRs are then,
-        * and I don't want to go parsing a bunch of platform functions
-        * by hand to try finding a system... */
-       if (i2sdev->bus_number != 0 && i2sdev->bus_number != 1 &&
-           (!i2sdev->enable ||
-            !i2sdev->cell_enable || !i2sdev->clock_enable ||
-            !i2sdev->cell_disable || !i2sdev->clock_disable)) {
-               pmf_put_function(i2sdev->enable);
-               pmf_put_function(i2sdev->cell_enable);
-               pmf_put_function(i2sdev->clock_enable);
-               pmf_put_function(i2sdev->cell_disable);
-               pmf_put_function(i2sdev->clock_disable);
-               return -ENODEV;
-       }
-
-       list_add(&i2sdev->item, &c->list);
-
-       return 0;
-}
-
-void i2sbus_control_remove_dev(struct i2sbus_control *c,
-                              struct i2sbus_dev *i2sdev)
-{
-       /* this is serialised externally */
-       list_del(&i2sdev->item);
-       if (list_empty(&c->list))
-               i2sbus_control_destroy(c);
-}
-
-int i2sbus_control_enable(struct i2sbus_control *c,
-                         struct i2sbus_dev *i2sdev)
-{
-       struct pmf_args args = { .count = 0 };
-       struct macio_chip *macio = c->macio;
-
-       if (i2sdev->enable)
-               return pmf_call_one(i2sdev->enable, &args);
-
-       if (macio == NULL || macio->base == NULL)
-               return -ENODEV;
-
-       switch (i2sdev->bus_number) {
-       case 0:
-               /* these need to be locked or done through
-                * newly created feature calls! */
-               MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_ENABLE);
-               break;
-       case 1:
-               MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_ENABLE);
-               break;
-       default:
-               return -ENODEV;
-       }
-       return 0;
-}
-
-int i2sbus_control_cell(struct i2sbus_control *c,
-                       struct i2sbus_dev *i2sdev,
-                       int enable)
-{
-       struct pmf_args args = { .count = 0 };
-       struct macio_chip *macio = c->macio;
-
-       switch (enable) {
-       case 0:
-               if (i2sdev->cell_disable)
-                       return pmf_call_one(i2sdev->cell_disable, &args);
-               break;
-       case 1:
-               if (i2sdev->cell_enable)
-                       return pmf_call_one(i2sdev->cell_enable, &args);
-               break;
-       default:
-               printk(KERN_ERR "i2sbus: INVALID CELL ENABLE VALUE\n");
-               return -ENODEV;
-       }
-
-       if (macio == NULL || macio->base == NULL)
-               return -ENODEV;
-
-       switch (i2sdev->bus_number) {
-       case 0:
-               if (enable)
-                       MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE);
-               else
-                       MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE);
-               break;
-       case 1:
-               if (enable)
-                       MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE);
-               else
-                       MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE);
-               break;
-       default:
-               return -ENODEV;
-       }
-       return 0;
-}
-
-int i2sbus_control_clock(struct i2sbus_control *c,
-                        struct i2sbus_dev *i2sdev,
-                        int enable)
-{
-       struct pmf_args args = { .count = 0 };
-       struct macio_chip *macio = c->macio;
-
-       switch (enable) {
-       case 0:
-               if (i2sdev->clock_disable)
-                       return pmf_call_one(i2sdev->clock_disable, &args);
-               break;
-       case 1:
-               if (i2sdev->clock_enable)
-                       return pmf_call_one(i2sdev->clock_enable, &args);
-               break;
-       default:
-               printk(KERN_ERR "i2sbus: INVALID CLOCK ENABLE VALUE\n");
-               return -ENODEV;
-       }
-
-       if (macio == NULL || macio->base == NULL)
-               return -ENODEV;
-
-       switch (i2sdev->bus_number) {
-       case 0:
-               if (enable)
-                       MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
-               else
-                       MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
-               break;
-       case 1:
-               if (enable)
-                       MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT);
-               else
-                       MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT);
-               break;
-       default:
-               return -ENODEV;
-       }
-       return 0;
-}
diff --git a/sound/aoa/soundbus/i2sbus/i2sbus-core.c b/sound/aoa/soundbus/i2sbus/i2sbus-core.c
deleted file mode 100644 (file)
index b4590df..0000000
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- * i2sbus driver
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-
-#include <linux/module.h>
-#include <linux/pci.h>
-#include <linux/interrupt.h>
-#include <linux/dma-mapping.h>
-
-#include <sound/core.h>
-
-#include <asm/macio.h>
-#include <asm/dbdma.h>
-
-#include "../soundbus.h"
-#include "i2sbus.h"
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
-MODULE_DESCRIPTION("Apple Soundbus: I2S support");
-
-static int force;
-module_param(force, int, 0444);
-MODULE_PARM_DESC(force, "Force loading i2sbus even when"
-                       " no layout-id property is present");
-
-static struct of_device_id i2sbus_match[] = {
-       { .name = "i2s" },
-       { }
-};
-
-MODULE_DEVICE_TABLE(of, i2sbus_match);
-
-static int alloc_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev,
-                                      struct dbdma_command_mem *r,
-                                      int numcmds)
-{
-       /* one more for rounding, one for branch back, one for stop command */
-       r->size = (numcmds + 3) * sizeof(struct dbdma_cmd);
-       /* We use the PCI APIs for now until the generic one gets fixed
-        * enough or until we get some macio-specific versions
-        */
-       r->space = dma_alloc_coherent(
-                       &macio_get_pci_dev(i2sdev->macio)->dev,
-                       r->size,
-                       &r->bus_addr,
-                       GFP_KERNEL);
-
-       if (!r->space) return -ENOMEM;
-
-       memset(r->space, 0, r->size);
-       r->cmds = (void*)DBDMA_ALIGN(r->space);
-       r->bus_cmd_start = r->bus_addr +
-                          (dma_addr_t)((char*)r->cmds - (char*)r->space);
-
-       return 0;
-}
-
-static void free_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev,
-                                      struct dbdma_command_mem *r)
-{
-       if (!r->space) return;
-       
-       dma_free_coherent(&macio_get_pci_dev(i2sdev->macio)->dev,
-                           r->size, r->space, r->bus_addr);
-}
-
-static void i2sbus_release_dev(struct device *dev)
-{
-       struct i2sbus_dev *i2sdev;
-       int i;
-
-       i2sdev = container_of(dev, struct i2sbus_dev, sound.ofdev.dev);
-
-       if (i2sdev->intfregs) iounmap(i2sdev->intfregs);
-       if (i2sdev->out.dbdma) iounmap(i2sdev->out.dbdma);
-       if (i2sdev->in.dbdma) iounmap(i2sdev->in.dbdma);
-       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
-               if (i2sdev->allocated_resource[i])
-                       release_and_free_resource(i2sdev->allocated_resource[i]);
-       free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring);
-       free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring);
-       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
-               free_irq(i2sdev->interrupts[i], i2sdev);
-       i2sbus_control_remove_dev(i2sdev->control, i2sdev);
-       mutex_destroy(&i2sdev->lock);
-       kfree(i2sdev);
-}
-
-static irqreturn_t i2sbus_bus_intr(int irq, void *devid)
-{
-       struct i2sbus_dev *dev = devid;
-       u32 intreg;
-
-       spin_lock(&dev->low_lock);
-       intreg = in_le32(&dev->intfregs->intr_ctl);
-
-       /* acknowledge interrupt reasons */
-       out_le32(&dev->intfregs->intr_ctl, intreg);
-
-       spin_unlock(&dev->low_lock);
-
-       return IRQ_HANDLED;
-}
-
-
-/*
- * XXX FIXME: We test the layout_id's here to get the proper way of
- * mapping in various registers, thanks to bugs in Apple device-trees.
- * We could instead key off the machine model and the name of the i2s
- * node (i2s-a). This we'll do when we move it all to macio_asic.c
- * and have that export items for each sub-node too.
- */
-static int i2sbus_get_and_fixup_rsrc(struct device_node *np, int index,
-                                    int layout, struct resource *res)
-{
-       struct device_node *parent;
-       int pindex, rc = -ENXIO;
-       const u32 *reg;
-
-       /* Machines with layout 76 and 36 (K2 based) have a weird device
-        * tree what we need to special case.
-        * Normal machines just fetch the resource from the i2s-X node.
-        * Darwin further divides normal machines into old and new layouts
-        * with a subtely different code path but that doesn't seem necessary
-        * in practice, they just bloated it. In addition, even on our K2
-        * case the i2s-modem node, if we ever want to handle it, uses the
-        * normal layout
-        */
-       if (layout != 76 && layout != 36)
-               return of_address_to_resource(np, index, res);
-
-       parent = of_get_parent(np);
-       pindex = (index == aoa_resource_i2smmio) ? 0 : 1;
-       rc = of_address_to_resource(parent, pindex, res);
-       if (rc)
-               goto bail;
-       reg = of_get_property(np, "reg", NULL);
-       if (reg == NULL) {
-               rc = -ENXIO;
-               goto bail;
-       }
-       res->start += reg[index * 2];
-       res->end = res->start + reg[index * 2 + 1] - 1;
- bail:
-       of_node_put(parent);
-       return rc;
-}
-
-/* FIXME: look at device node refcounting */
-static int i2sbus_add_dev(struct macio_dev *macio,
-                         struct i2sbus_control *control,
-                         struct device_node *np)
-{
-       struct i2sbus_dev *dev;
-       struct device_node *child = NULL, *sound = NULL;
-       struct resource *r;
-       int i, layout = 0, rlen, ok = force;
-       static const char *rnames[] = { "i2sbus: %s (control)",
-                                       "i2sbus: %s (tx)",
-                                       "i2sbus: %s (rx)" };
-       static irq_handler_t ints[] = {
-               i2sbus_bus_intr,
-               i2sbus_tx_intr,
-               i2sbus_rx_intr
-       };
-
-       if (strlen(np->name) != 5)
-               return 0;
-       if (strncmp(np->name, "i2s-", 4))
-               return 0;
-
-       dev = kzalloc(sizeof(struct i2sbus_dev), GFP_KERNEL);
-       if (!dev)
-               return 0;
-
-       i = 0;
-       while ((child = of_get_next_child(np, child))) {
-               if (strcmp(child->name, "sound") == 0) {
-                       i++;
-                       sound = child;
-               }
-       }
-       if (i == 1) {
-               const u32 *layout_id =
-                       of_get_property(sound, "layout-id", NULL);
-               if (layout_id) {
-                       layout = *layout_id;
-                       snprintf(dev->sound.modalias, 32,
-                                "sound-layout-%d", layout);
-                       ok = 1;
-               }
-       }
-       /* for the time being, until we can handle non-layout-id
-        * things in some fabric, refuse to attach if there is no
-        * layout-id property or we haven't been forced to attach.
-        * When there are two i2s busses and only one has a layout-id,
-        * then this depends on the order, but that isn't important
-        * either as the second one in that case is just a modem. */
-       if (!ok) {
-               kfree(dev);
-               return -ENODEV;
-       }
-
-       mutex_init(&dev->lock);
-       spin_lock_init(&dev->low_lock);
-       dev->sound.ofdev.node = np;
-       dev->sound.ofdev.dma_mask = macio->ofdev.dma_mask;
-       dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.dma_mask;
-       dev->sound.ofdev.dev.parent = &macio->ofdev.dev;
-       dev->sound.ofdev.dev.release = i2sbus_release_dev;
-       dev->sound.attach_codec = i2sbus_attach_codec;
-       dev->sound.detach_codec = i2sbus_detach_codec;
-       dev->sound.pcmid = -1;
-       dev->macio = macio;
-       dev->control = control;
-       dev->bus_number = np->name[4] - 'a';
-       INIT_LIST_HEAD(&dev->sound.codec_list);
-
-       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
-               dev->interrupts[i] = -1;
-               snprintf(dev->rnames[i], sizeof(dev->rnames[i]),
-                        rnames[i], np->name);
-       }
-       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
-               int irq = irq_of_parse_and_map(np, i);
-               if (request_irq(irq, ints[i], 0, dev->rnames[i], dev))
-                       goto err;
-               dev->interrupts[i] = irq;
-       }
-
-
-       /* Resource handling is problematic as some device-trees contain
-        * useless crap (ugh ugh ugh). We work around that here by calling
-        * specific functions for calculating the appropriate resources.
-        *
-        * This will all be moved to macio_asic.c at one point
-        */
-       for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
-               if (i2sbus_get_and_fixup_rsrc(np,i,layout,&dev->resources[i]))
-                       goto err;
-               /* If only we could use our resource dev->resources[i]...
-                * but request_resource doesn't know about parents and
-                * contained resources...
-                */
-               dev->allocated_resource[i] = 
-                       request_mem_region(dev->resources[i].start,
-                                          dev->resources[i].end -
-                                          dev->resources[i].start + 1,
-                                          dev->rnames[i]);
-               if (!dev->allocated_resource[i]) {
-                       printk(KERN_ERR "i2sbus: failed to claim resource %d!\n", i);
-                       goto err;
-               }
-       }
-
-       r = &dev->resources[aoa_resource_i2smmio];
-       rlen = r->end - r->start + 1;
-       if (rlen < sizeof(struct i2s_interface_regs))
-               goto err;
-       dev->intfregs = ioremap(r->start, rlen);
-
-       r = &dev->resources[aoa_resource_txdbdma];
-       rlen = r->end - r->start + 1;
-       if (rlen < sizeof(struct dbdma_regs))
-               goto err;
-       dev->out.dbdma = ioremap(r->start, rlen);
-
-       r = &dev->resources[aoa_resource_rxdbdma];
-       rlen = r->end - r->start + 1;
-       if (rlen < sizeof(struct dbdma_regs))
-               goto err;
-       dev->in.dbdma = ioremap(r->start, rlen);
-
-       if (!dev->intfregs || !dev->out.dbdma || !dev->in.dbdma)
-               goto err;
-
-       if (alloc_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring,
-                                       MAX_DBDMA_COMMANDS))
-               goto err;
-       if (alloc_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring,
-                                       MAX_DBDMA_COMMANDS))
-               goto err;
-
-       if (i2sbus_control_add_dev(dev->control, dev)) {
-               printk(KERN_ERR "i2sbus: control layer didn't like bus\n");
-               goto err;
-       }
-
-       if (soundbus_add_one(&dev->sound)) {
-               printk(KERN_DEBUG "i2sbus: device registration error!\n");
-               goto err;
-       }
-
-       /* enable this cell */
-       i2sbus_control_cell(dev->control, dev, 1);
-       i2sbus_control_enable(dev->control, dev);
-       i2sbus_control_clock(dev->control, dev, 1);
-
-       return 1;
- err:
-       for (i=0;i<3;i++)
-               if (dev->interrupts[i] != -1)
-                       free_irq(dev->interrupts[i], dev);
-       free_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring);
-       free_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring);
-       if (dev->intfregs) iounmap(dev->intfregs);
-       if (dev->out.dbdma) iounmap(dev->out.dbdma);
-       if (dev->in.dbdma) iounmap(dev->in.dbdma);
-       for (i=0;i<3;i++)
-               if (dev->allocated_resource[i])
-                       release_and_free_resource(dev->allocated_resource[i]);
-       mutex_destroy(&dev->lock);
-       kfree(dev);
-       return 0;
-}
-
-static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match)
-{
-       struct device_node *np = NULL;
-       int got = 0, err;
-       struct i2sbus_control *control = NULL;
-
-       err = i2sbus_control_init(dev, &control);
-       if (err)
-               return err;
-       if (!control) {
-               printk(KERN_ERR "i2sbus_control_init API breakage\n");
-               return -ENODEV;
-       }
-
-       while ((np = of_get_next_child(dev->ofdev.node, np))) {
-               if (of_device_is_compatible(np, "i2sbus") ||
-                   of_device_is_compatible(np, "i2s-modem")) {
-                       got += i2sbus_add_dev(dev, control, np);
-               }
-       }
-
-       if (!got) {
-               /* found none, clean up */
-               i2sbus_control_destroy(control);
-               return -ENODEV;
-       }
-
-       dev->ofdev.dev.driver_data = control;
-
-       return 0;
-}
-
-static int i2sbus_remove(struct macio_dev* dev)
-{
-       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
-       struct i2sbus_dev *i2sdev, *tmp;
-
-       list_for_each_entry_safe(i2sdev, tmp, &control->list, item)
-               soundbus_remove_one(&i2sdev->sound);
-
-       return 0;
-}
-
-#ifdef CONFIG_PM
-static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
-{
-       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
-       struct codec_info_item *cii;
-       struct i2sbus_dev* i2sdev;
-       int err, ret = 0;
-
-       list_for_each_entry(i2sdev, &control->list, item) {
-               /* Notify Alsa */
-               if (i2sdev->sound.pcm) {
-                       /* Suspend PCM streams */
-                       snd_pcm_suspend_all(i2sdev->sound.pcm);
-               }
-
-               /* Notify codecs */
-               list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
-                       err = 0;
-                       if (cii->codec->suspend)
-                               err = cii->codec->suspend(cii, state);
-                       if (err)
-                               ret = err;
-               }
-
-               /* wait until streams are stopped */
-               i2sbus_wait_for_stop_both(i2sdev);
-       }
-
-       return ret;
-}
-
-static int i2sbus_resume(struct macio_dev* dev)
-{
-       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
-       struct codec_info_item *cii;
-       struct i2sbus_dev* i2sdev;
-       int err, ret = 0;
-
-       list_for_each_entry(i2sdev, &control->list, item) {
-               /* reset i2s bus format etc. */
-               i2sbus_pcm_prepare_both(i2sdev);
-
-               /* Notify codecs so they can re-initialize */
-               list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
-                       err = 0;
-                       if (cii->codec->resume)
-                               err = cii->codec->resume(cii);
-                       if (err)
-                               ret = err;
-               }
-       }
-
-       return ret;
-}
-#endif /* CONFIG_PM */
-
-static int i2sbus_shutdown(struct macio_dev* dev)
-{
-       return 0;
-}
-
-static struct macio_driver i2sbus_drv = {
-       .name = "soundbus-i2s",
-       .owner = THIS_MODULE,
-       .match_table = i2sbus_match,
-       .probe = i2sbus_probe,
-       .remove = i2sbus_remove,
-#ifdef CONFIG_PM
-       .suspend = i2sbus_suspend,
-       .resume = i2sbus_resume,
-#endif
-       .shutdown = i2sbus_shutdown,
-};
-
-static int __init soundbus_i2sbus_init(void)
-{
-       return macio_register_driver(&i2sbus_drv);
-}
-
-static void __exit soundbus_i2sbus_exit(void)
-{
-       macio_unregister_driver(&i2sbus_drv);
-}
-
-module_init(soundbus_i2sbus_init);
-module_exit(soundbus_i2sbus_exit);
diff --git a/sound/aoa/soundbus/i2sbus/i2sbus-interface.h b/sound/aoa/soundbus/i2sbus/i2sbus-interface.h
deleted file mode 100644 (file)
index c6b5f54..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * i2sbus driver -- interface register definitions
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-#ifndef __I2SBUS_INTERFACE_H
-#define __I2SBUS_INTERFACE_H
-
-/* i2s bus control registers, at least what we know about them */
-
-#define __PAD(m,n) u8 __pad##m[n]
-#define _PAD(line, n) __PAD(line, n)
-#define PAD(n) _PAD(__LINE__, (n))
-struct i2s_interface_regs {
-       __le32 intr_ctl;        /* 0x00 */
-       PAD(12);
-       __le32 serial_format;   /* 0x10 */
-       PAD(12);
-       __le32 codec_msg_out;   /* 0x20 */
-       PAD(12);
-       __le32 codec_msg_in;    /* 0x30 */
-       PAD(12);
-       __le32 frame_count;     /* 0x40 */
-       PAD(12);
-       __le32 frame_match;     /* 0x50 */
-       PAD(12);
-       __le32 data_word_sizes; /* 0x60 */
-       PAD(12);
-       __le32 peak_level_sel;  /* 0x70 */
-       PAD(12);
-       __le32 peak_level_in0;  /* 0x80 */
-       PAD(12);
-       __le32 peak_level_in1;  /* 0x90 */
-       PAD(12);
-       /* total size: 0x100 bytes */
-}  __attribute__((__packed__));
-
-/* interrupt register is just a bitfield with
- * interrupt enable and pending bits */
-#define I2S_REG_INTR_CTL               0x00
-#      define I2S_INT_FRAME_COUNT              (1<<31)
-#      define I2S_PENDING_FRAME_COUNT          (1<<30)
-#      define I2S_INT_MESSAGE_FLAG             (1<<29)
-#      define I2S_PENDING_MESSAGE_FLAG         (1<<28)
-#      define I2S_INT_NEW_PEAK                 (1<<27)
-#      define I2S_PENDING_NEW_PEAK             (1<<26)
-#      define I2S_INT_CLOCKS_STOPPED           (1<<25)
-#      define I2S_PENDING_CLOCKS_STOPPED       (1<<24)
-#      define I2S_INT_EXTERNAL_SYNC_ERROR      (1<<23)
-#      define I2S_PENDING_EXTERNAL_SYNC_ERROR  (1<<22)
-#      define I2S_INT_EXTERNAL_SYNC_OK         (1<<21)
-#      define I2S_PENDING_EXTERNAL_SYNC_OK     (1<<20)
-#      define I2S_INT_NEW_SAMPLE_RATE          (1<<19)
-#      define I2S_PENDING_NEW_SAMPLE_RATE      (1<<18)
-#      define I2S_INT_STATUS_FLAG              (1<<17)
-#      define I2S_PENDING_STATUS_FLAG          (1<<16)
-
-/* serial format register is more interesting :)
- * It contains:
- *  - clock source
- *  - MClk divisor
- *  - SClk divisor
- *  - SClk master flag
- *  - serial format (sony, i2s 64x, i2s 32x, dav, silabs)
- *  - external sample frequency interrupt (don't understand)
- *  - external sample frequency
- */
-#define I2S_REG_SERIAL_FORMAT          0x10
-/* clock source. You get either 18.432, 45.1584 or 49.1520 MHz */
-#      define I2S_SF_CLOCK_SOURCE_SHIFT        30
-#      define I2S_SF_CLOCK_SOURCE_MASK         (3<<I2S_SF_CLOCK_SOURCE_SHIFT)
-#      define I2S_SF_CLOCK_SOURCE_18MHz        (0<<I2S_SF_CLOCK_SOURCE_SHIFT)
-#      define I2S_SF_CLOCK_SOURCE_45MHz        (1<<I2S_SF_CLOCK_SOURCE_SHIFT)
-#      define I2S_SF_CLOCK_SOURCE_49MHz        (2<<I2S_SF_CLOCK_SOURCE_SHIFT)
-/* also, let's define the exact clock speeds here, in Hz */
-#define I2S_CLOCK_SPEED_18MHz  18432000
-#define I2S_CLOCK_SPEED_45MHz  45158400
-#define I2S_CLOCK_SPEED_49MHz  49152000
-/* MClk is the clock that drives the codec, usually called its 'system clock'.
- * It is derived by taking only every 'divisor' tick of the clock.
- */
-#      define I2S_SF_MCLKDIV_SHIFT             24
-#      define I2S_SF_MCLKDIV_MASK              (0x1F<<I2S_SF_MCLKDIV_SHIFT)
-#      define I2S_SF_MCLKDIV_1                 (0x14<<I2S_SF_MCLKDIV_SHIFT)
-#      define I2S_SF_MCLKDIV_3                 (0x13<<I2S_SF_MCLKDIV_SHIFT)
-#      define I2S_SF_MCLKDIV_5                 (0x12<<I2S_SF_MCLKDIV_SHIFT)
-#      define I2S_SF_MCLKDIV_14                (0x0E<<I2S_SF_MCLKDIV_SHIFT)
-#      define I2S_SF_MCLKDIV_OTHER(div)        (((div/2-1)<<I2S_SF_MCLKDIV_SHIFT)&I2S_SF_MCLKDIV_MASK)
-static inline int i2s_sf_mclkdiv(int div, int *out)
-{
-       int d;
-
-       switch(div) {
-       case 1: *out |= I2S_SF_MCLKDIV_1; return 0;
-       case 3: *out |= I2S_SF_MCLKDIV_3; return 0;
-       case 5: *out |= I2S_SF_MCLKDIV_5; return 0;
-       case 14: *out |= I2S_SF_MCLKDIV_14; return 0;
-       default:
-               if (div%2) return -1;
-               d = div/2-1;
-               if (d == 0x14 || d == 0x13 || d == 0x12 || d == 0x0E)
-                       return -1;
-               *out |= I2S_SF_MCLKDIV_OTHER(div);
-               return 0;
-       }
-}
-/* SClk is the clock that drives the i2s wire bus. Note that it is
- * derived from the MClk above by taking only every 'divisor' tick
- * of MClk.
- */
-#      define I2S_SF_SCLKDIV_SHIFT             20
-#      define I2S_SF_SCLKDIV_MASK              (0xF<<I2S_SF_SCLKDIV_SHIFT)
-#      define I2S_SF_SCLKDIV_1                 (8<<I2S_SF_SCLKDIV_SHIFT)
-#      define I2S_SF_SCLKDIV_3                 (9<<I2S_SF_SCLKDIV_SHIFT)
-#      define I2S_SF_SCLKDIV_OTHER(div)        (((div/2-1)<<I2S_SF_SCLKDIV_SHIFT)&I2S_SF_SCLKDIV_MASK)
-static inline int i2s_sf_sclkdiv(int div, int *out)
-{
-       int d;
-
-       switch(div) {
-       case 1: *out |= I2S_SF_SCLKDIV_1; return 0;
-       case 3: *out |= I2S_SF_SCLKDIV_3; return 0;
-       default:
-               if (div%2) return -1;
-               d = div/2-1;
-               if (d == 8 || d == 9) return -1;
-               *out |= I2S_SF_SCLKDIV_OTHER(div);
-               return 0;
-       }
-}
-#      define I2S_SF_SCLK_MASTER               (1<<19)
-/* serial format is the way the data is put to the i2s wire bus */
-#      define I2S_SF_SERIAL_FORMAT_SHIFT       16
-#      define I2S_SF_SERIAL_FORMAT_MASK        (7<<I2S_SF_SERIAL_FORMAT_SHIFT)
-#      define I2S_SF_SERIAL_FORMAT_SONY        (0<<I2S_SF_SERIAL_FORMAT_SHIFT)
-#      define I2S_SF_SERIAL_FORMAT_I2S_64X     (1<<I2S_SF_SERIAL_FORMAT_SHIFT)
-#      define I2S_SF_SERIAL_FORMAT_I2S_32X     (2<<I2S_SF_SERIAL_FORMAT_SHIFT)
-#      define I2S_SF_SERIAL_FORMAT_I2S_DAV     (4<<I2S_SF_SERIAL_FORMAT_SHIFT)
-#      define I2S_SF_SERIAL_FORMAT_I2S_SILABS  (5<<I2S_SF_SERIAL_FORMAT_SHIFT)
-/* unknown */
-#      define I2S_SF_EXT_SAMPLE_FREQ_INT_SHIFT 12
-#      define I2S_SF_EXT_SAMPLE_FREQ_INT_MASK  (0xF<<I2S_SF_SAMPLE_FREQ_INT_SHIFT)
-/* probably gives external frequency? */
-#      define I2S_SF_EXT_SAMPLE_FREQ_MASK      0xFFF
-
-/* used to send codec messages, but how isn't clear */
-#define I2S_REG_CODEC_MSG_OUT          0x20
-
-/* used to receive codec messages, but how isn't clear */
-#define I2S_REG_CODEC_MSG_IN           0x30
-
-/* frame count reg isn't clear to me yet, but probably useful */
-#define I2S_REG_FRAME_COUNT            0x40
-
-/* program to some value, and get interrupt if frame count reaches it */
-#define I2S_REG_FRAME_MATCH            0x50
-
-/* this register describes how the bus transfers data */
-#define I2S_REG_DATA_WORD_SIZES                0x60
-/* number of interleaved input channels */
-#      define I2S_DWS_NUM_CHANNELS_IN_SHIFT    24
-#      define I2S_DWS_NUM_CHANNELS_IN_MASK     (0x1F<<I2S_DWS_NUM_CHANNELS_IN_SHIFT)
-/* word size of input data */
-#      define I2S_DWS_DATA_IN_SIZE_SHIFT       16
-#      define I2S_DWS_DATA_IN_16BIT            (0<<I2S_DWS_DATA_IN_SIZE_SHIFT)
-#      define I2S_DWS_DATA_IN_24BIT            (3<<I2S_DWS_DATA_IN_SIZE_SHIFT)
-/* number of interleaved output channels */
-#      define I2S_DWS_NUM_CHANNELS_OUT_SHIFT   8
-#      define I2S_DWS_NUM_CHANNELS_OUT_MASK    (0x1F<<I2S_DWS_NUM_CHANNELS_OUT_SHIFT)
-/* word size of output data */
-#      define I2S_DWS_DATA_OUT_SIZE_SHIFT      0
-#      define I2S_DWS_DATA_OUT_16BIT           (0<<I2S_DWS_DATA_OUT_SIZE_SHIFT)
-#      define I2S_DWS_DATA_OUT_24BIT           (3<<I2S_DWS_DATA_OUT_SIZE_SHIFT)
-
-
-/* unknown */
-#define I2S_REG_PEAK_LEVEL_SEL         0x70
-
-/* unknown */
-#define I2S_REG_PEAK_LEVEL_IN0         0x80
-
-/* unknown */
-#define I2S_REG_PEAK_LEVEL_IN1         0x90
-
-#endif /* __I2SBUS_INTERFACE_H */
diff --git a/sound/aoa/soundbus/i2sbus/i2sbus-pcm.c b/sound/aoa/soundbus/i2sbus/i2sbus-pcm.c
deleted file mode 100644 (file)
index 59bacd3..0000000
+++ /dev/null
@@ -1,1062 +0,0 @@
-/*
- * i2sbus driver -- pcm routines
- *
- * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
- *
- * GPL v2, can be found in COPYING.
- */
-
-#include <asm/io.h>
-#include <linux/delay.h>
-#include <sound/core.h>
-#include <asm/macio.h>
-#include <linux/pci.h>
-#include "../soundbus.h"
-#include "i2sbus.h"
-
-static inline void get_pcm_info(struct i2sbus_dev *i2sdev, int in,
-                               struct pcm_info **pi, struct pcm_info **other)
-{
-       if (in) {
-               if (pi)
-                       *pi = &i2sdev->in;
-               if (other)
-                       *other = &i2sdev->out;
-       } else {
-               if (pi)
-                       *pi = &i2sdev->out;
-               if (other)
-                       *other = &i2sdev->in;
-       }
-}
-
-static int clock_and_divisors(int mclk, int sclk, int rate, int *out)
-{
-       /* sclk must be derived from mclk! */
-       if (mclk % sclk)
-               return -1;
-       /* derive sclk register value */
-       if (i2s_sf_sclkdiv(mclk / sclk, out))
-               return -1;
-
-       if (I2S_CLOCK_SPEED_18MHz % (rate * mclk) == 0) {
-               if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_18MHz / (rate * mclk), out)) {
-                       *out |= I2S_SF_CLOCK_SOURCE_18MHz;
-                       return 0;
-               }
-       }
-       if (I2S_CLOCK_SPEED_45MHz % (rate * mclk) == 0) {
-               if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_45MHz / (rate * mclk), out)) {
-                       *out |= I2S_SF_CLOCK_SOURCE_45MHz;
-                       return 0;
-               }
-       }
-       if (I2S_CLOCK_SPEED_49MHz % (rate * mclk) == 0) {
-               if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_49MHz / (rate * mclk), out)) {
-                       *out |= I2S_SF_CLOCK_SOURCE_49MHz;
-                       return 0;
-               }
-       }
-       return -1;
-}
-
-#define CHECK_RATE(rate)                                               \
-       do { if (rates & SNDRV_PCM_RATE_ ##rate) {                      \
-               int dummy;                                              \
-               if (clock_and_divisors(sysclock_factor,                 \
-                                      bus_factor, rate, &dummy))       \
-                       rates &= ~SNDRV_PCM_RATE_ ##rate;               \
-       } } while (0)
-
-static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in)
-{
-       struct pcm_info *pi, *other;
-       struct soundbus_dev *sdev;
-       int masks_inited = 0, err;
-       struct codec_info_item *cii, *rev;
-       struct snd_pcm_hardware *hw;
-       u64 formats = 0;
-       unsigned int rates = 0;
-       struct transfer_info v;
-       int result = 0;
-       int bus_factor = 0, sysclock_factor = 0;
-       int found_this;
-
-       mutex_lock(&i2sdev->lock);
-
-       get_pcm_info(i2sdev, in, &pi, &other);
-
-       hw = &pi->substream->runtime->hw;
-       sdev = &i2sdev->sound;
-
-       if (pi->active) {
-               /* alsa messed up */
-               result = -EBUSY;
-               goto out_unlock;
-       }
-
-       /* we now need to assign the hw */
-       list_for_each_entry(cii, &sdev->codec_list, list) {
-               struct transfer_info *ti = cii->codec->transfers;
-               bus_factor = cii->codec->bus_factor;
-               sysclock_factor = cii->codec->sysclock_factor;
-               while (ti->formats && ti->rates) {
-                       v = *ti;
-                       if (ti->transfer_in == in
-                           && cii->codec->usable(cii, ti, &v)) {
-                               if (masks_inited) {
-                                       formats &= v.formats;
-                                       rates &= v.rates;
-                               } else {
-                                       formats = v.formats;
-                                       rates = v.rates;
-                                       masks_inited = 1;
-                               }
-                       }
-                       ti++;
-               }
-       }
-       if (!masks_inited || !bus_factor || !sysclock_factor) {
-               result = -ENODEV;
-               goto out_unlock;
-       }
-       /* bus dependent stuff */
-       hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
-                  SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME |
-                  SNDRV_PCM_INFO_JOINT_DUPLEX;
-
-       CHECK_RATE(5512);
-       CHECK_RATE(8000);
-       CHECK_RATE(11025);
-       CHECK_RATE(16000);
-       CHECK_RATE(22050);
-       CHECK_RATE(32000);
-       CHECK_RATE(44100);
-       CHECK_RATE(48000);
-       CHECK_RATE(64000);
-       CHECK_RATE(88200);
-       CHECK_RATE(96000);
-       CHECK_RATE(176400);
-       CHECK_RATE(192000);
-       hw->rates = rates;
-
-       /* well. the codec might want 24 bits only, and we'll
-        * ever only transfer 24 bits, but they are top-aligned!
-        * So for alsa, we claim that we're doing full 32 bit
-        * while in reality we'll ignore the lower 8 bits of
-        * that when doing playback (they're transferred as 0
-        * as far as I know, no codecs we have are 32-bit capable
-        * so I can't really test) and when doing recording we'll
-        * always have those lower 8 bits recorded as 0 */
-       if (formats & SNDRV_PCM_FMTBIT_S24_BE)
-               formats |= SNDRV_PCM_FMTBIT_S32_BE;
-       if (formats & SNDRV_PCM_FMTBIT_U24_BE)
-               formats |= SNDRV_PCM_FMTBIT_U32_BE;
-       /* now mask off what we can support. I suppose we could
-        * also support S24_3LE and some similar formats, but I
-        * doubt there's a codec that would be able to use that,
-        * so we don't support it here. */
-       hw->formats = formats & (SNDRV_PCM_FMTBIT_S16_BE |
-                                SNDRV_PCM_FMTBIT_U16_BE |
-                                SNDRV_PCM_FMTBIT_S32_BE |
-                                SNDRV_PCM_FMTBIT_U32_BE);
-
-       /* we need to set the highest and lowest rate possible.
-        * These are the highest and lowest rates alsa can
-        * support properly in its bitfield.
-        * Below, we'll use that to restrict to the rate
-        * currently in use (if any). */
-       hw->rate_min = 5512;
-       hw->rate_max = 192000;
-       /* if the other stream is active, then we can only
-        * support what it is currently using.
-        * FIXME: I lied. This comment is wrong. We can support
-        * anything that works with the same serial format, ie.
-        * when recording 24 bit sound we can well play 16 bit
-        * sound at the same time iff using the same transfer mode.
-        */
-       if (other->active) {
-               /* FIXME: is this guaranteed by the alsa api? */
-               hw->formats &= (1ULL << i2sdev->format);
-               /* see above, restrict rates to the one we already have */
-               hw->rate_min = i2sdev->rate;
-               hw->rate_max = i2sdev->rate;
-       }
-
-       hw->channels_min = 2;
-       hw->channels_max = 2;
-       /* these are somewhat arbitrary */
-       hw->buffer_bytes_max = 131072;
-       hw->period_bytes_min = 256;
-       hw->period_bytes_max = 16384;
-       hw->periods_min = 3;
-       hw->periods_max = MAX_DBDMA_COMMANDS;
-       err = snd_pcm_hw_constraint_integer(pi->substream->runtime,
-                                           SNDRV_PCM_HW_PARAM_PERIODS);
-       if (err < 0) {
-               result = err;
-               goto out_unlock;
-       }
-       list_for_each_entry(cii, &sdev->codec_list, list) {
-               if (cii->codec->open) {
-                       err = cii->codec->open(cii, pi->substream);
-                       if (err) {
-                               result = err;
-                               /* unwind */
-                               found_this = 0;
-                               list_for_each_entry_reverse(rev,
-                                   &sdev->codec_list, list) {
-                                       if (found_this && rev->codec->close) {
-                                               rev->codec->close(rev,
-                                                               pi->substream);
-                                       }
-                                       if (rev == cii)
-                                               found_this = 1;
-                               }
-                               goto out_unlock;
-                       }
-               }
-       }
-
- out_unlock:
-       mutex_unlock(&i2sdev->lock);
-       return result;
-}
-
-#undef CHECK_RATE
-
-static int i2sbus_pcm_close(struct i2sbus_dev *i2sdev, int in)
-{
-       struct codec_info_item *cii;
-       struct pcm_info *pi;
-       int err = 0, tmp;
-
-       mutex_lock(&i2sdev->lock);
-
-       get_pcm_info(i2sdev, in, &pi, NULL);
-
-       list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
-               if (cii->codec->close) {
-                       tmp = cii->codec->close(cii, pi->substream);
-                       if (tmp)
-                               err = tmp;
-               }
-       }
-
-       pi->substream = NULL;
-       pi->active = 0;
-       mutex_unlock(&i2sdev->lock);
-       return err;
-}
-
-static void i2sbus_wait_for_stop(struct i2sbus_dev *i2sdev,
-                                struct pcm_info *pi)
-{
-       unsigned long flags;
-       struct completion done;
-       long timeout;
-
-       spin_lock_irqsave(&i2sdev->low_lock, flags);
-       if (pi->dbdma_ring.stopping) {
-               init_completion(&done);
-               pi->stop_completion = &done;
-               spin_unlock_irqrestore(&i2sdev->low_lock, flags);
-               timeout = wait_for_completion_timeout(&done, HZ);
-               spin_lock_irqsave(&i2sdev->low_lock, flags);
-               pi->stop_completion = NULL;
-               if (timeout == 0) {
-                       /* timeout expired, stop dbdma forcefully */
-                       printk(KERN_ERR "i2sbus_wait_for_stop: timed out\n");
-                       /* make sure RUN, PAUSE and S0 bits are cleared */
-                       out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
-                       pi->dbdma_ring.stopping = 0;
-                       timeout = 10;
-                       while (in_le32(&pi->dbdma->status) & ACTIVE) {
-                               if (--timeout <= 0)
-                                       break;
-                               udelay(1);
-                       }
-               }
-       }
-       spin_unlock_irqrestore(&i2sdev->low_lock, flags);
-}
-
-#ifdef CONFIG_PM
-void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev)
-{
-       struct pcm_info *pi;
-
-       get_pcm_info(i2sdev, 0, &pi, NULL);
-       i2sbus_wait_for_stop(i2sdev, pi);
-       get_pcm_info(i2sdev, 1, &pi, NULL);
-       i2sbus_wait_for_stop(i2sdev, pi);
-}
-#endif
-
-static int i2sbus_hw_params(struct snd_pcm_substream *substream,
-                           struct snd_pcm_hw_params *params)
-{
-       return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
-}
-
-static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-       struct pcm_info *pi;
-
-       get_pcm_info(i2sdev, in, &pi, NULL);
-       if (pi->dbdma_ring.stopping)
-               i2sbus_wait_for_stop(i2sdev, pi);
-       snd_pcm_lib_free_pages(substream);
-       return 0;
-}
-
-static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream)
-{
-       return i2sbus_hw_free(substream, 0);
-}
-
-static int i2sbus_record_hw_free(struct snd_pcm_substream *substream)
-{
-       return i2sbus_hw_free(substream, 1);
-}
-
-static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
-{
-       /* whee. Hard work now. The user has selected a bitrate
-        * and bit format, so now we have to program our
-        * I2S controller appropriately. */
-       struct snd_pcm_runtime *runtime;
-       struct dbdma_cmd *command;
-       int i, periodsize, nperiods;
-       dma_addr_t offset;
-       struct bus_info bi;
-       struct codec_info_item *cii;
-       int sfr = 0;            /* serial format register */
-       int dws = 0;            /* data word sizes reg */
-       int input_16bit;
-       struct pcm_info *pi, *other;
-       int cnt;
-       int result = 0;
-       unsigned int cmd, stopaddr;
-
-       mutex_lock(&i2sdev->lock);
-
-       get_pcm_info(i2sdev, in, &pi, &other);
-
-       if (pi->dbdma_ring.running) {
-               result = -EBUSY;
-               goto out_unlock;
-       }
-       if (pi->dbdma_ring.stopping)
-               i2sbus_wait_for_stop(i2sdev, pi);
-
-       if (!pi->substream || !pi->substream->runtime) {
-               result = -EINVAL;
-               goto out_unlock;
-       }
-
-       runtime = pi->substream->runtime;
-       pi->active = 1;
-       if (other->active &&
-           ((i2sdev->format != runtime->format)
-            || (i2sdev->rate != runtime->rate))) {
-               result = -EINVAL;
-               goto out_unlock;
-       }
-
-       i2sdev->format = runtime->format;
-       i2sdev->rate = runtime->rate;
-
-       periodsize = snd_pcm_lib_period_bytes(pi->substream);
-       nperiods = pi->substream->runtime->periods;
-       pi->current_period = 0;
-
-       /* generate dbdma command ring first */
-       command = pi->dbdma_ring.cmds;
-       memset(command, 0, (nperiods + 2) * sizeof(struct dbdma_cmd));
-
-       /* commands to DMA to/from the ring */
-       /*
-        * For input, we need to do a graceful stop; if we abort
-        * the DMA, we end up with leftover bytes that corrupt
-        * the next recording.  To do this we set the S0 status
-        * bit and wait for the DMA controller to stop.  Each
-        * command has a branch condition to
-        * make it branch to a stop command if S0 is set.
-        * On input we also need to wait for the S7 bit to be
-        * set before turning off the DMA controller.
-        * In fact we do the graceful stop for output as well.
-        */
-       offset = runtime->dma_addr;
-       cmd = (in? INPUT_MORE: OUTPUT_MORE) | BR_IFSET | INTR_ALWAYS;
-       stopaddr = pi->dbdma_ring.bus_cmd_start +
-               (nperiods + 1) * sizeof(struct dbdma_cmd);
-       for (i = 0; i < nperiods; i++, command++, offset += periodsize) {
-               command->command = cpu_to_le16(cmd);
-               command->cmd_dep = cpu_to_le32(stopaddr);
-               command->phy_addr = cpu_to_le32(offset);
-               command->req_count = cpu_to_le16(periodsize);
-       }
-
-       /* branch back to beginning of ring */
-       command->command = cpu_to_le16(DBDMA_NOP | BR_ALWAYS);
-       command->cmd_dep = cpu_to_le32(pi->dbdma_ring.bus_cmd_start);
-       command++;
-
-       /* set stop command */
-       command->command = cpu_to_le16(DBDMA_STOP);
-
-       /* ok, let's set the serial format and stuff */
-       switch (runtime->format) {
-       /* 16 bit formats */
-       case SNDRV_PCM_FORMAT_S16_BE:
-       case SNDRV_PCM_FORMAT_U16_BE:
-               /* FIXME: if we add different bus factors we need to
-                * do more here!! */
-               bi.bus_factor = 0;
-               list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
-                       bi.bus_factor = cii->codec->bus_factor;
-                       break;
-               }
-               if (!bi.bus_factor) {
-                       result = -ENODEV;
-                       goto out_unlock;
-               }
-               input_16bit = 1;
-               break;
-       case SNDRV_PCM_FORMAT_S32_BE:
-       case SNDRV_PCM_FORMAT_U32_BE:
-               /* force 64x bus speed, otherwise the data cannot be
-                * transferred quickly enough! */
-               bi.bus_factor = 64;
-               input_16bit = 0;
-               break;
-       default:
-               result = -EINVAL;
-               goto out_unlock;
-       }
-       /* we assume all sysclocks are the same! */
-       list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
-               bi.sysclock_factor = cii->codec->sysclock_factor;
-               break;
-       }
-
-       if (clock_and_divisors(bi.sysclock_factor,
-                              bi.bus_factor,
-                              runtime->rate,
-                              &sfr) < 0) {
-               result = -EINVAL;
-               goto out_unlock;
-       }
-       switch (bi.bus_factor) {
-       case 32:
-               sfr |= I2S_SF_SERIAL_FORMAT_I2S_32X;
-               break;
-       case 64:
-               sfr |= I2S_SF_SERIAL_FORMAT_I2S_64X;
-               break;
-       }
-       /* FIXME: THIS ASSUMES MASTER ALL THE TIME */
-       sfr |= I2S_SF_SCLK_MASTER;
-
-       list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
-               int err = 0;
-               if (cii->codec->prepare)
-                       err = cii->codec->prepare(cii, &bi, pi->substream);
-               if (err) {
-                       result = err;
-                       goto out_unlock;
-               }
-       }
-       /* codecs are fine with it, so set our clocks */
-       if (input_16bit)
-               dws =   (2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
-                       (2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
-                       I2S_DWS_DATA_IN_16BIT | I2S_DWS_DATA_OUT_16BIT;
-       else
-               dws =   (2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
-                       (2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
-                       I2S_DWS_DATA_IN_24BIT | I2S_DWS_DATA_OUT_24BIT;
-
-       /* early exit if already programmed correctly */
-       /* not locking these is fine since we touch them only in this function */
-       if (in_le32(&i2sdev->intfregs->serial_format) == sfr
-        && in_le32(&i2sdev->intfregs->data_word_sizes) == dws)
-               goto out_unlock;
-
-       /* let's notify the codecs about clocks going away.
-        * For now we only do mastering on the i2s cell... */
-       list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
-               if (cii->codec->switch_clock)
-                       cii->codec->switch_clock(cii, CLOCK_SWITCH_PREPARE_SLAVE);
-
-       i2sbus_control_enable(i2sdev->control, i2sdev);
-       i2sbus_control_cell(i2sdev->control, i2sdev, 1);
-
-       out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
-
-       i2sbus_control_clock(i2sdev->control, i2sdev, 0);
-
-       msleep(1);
-
-       /* wait for clock stopped. This can apparently take a while... */
-       cnt = 100;
-       while (cnt-- &&
-           !(in_le32(&i2sdev->intfregs->intr_ctl) & I2S_PENDING_CLOCKS_STOPPED)) {
-               msleep(5);
-       }
-       out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
-
-       /* not locking these is fine since we touch them only in this function */
-       out_le32(&i2sdev->intfregs->serial_format, sfr);
-       out_le32(&i2sdev->intfregs->data_word_sizes, dws);
-
-        i2sbus_control_enable(i2sdev->control, i2sdev);
-        i2sbus_control_cell(i2sdev->control, i2sdev, 1);
-        i2sbus_control_clock(i2sdev->control, i2sdev, 1);
-       msleep(1);
-
-       list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
-               if (cii->codec->switch_clock)
-                       cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE);
-
- out_unlock:
-       mutex_unlock(&i2sdev->lock);
-       return result;
-}
-
-#ifdef CONFIG_PM
-void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev)
-{
-       i2sbus_pcm_prepare(i2sdev, 0);
-       i2sbus_pcm_prepare(i2sdev, 1);
-}
-#endif
-
-static int i2sbus_pcm_trigger(struct i2sbus_dev *i2sdev, int in, int cmd)
-{
-       struct codec_info_item *cii;
-       struct pcm_info *pi;
-       int result = 0;
-       unsigned long flags;
-
-       spin_lock_irqsave(&i2sdev->low_lock, flags);
-
-       get_pcm_info(i2sdev, in, &pi, NULL);
-
-       switch (cmd) {
-       case SNDRV_PCM_TRIGGER_START:
-       case SNDRV_PCM_TRIGGER_RESUME:
-               if (pi->dbdma_ring.running) {
-                       result = -EALREADY;
-                       goto out_unlock;
-               }
-               list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
-                       if (cii->codec->start)
-                               cii->codec->start(cii, pi->substream);
-               pi->dbdma_ring.running = 1;
-
-               if (pi->dbdma_ring.stopping) {
-                       /* Clear the S0 bit, then see if we stopped yet */
-                       out_le32(&pi->dbdma->control, 1 << 16);
-                       if (in_le32(&pi->dbdma->status) & ACTIVE) {
-                               /* possible race here? */
-                               udelay(10);
-                               if (in_le32(&pi->dbdma->status) & ACTIVE) {
-                                       pi->dbdma_ring.stopping = 0;
-                                       goto out_unlock; /* keep running */
-                               }
-                       }
-               }
-
-               /* make sure RUN, PAUSE and S0 bits are cleared */
-               out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
-
-               /* set branch condition select register */
-               out_le32(&pi->dbdma->br_sel, (1 << 16) | 1);
-
-               /* write dma command buffer address to the dbdma chip */
-               out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start);
-
-               /* initialize the frame count and current period */
-               pi->current_period = 0;
-               pi->frame_count = in_le32(&i2sdev->intfregs->frame_count);
-
-               /* set the DMA controller running */
-               out_le32(&pi->dbdma->control, (RUN << 16) | RUN);
-
-               /* off you go! */
-               break;
-
-       case SNDRV_PCM_TRIGGER_STOP:
-       case SNDRV_PCM_TRIGGER_SUSPEND:
-               if (!pi->dbdma_ring.running) {
-                       result = -EALREADY;
-                       goto out_unlock;
-               }
-               pi->dbdma_ring.running = 0;
-
-               /* Set the S0 bit to make the DMA branch to the stop cmd */
-               out_le32(&pi->dbdma->control, (1 << 16) | 1);
-               pi->dbdma_ring.stopping = 1;
-
-               list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
-                       if (cii->codec->stop)
-                               cii->codec->stop(cii, pi->substream);
-               break;
-       default:
-               result = -EINVAL;
-               goto out_unlock;
-       }
-
- out_unlock:
-       spin_unlock_irqrestore(&i2sdev->low_lock, flags);
-       return result;
-}
-
-static snd_pcm_uframes_t i2sbus_pcm_pointer(struct i2sbus_dev *i2sdev, int in)
-{
-       struct pcm_info *pi;
-       u32 fc;
-
-       get_pcm_info(i2sdev, in, &pi, NULL);
-
-       fc = in_le32(&i2sdev->intfregs->frame_count);
-       fc = fc - pi->frame_count;
-
-       if (fc >= pi->substream->runtime->buffer_size)
-               fc %= pi->substream->runtime->buffer_size;
-       return fc;
-}
-
-static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in)
-{
-       struct pcm_info *pi;
-       u32 fc, nframes;
-       u32 status;
-       int timeout, i;
-       int dma_stopped = 0;
-       struct snd_pcm_runtime *runtime;
-
-       spin_lock(&i2sdev->low_lock);
-       get_pcm_info(i2sdev, in, &pi, NULL);
-       if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping)
-               goto out_unlock;
-
-       i = pi->current_period;
-       runtime = pi->substream->runtime;
-       while (pi->dbdma_ring.cmds[i].xfer_status) {
-               if (le16_to_cpu(pi->dbdma_ring.cmds[i].xfer_status) & BT)
-                       /*
-                        * BT is the branch taken bit.  If it took a branch
-                        * it is because we set the S0 bit to make it
-                        * branch to the stop command.
-                        */
-                       dma_stopped = 1;
-               pi->dbdma_ring.cmds[i].xfer_status = 0;
-
-               if (++i >= runtime->periods) {
-                       i = 0;
-                       pi->frame_count += runtime->buffer_size;
-               }
-               pi->current_period = i;
-
-               /*
-                * Check the frame count.  The DMA tends to get a bit
-                * ahead of the frame counter, which confuses the core.
-                */
-               fc = in_le32(&i2sdev->intfregs->frame_count);
-               nframes = i * runtime->period_size;
-               if (fc < pi->frame_count + nframes)
-                       pi->frame_count = fc - nframes;
-       }
-
-       if (dma_stopped) {
-               timeout = 1000;
-               for (;;) {
-                       status = in_le32(&pi->dbdma->status);
-                       if (!(status & ACTIVE) && (!in || (status & 0x80)))
-                               break;
-                       if (--timeout <= 0) {
-                               printk(KERN_ERR "i2sbus: timed out "
-                                      "waiting for DMA to stop!\n");
-                               break;
-                       }
-                       udelay(1);
-               }
-
-               /* Turn off DMA controller, clear S0 bit */
-               out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
-
-               pi->dbdma_ring.stopping = 0;
-               if (pi->stop_completion)
-                       complete(pi->stop_completion);
-       }
-
-       if (!pi->dbdma_ring.running)
-               goto out_unlock;
-       spin_unlock(&i2sdev->low_lock);
-       /* may call _trigger again, hence needs to be unlocked */
-       snd_pcm_period_elapsed(pi->substream);
-       return;
-
- out_unlock:
-       spin_unlock(&i2sdev->low_lock);
-}
-
-irqreturn_t i2sbus_tx_intr(int irq, void *devid)
-{
-       handle_interrupt((struct i2sbus_dev *)devid, 0);
-       return IRQ_HANDLED;
-}
-
-irqreturn_t i2sbus_rx_intr(int irq, void *devid)
-{
-       handle_interrupt((struct i2sbus_dev *)devid, 1);
-       return IRQ_HANDLED;
-}
-
-static int i2sbus_playback_open(struct snd_pcm_substream *substream)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-
-       if (!i2sdev)
-               return -EINVAL;
-       i2sdev->out.substream = substream;
-       return i2sbus_pcm_open(i2sdev, 0);
-}
-
-static int i2sbus_playback_close(struct snd_pcm_substream *substream)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-       int err;
-
-       if (!i2sdev)
-               return -EINVAL;
-       if (i2sdev->out.substream != substream)
-               return -EINVAL;
-       err = i2sbus_pcm_close(i2sdev, 0);
-       if (!err)
-               i2sdev->out.substream = NULL;
-       return err;
-}
-
-static int i2sbus_playback_prepare(struct snd_pcm_substream *substream)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-
-       if (!i2sdev)
-               return -EINVAL;
-       if (i2sdev->out.substream != substream)
-               return -EINVAL;
-       return i2sbus_pcm_prepare(i2sdev, 0);
-}
-
-static int i2sbus_playback_trigger(struct snd_pcm_substream *substream, int cmd)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-
-       if (!i2sdev)
-               return -EINVAL;
-       if (i2sdev->out.substream != substream)
-               return -EINVAL;
-       return i2sbus_pcm_trigger(i2sdev, 0, cmd);
-}
-
-static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream
-                                                *substream)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-
-       if (!i2sdev)
-               return -EINVAL;
-       if (i2sdev->out.substream != substream)
-               return 0;
-       return i2sbus_pcm_pointer(i2sdev, 0);
-}
-
-static struct snd_pcm_ops i2sbus_playback_ops = {
-       .open =         i2sbus_playback_open,
-       .close =        i2sbus_playback_close,
-       .ioctl =        snd_pcm_lib_ioctl,
-       .hw_params =    i2sbus_hw_params,
-       .hw_free =      i2sbus_playback_hw_free,
-       .prepare =      i2sbus_playback_prepare,
-       .trigger =      i2sbus_playback_trigger,
-       .pointer =      i2sbus_playback_pointer,
-};
-
-static int i2sbus_record_open(struct snd_pcm_substream *substream)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-
-       if (!i2sdev)
-               return -EINVAL;
-       i2sdev->in.substream = substream;
-       return i2sbus_pcm_open(i2sdev, 1);
-}
-
-static int i2sbus_record_close(struct snd_pcm_substream *substream)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-       int err;
-
-       if (!i2sdev)
-               return -EINVAL;
-       if (i2sdev->in.substream != substream)
-               return -EINVAL;
-       err = i2sbus_pcm_close(i2sdev, 1);
-       if (!err)
-               i2sdev->in.substream = NULL;
-       return err;
-}
-
-static int i2sbus_record_prepare(struct snd_pcm_substream *substream)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-
-       if (!i2sdev)
-               return -EINVAL;
-       if (i2sdev->in.substream != substream)
-               return -EINVAL;
-       return i2sbus_pcm_prepare(i2sdev, 1);
-}
-
-static int i2sbus_record_trigger(struct snd_pcm_substream *substream, int cmd)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-
-       if (!i2sdev)
-               return -EINVAL;
-       if (i2sdev->in.substream != substream)
-               return -EINVAL;
-       return i2sbus_pcm_trigger(i2sdev, 1, cmd);
-}
-
-static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream
-                                              *substream)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
-
-       if (!i2sdev)
-               return -EINVAL;
-       if (i2sdev->in.substream != substream)
-               return 0;
-       return i2sbus_pcm_pointer(i2sdev, 1);
-}
-
-static struct snd_pcm_ops i2sbus_record_ops = {
-       .open =         i2sbus_record_open,
-       .close =        i2sbus_record_close,
-       .ioctl =        snd_pcm_lib_ioctl,
-       .hw_params =    i2sbus_hw_params,
-       .hw_free =      i2sbus_record_hw_free,
-       .prepare =      i2sbus_record_prepare,
-       .trigger =      i2sbus_record_trigger,
-       .pointer =      i2sbus_record_pointer,
-};
-
-static void i2sbus_private_free(struct snd_pcm *pcm)
-{
-       struct i2sbus_dev *i2sdev = snd_pcm_chip(pcm);
-       struct codec_info_item *p, *tmp;
-
-       i2sdev->sound.pcm = NULL;
-       i2sdev->out.created = 0;
-       i2sdev->in.created = 0;
-       list_for_each_entry_safe(p, tmp, &i2sdev->sound.codec_list, list) {
-               printk(KERN_ERR "i2sbus: a codec didn't unregister!\n");
-               list_del(&p->list);
-               module_put(p->codec->owner);
-               kfree(p);
-       }
-       soundbus_dev_put(&i2sdev->sound);
-       module_put(THIS_MODULE);
-}
-
-int
-i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
-                   struct codec_info *ci, void *data)
-{
-       int err, in = 0, out = 0;
-       struct transfer_info *tmp;
-       struct i2sbus_dev *i2sdev = soundbus_dev_to_i2sbus_dev(dev);
-       struct codec_info_item *cii;
-
-       if (!dev->pcmname || dev->pcmid == -1) {
-               printk(KERN_ERR "i2sbus: pcm name and id must be set!\n");
-               return -EINVAL;
-       }
-
-       list_for_each_entry(cii, &dev->codec_list, list) {
-               if (cii->codec_data == data)
-                       return -EALREADY;
-       }
-
-       if (!ci->transfers || !ci->transfers->formats
-           || !ci->transfers->rates || !ci->usable)
-               return -EINVAL;
-
-       /* we currently code the i2s transfer on the clock, and support only
-        * 32 and 64 */
-       if (ci->bus_factor != 32 && ci->bus_factor != 64)
-               return -EINVAL;
-
-       /* If you want to fix this, you need to keep track of what transport infos
-        * are to be used, which codecs they belong to, and then fix all the
-        * sysclock/busclock stuff above to depend on which is usable */
-       list_for_each_entry(cii, &dev->codec_list, list) {
-               if (cii->codec->sysclock_factor != ci->sysclock_factor) {
-                       printk(KERN_DEBUG
-                              "cannot yet handle multiple different sysclocks!\n");
-                       return -EINVAL;
-               }
-               if (cii->codec->bus_factor != ci->bus_factor) {
-                       printk(KERN_DEBUG
-                              "cannot yet handle multiple different bus clocks!\n");
-                       return -EINVAL;
-               }
-       }
-
-       tmp = ci->transfers;
-       while (tmp->formats && tmp->rates) {
-               if (tmp->transfer_in)
-                       in = 1;
-               else
-                       out = 1;
-               tmp++;
-       }
-
-       cii = kzalloc(sizeof(struct codec_info_item), GFP_KERNEL);
-       if (!cii) {
-               printk(KERN_DEBUG "i2sbus: failed to allocate cii\n");
-               return -ENOMEM;
-       }
-
-       /* use the private data to point to the codec info */
-       cii->sdev = soundbus_dev_get(dev);
-       cii->codec = ci;
-       cii->codec_data = data;
-
-       if (!cii->sdev) {
-               printk(KERN_DEBUG
-                      "i2sbus: failed to get soundbus dev reference\n");
-               err = -ENODEV;
-               goto out_free_cii;
-       }
-
-       if (!try_module_get(THIS_MODULE)) {
-               printk(KERN_DEBUG "i2sbus: failed to get module reference!\n");
-               err = -EBUSY;
-               goto out_put_sdev;
-       }
-
-       if (!try_module_get(ci->owner)) {
-               printk(KERN_DEBUG
-                      "i2sbus: failed to get module reference to codec owner!\n");
-               err = -EBUSY;
-               goto out_put_this_module;
-       }
-
-       if (!dev->pcm) {
-               err = snd_pcm_new(card, dev->pcmname, dev->pcmid, 0, 0,
-                                 &dev->pcm);
-               if (err) {
-                       printk(KERN_DEBUG "i2sbus: failed to create pcm\n");
-                       goto out_put_ci_module;
-               }
-               dev->pcm->dev = &dev->ofdev.dev;
-       }
-
-       /* ALSA yet again sucks.
-        * If it is ever fixed, remove this line. See below. */
-       out = in = 1;
-
-       if (!i2sdev->out.created && out) {
-               if (dev->pcm->card != card) {
-                       /* eh? */
-                       printk(KERN_ERR
-                              "Can't attach same bus to different cards!\n");
-                       err = -EINVAL;
-                       goto out_put_ci_module;
-               }
-               err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1);
-               if (err)
-                       goto out_put_ci_module;
-               snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
-                               &i2sbus_playback_ops);
-               i2sdev->out.created = 1;
-       }
-
-       if (!i2sdev->in.created && in) {
-               if (dev->pcm->card != card) {
-                       printk(KERN_ERR
-                              "Can't attach same bus to different cards!\n");
-                       err = -EINVAL;
-                       goto out_put_ci_module;
-               }
-               err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1);
-               if (err)
-                       goto out_put_ci_module;
-               snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE,
-                               &i2sbus_record_ops);
-               i2sdev->in.created = 1;
-       }
-
-       /* so we have to register the pcm after adding any substream
-        * to it because alsa doesn't create the devices for the
-        * substreams when we add them later.
-        * Therefore, force in and out on both busses (above) and
-        * register the pcm now instead of just after creating it.
-        */
-       err = snd_device_register(card, dev->pcm);
-       if (err) {
-               printk(KERN_ERR "i2sbus: error registering new pcm\n");
-               goto out_put_ci_module;
-       }
-       /* no errors any more, so let's add this to our list */
-       list_add(&cii->list, &dev->codec_list);
-
-       dev->pcm->private_data = i2sdev;
-       dev->pcm->private_free = i2sbus_private_free;
-
-       /* well, we really should support scatter/gather DMA */
-       snd_pcm_lib_preallocate_pages_for_all(
-               dev->pcm, SNDRV_DMA_TYPE_DEV,
-               snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)),
-               64 * 1024, 64 * 1024);
-
-       return 0;
- out_put_ci_module:
-       module_put(ci->owner);
- out_put_this_module:
-       module_put(THIS_MODULE);
- out_put_sdev:
-       soundbus_dev_put(dev);
- out_free_cii:
-       kfree(cii);
-       return err;
-}
-
-void i2sbus_detach_codec(struct soundbus_dev *dev, void *data)
-{
-       struct codec_info_item *cii = NULL, *i;
-
-       list_for_each_entry(i, &dev->codec_list, list) {
-               if (i->codec_data == data) {
-                       cii = i;
-                       break;
-               }
-       }
-       if (cii) {
-               list_del(&cii->list);
-               module_put(cii->codec->owner);
-               kfree(cii);
-       }
-       /* no more codecs, but still a pcm? */
-       if (list_empty(&dev->codec_list) && dev->pcm) {
-               /* the actual cleanup is done by the callback above! */
-               snd_device_free(dev->pcm->card, dev->pcm);
-       }
-}
index ff29654..befefd9 100644 (file)
@@ -18,7 +18,7 @@
 #include <asm/pmac_feature.h>
 #include <asm/dbdma.h>
 
-#include "i2sbus-interface.h"
+#include "interface.h"
 #include "../soundbus.h"
 
 struct i2sbus_control {
diff --git a/sound/aoa/soundbus/i2sbus/interface.h b/sound/aoa/soundbus/i2sbus/interface.h
new file mode 100644 (file)
index 0000000..c6b5f54
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * i2sbus driver -- interface register definitions
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#ifndef __I2SBUS_INTERFACE_H
+#define __I2SBUS_INTERFACE_H
+
+/* i2s bus control registers, at least what we know about them */
+
+#define __PAD(m,n) u8 __pad##m[n]
+#define _PAD(line, n) __PAD(line, n)
+#define PAD(n) _PAD(__LINE__, (n))
+struct i2s_interface_regs {
+       __le32 intr_ctl;        /* 0x00 */
+       PAD(12);
+       __le32 serial_format;   /* 0x10 */
+       PAD(12);
+       __le32 codec_msg_out;   /* 0x20 */
+       PAD(12);
+       __le32 codec_msg_in;    /* 0x30 */
+       PAD(12);
+       __le32 frame_count;     /* 0x40 */
+       PAD(12);
+       __le32 frame_match;     /* 0x50 */
+       PAD(12);
+       __le32 data_word_sizes; /* 0x60 */
+       PAD(12);
+       __le32 peak_level_sel;  /* 0x70 */
+       PAD(12);
+       __le32 peak_level_in0;  /* 0x80 */
+       PAD(12);
+       __le32 peak_level_in1;  /* 0x90 */
+       PAD(12);
+       /* total size: 0x100 bytes */
+}  __attribute__((__packed__));
+
+/* interrupt register is just a bitfield with
+ * interrupt enable and pending bits */
+#define I2S_REG_INTR_CTL               0x00
+#      define I2S_INT_FRAME_COUNT              (1<<31)
+#      define I2S_PENDING_FRAME_COUNT          (1<<30)
+#      define I2S_INT_MESSAGE_FLAG             (1<<29)
+#      define I2S_PENDING_MESSAGE_FLAG         (1<<28)
+#      define I2S_INT_NEW_PEAK                 (1<<27)
+#      define I2S_PENDING_NEW_PEAK             (1<<26)
+#      define I2S_INT_CLOCKS_STOPPED           (1<<25)
+#      define I2S_PENDING_CLOCKS_STOPPED       (1<<24)
+#      define I2S_INT_EXTERNAL_SYNC_ERROR      (1<<23)
+#      define I2S_PENDING_EXTERNAL_SYNC_ERROR  (1<<22)
+#      define I2S_INT_EXTERNAL_SYNC_OK         (1<<21)
+#      define I2S_PENDING_EXTERNAL_SYNC_OK     (1<<20)
+#      define I2S_INT_NEW_SAMPLE_RATE          (1<<19)
+#      define I2S_PENDING_NEW_SAMPLE_RATE      (1<<18)
+#      define I2S_INT_STATUS_FLAG              (1<<17)
+#      define I2S_PENDING_STATUS_FLAG          (1<<16)
+
+/* serial format register is more interesting :)
+ * It contains:
+ *  - clock source
+ *  - MClk divisor
+ *  - SClk divisor
+ *  - SClk master flag
+ *  - serial format (sony, i2s 64x, i2s 32x, dav, silabs)
+ *  - external sample frequency interrupt (don't understand)
+ *  - external sample frequency
+ */
+#define I2S_REG_SERIAL_FORMAT          0x10
+/* clock source. You get either 18.432, 45.1584 or 49.1520 MHz */
+#      define I2S_SF_CLOCK_SOURCE_SHIFT        30
+#      define I2S_SF_CLOCK_SOURCE_MASK         (3<<I2S_SF_CLOCK_SOURCE_SHIFT)
+#      define I2S_SF_CLOCK_SOURCE_18MHz        (0<<I2S_SF_CLOCK_SOURCE_SHIFT)
+#      define I2S_SF_CLOCK_SOURCE_45MHz        (1<<I2S_SF_CLOCK_SOURCE_SHIFT)
+#      define I2S_SF_CLOCK_SOURCE_49MHz        (2<<I2S_SF_CLOCK_SOURCE_SHIFT)
+/* also, let's define the exact clock speeds here, in Hz */
+#define I2S_CLOCK_SPEED_18MHz  18432000
+#define I2S_CLOCK_SPEED_45MHz  45158400
+#define I2S_CLOCK_SPEED_49MHz  49152000
+/* MClk is the clock that drives the codec, usually called its 'system clock'.
+ * It is derived by taking only every 'divisor' tick of the clock.
+ */
+#      define I2S_SF_MCLKDIV_SHIFT             24
+#      define I2S_SF_MCLKDIV_MASK              (0x1F<<I2S_SF_MCLKDIV_SHIFT)
+#      define I2S_SF_MCLKDIV_1                 (0x14<<I2S_SF_MCLKDIV_SHIFT)
+#      define I2S_SF_MCLKDIV_3                 (0x13<<I2S_SF_MCLKDIV_SHIFT)
+#      define I2S_SF_MCLKDIV_5                 (0x12<<I2S_SF_MCLKDIV_SHIFT)
+#      define I2S_SF_MCLKDIV_14                (0x0E<<I2S_SF_MCLKDIV_SHIFT)
+#      define I2S_SF_MCLKDIV_OTHER(div)        (((div/2-1)<<I2S_SF_MCLKDIV_SHIFT)&I2S_SF_MCLKDIV_MASK)
+static inline int i2s_sf_mclkdiv(int div, int *out)
+{
+       int d;
+
+       switch(div) {
+       case 1: *out |= I2S_SF_MCLKDIV_1; return 0;
+       case 3: *out |= I2S_SF_MCLKDIV_3; return 0;
+       case 5: *out |= I2S_SF_MCLKDIV_5; return 0;
+       case 14: *out |= I2S_SF_MCLKDIV_14; return 0;
+       default:
+               if (div%2) return -1;
+               d = div/2-1;
+               if (d == 0x14 || d == 0x13 || d == 0x12 || d == 0x0E)
+                       return -1;
+               *out |= I2S_SF_MCLKDIV_OTHER(div);
+               return 0;
+       }
+}
+/* SClk is the clock that drives the i2s wire bus. Note that it is
+ * derived from the MClk above by taking only every 'divisor' tick
+ * of MClk.
+ */
+#      define I2S_SF_SCLKDIV_SHIFT             20
+#      define I2S_SF_SCLKDIV_MASK              (0xF<<I2S_SF_SCLKDIV_SHIFT)
+#      define I2S_SF_SCLKDIV_1                 (8<<I2S_SF_SCLKDIV_SHIFT)
+#      define I2S_SF_SCLKDIV_3                 (9<<I2S_SF_SCLKDIV_SHIFT)
+#      define I2S_SF_SCLKDIV_OTHER(div)        (((div/2-1)<<I2S_SF_SCLKDIV_SHIFT)&I2S_SF_SCLKDIV_MASK)
+static inline int i2s_sf_sclkdiv(int div, int *out)
+{
+       int d;
+
+       switch(div) {
+       case 1: *out |= I2S_SF_SCLKDIV_1; return 0;
+       case 3: *out |= I2S_SF_SCLKDIV_3; return 0;
+       default:
+               if (div%2) return -1;
+               d = div/2-1;
+               if (d == 8 || d == 9) return -1;
+               *out |= I2S_SF_SCLKDIV_OTHER(div);
+               return 0;
+       }
+}
+#      define I2S_SF_SCLK_MASTER               (1<<19)
+/* serial format is the way the data is put to the i2s wire bus */
+#      define I2S_SF_SERIAL_FORMAT_SHIFT       16
+#      define I2S_SF_SERIAL_FORMAT_MASK        (7<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#      define I2S_SF_SERIAL_FORMAT_SONY        (0<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#      define I2S_SF_SERIAL_FORMAT_I2S_64X     (1<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#      define I2S_SF_SERIAL_FORMAT_I2S_32X     (2<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#      define I2S_SF_SERIAL_FORMAT_I2S_DAV     (4<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#      define I2S_SF_SERIAL_FORMAT_I2S_SILABS  (5<<I2S_SF_SERIAL_FORMAT_SHIFT)
+/* unknown */
+#      define I2S_SF_EXT_SAMPLE_FREQ_INT_SHIFT 12
+#      define I2S_SF_EXT_SAMPLE_FREQ_INT_MASK  (0xF<<I2S_SF_SAMPLE_FREQ_INT_SHIFT)
+/* probably gives external frequency? */
+#      define I2S_SF_EXT_SAMPLE_FREQ_MASK      0xFFF
+
+/* used to send codec messages, but how isn't clear */
+#define I2S_REG_CODEC_MSG_OUT          0x20
+
+/* used to receive codec messages, but how isn't clear */
+#define I2S_REG_CODEC_MSG_IN           0x30
+
+/* frame count reg isn't clear to me yet, but probably useful */
+#define I2S_REG_FRAME_COUNT            0x40
+
+/* program to some value, and get interrupt if frame count reaches it */
+#define I2S_REG_FRAME_MATCH            0x50
+
+/* this register describes how the bus transfers data */
+#define I2S_REG_DATA_WORD_SIZES                0x60
+/* number of interleaved input channels */
+#      define I2S_DWS_NUM_CHANNELS_IN_SHIFT    24
+#      define I2S_DWS_NUM_CHANNELS_IN_MASK     (0x1F<<I2S_DWS_NUM_CHANNELS_IN_SHIFT)
+/* word size of input data */
+#      define I2S_DWS_DATA_IN_SIZE_SHIFT       16
+#      define I2S_DWS_DATA_IN_16BIT            (0<<I2S_DWS_DATA_IN_SIZE_SHIFT)
+#      define I2S_DWS_DATA_IN_24BIT            (3<<I2S_DWS_DATA_IN_SIZE_SHIFT)
+/* number of interleaved output channels */
+#      define I2S_DWS_NUM_CHANNELS_OUT_SHIFT   8
+#      define I2S_DWS_NUM_CHANNELS_OUT_MASK    (0x1F<<I2S_DWS_NUM_CHANNELS_OUT_SHIFT)
+/* word size of output data */
+#      define I2S_DWS_DATA_OUT_SIZE_SHIFT      0
+#      define I2S_DWS_DATA_OUT_16BIT           (0<<I2S_DWS_DATA_OUT_SIZE_SHIFT)
+#      define I2S_DWS_DATA_OUT_24BIT           (3<<I2S_DWS_DATA_OUT_SIZE_SHIFT)
+
+
+/* unknown */
+#define I2S_REG_PEAK_LEVEL_SEL         0x70
+
+/* unknown */
+#define I2S_REG_PEAK_LEVEL_IN0         0x80
+
+/* unknown */
+#define I2S_REG_PEAK_LEVEL_IN1         0x90
+
+#endif /* __I2SBUS_INTERFACE_H */
diff --git a/sound/aoa/soundbus/i2sbus/pcm.c b/sound/aoa/soundbus/i2sbus/pcm.c
new file mode 100644 (file)
index 0000000..59bacd3
--- /dev/null
@@ -0,0 +1,1062 @@
+/*
+ * i2sbus driver -- pcm routines
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <asm/macio.h>
+#include <linux/pci.h>
+#include "../soundbus.h"
+#include "i2sbus.h"
+
+static inline void get_pcm_info(struct i2sbus_dev *i2sdev, int in,
+                               struct pcm_info **pi, struct pcm_info **other)
+{
+       if (in) {
+               if (pi)
+                       *pi = &i2sdev->in;
+               if (other)
+                       *other = &i2sdev->out;
+       } else {
+               if (pi)
+                       *pi = &i2sdev->out;
+               if (other)
+                       *other = &i2sdev->in;
+       }
+}
+
+static int clock_and_divisors(int mclk, int sclk, int rate, int *out)
+{
+       /* sclk must be derived from mclk! */
+       if (mclk % sclk)
+               return -1;
+       /* derive sclk register value */
+       if (i2s_sf_sclkdiv(mclk / sclk, out))
+               return -1;
+
+       if (I2S_CLOCK_SPEED_18MHz % (rate * mclk) == 0) {
+               if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_18MHz / (rate * mclk), out)) {
+                       *out |= I2S_SF_CLOCK_SOURCE_18MHz;
+                       return 0;
+               }
+       }
+       if (I2S_CLOCK_SPEED_45MHz % (rate * mclk) == 0) {
+               if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_45MHz / (rate * mclk), out)) {
+                       *out |= I2S_SF_CLOCK_SOURCE_45MHz;
+                       return 0;
+               }
+       }
+       if (I2S_CLOCK_SPEED_49MHz % (rate * mclk) == 0) {
+               if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_49MHz / (rate * mclk), out)) {
+                       *out |= I2S_SF_CLOCK_SOURCE_49MHz;
+                       return 0;
+               }
+       }
+       return -1;
+}
+
+#define CHECK_RATE(rate)                                               \
+       do { if (rates & SNDRV_PCM_RATE_ ##rate) {                      \
+               int dummy;                                              \
+               if (clock_and_divisors(sysclock_factor,                 \
+                                      bus_factor, rate, &dummy))       \
+                       rates &= ~SNDRV_PCM_RATE_ ##rate;               \
+       } } while (0)
+
+static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in)
+{
+       struct pcm_info *pi, *other;
+       struct soundbus_dev *sdev;
+       int masks_inited = 0, err;
+       struct codec_info_item *cii, *rev;
+       struct snd_pcm_hardware *hw;
+       u64 formats = 0;
+       unsigned int rates = 0;
+       struct transfer_info v;
+       int result = 0;
+       int bus_factor = 0, sysclock_factor = 0;
+       int found_this;
+
+       mutex_lock(&i2sdev->lock);
+
+       get_pcm_info(i2sdev, in, &pi, &other);
+
+       hw = &pi->substream->runtime->hw;
+       sdev = &i2sdev->sound;
+
+       if (pi->active) {
+               /* alsa messed up */
+               result = -EBUSY;
+               goto out_unlock;
+       }
+
+       /* we now need to assign the hw */
+       list_for_each_entry(cii, &sdev->codec_list, list) {
+               struct transfer_info *ti = cii->codec->transfers;
+               bus_factor = cii->codec->bus_factor;
+               sysclock_factor = cii->codec->sysclock_factor;
+               while (ti->formats && ti->rates) {
+                       v = *ti;
+                       if (ti->transfer_in == in
+                           && cii->codec->usable(cii, ti, &v)) {
+                               if (masks_inited) {
+                                       formats &= v.formats;
+                                       rates &= v.rates;
+                               } else {
+                                       formats = v.formats;
+                                       rates = v.rates;
+                                       masks_inited = 1;
+                               }
+                       }
+                       ti++;
+               }
+       }
+       if (!masks_inited || !bus_factor || !sysclock_factor) {
+               result = -ENODEV;
+               goto out_unlock;
+       }
+       /* bus dependent stuff */
+       hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+                  SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME |
+                  SNDRV_PCM_INFO_JOINT_DUPLEX;
+
+       CHECK_RATE(5512);
+       CHECK_RATE(8000);
+       CHECK_RATE(11025);
+       CHECK_RATE(16000);
+       CHECK_RATE(22050);
+       CHECK_RATE(32000);
+       CHECK_RATE(44100);
+       CHECK_RATE(48000);
+       CHECK_RATE(64000);
+       CHECK_RATE(88200);
+       CHECK_RATE(96000);
+       CHECK_RATE(176400);
+       CHECK_RATE(192000);
+       hw->rates = rates;
+
+       /* well. the codec might want 24 bits only, and we'll
+        * ever only transfer 24 bits, but they are top-aligned!
+        * So for alsa, we claim that we're doing full 32 bit
+        * while in reality we'll ignore the lower 8 bits of
+        * that when doing playback (they're transferred as 0
+        * as far as I know, no codecs we have are 32-bit capable
+        * so I can't really test) and when doing recording we'll
+        * always have those lower 8 bits recorded as 0 */
+       if (formats & SNDRV_PCM_FMTBIT_S24_BE)
+               formats |= SNDRV_PCM_FMTBIT_S32_BE;
+       if (formats & SNDRV_PCM_FMTBIT_U24_BE)
+               formats |= SNDRV_PCM_FMTBIT_U32_BE;
+       /* now mask off what we can support. I suppose we could
+        * also support S24_3LE and some similar formats, but I
+        * doubt there's a codec that would be able to use that,
+        * so we don't support it here. */
+       hw->formats = formats & (SNDRV_PCM_FMTBIT_S16_BE |
+                                SNDRV_PCM_FMTBIT_U16_BE |
+                                SNDRV_PCM_FMTBIT_S32_BE |
+                                SNDRV_PCM_FMTBIT_U32_BE);
+
+       /* we need to set the highest and lowest rate possible.
+        * These are the highest and lowest rates alsa can
+        * support properly in its bitfield.
+        * Below, we'll use that to restrict to the rate
+        * currently in use (if any). */
+       hw->rate_min = 5512;
+       hw->rate_max = 192000;
+       /* if the other stream is active, then we can only
+        * support what it is currently using.
+        * FIXME: I lied. This comment is wrong. We can support
+        * anything that works with the same serial format, ie.
+        * when recording 24 bit sound we can well play 16 bit
+        * sound at the same time iff using the same transfer mode.
+        */
+       if (other->active) {
+               /* FIXME: is this guaranteed by the alsa api? */
+               hw->formats &= (1ULL << i2sdev->format);
+               /* see above, restrict rates to the one we already have */
+               hw->rate_min = i2sdev->rate;
+               hw->rate_max = i2sdev->rate;
+       }
+
+       hw->channels_min = 2;
+       hw->channels_max = 2;
+       /* these are somewhat arbitrary */
+       hw->buffer_bytes_max = 131072;
+       hw->period_bytes_min = 256;
+       hw->period_bytes_max = 16384;
+       hw->periods_min = 3;
+       hw->periods_max = MAX_DBDMA_COMMANDS;
+       err = snd_pcm_hw_constraint_integer(pi->substream->runtime,
+                                           SNDRV_PCM_HW_PARAM_PERIODS);
+       if (err < 0) {
+               result = err;
+               goto out_unlock;
+       }
+       list_for_each_entry(cii, &sdev->codec_list, list) {
+               if (cii->codec->open) {
+                       err = cii->codec->open(cii, pi->substream);
+                       if (err) {
+                               result = err;
+                               /* unwind */
+                               found_this = 0;
+                               list_for_each_entry_reverse(rev,
+                                   &sdev->codec_list, list) {
+                                       if (found_this && rev->codec->close) {
+                                               rev->codec->close(rev,
+                                                               pi->substream);
+                                       }
+                                       if (rev == cii)
+                                               found_this = 1;
+                               }
+                               goto out_unlock;
+                       }
+               }
+       }
+
+ out_unlock:
+       mutex_unlock(&i2sdev->lock);
+       return result;
+}
+
+#undef CHECK_RATE
+
+static int i2sbus_pcm_close(struct i2sbus_dev *i2sdev, int in)
+{
+       struct codec_info_item *cii;
+       struct pcm_info *pi;
+       int err = 0, tmp;
+
+       mutex_lock(&i2sdev->lock);
+
+       get_pcm_info(i2sdev, in, &pi, NULL);
+
+       list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+               if (cii->codec->close) {
+                       tmp = cii->codec->close(cii, pi->substream);
+                       if (tmp)
+                               err = tmp;
+               }
+       }
+
+       pi->substream = NULL;
+       pi->active = 0;
+       mutex_unlock(&i2sdev->lock);
+       return err;
+}
+
+static void i2sbus_wait_for_stop(struct i2sbus_dev *i2sdev,
+                                struct pcm_info *pi)
+{
+       unsigned long flags;
+       struct completion done;
+       long timeout;
+
+       spin_lock_irqsave(&i2sdev->low_lock, flags);
+       if (pi->dbdma_ring.stopping) {
+               init_completion(&done);
+               pi->stop_completion = &done;
+               spin_unlock_irqrestore(&i2sdev->low_lock, flags);
+               timeout = wait_for_completion_timeout(&done, HZ);
+               spin_lock_irqsave(&i2sdev->low_lock, flags);
+               pi->stop_completion = NULL;
+               if (timeout == 0) {
+                       /* timeout expired, stop dbdma forcefully */
+                       printk(KERN_ERR "i2sbus_wait_for_stop: timed out\n");
+                       /* make sure RUN, PAUSE and S0 bits are cleared */
+                       out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
+                       pi->dbdma_ring.stopping = 0;
+                       timeout = 10;
+                       while (in_le32(&pi->dbdma->status) & ACTIVE) {
+                               if (--timeout <= 0)
+                                       break;
+                               udelay(1);
+                       }
+               }
+       }
+       spin_unlock_irqrestore(&i2sdev->low_lock, flags);
+}
+
+#ifdef CONFIG_PM
+void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev)
+{
+       struct pcm_info *pi;
+
+       get_pcm_info(i2sdev, 0, &pi, NULL);
+       i2sbus_wait_for_stop(i2sdev, pi);
+       get_pcm_info(i2sdev, 1, &pi, NULL);
+       i2sbus_wait_for_stop(i2sdev, pi);
+}
+#endif
+
+static int i2sbus_hw_params(struct snd_pcm_substream *substream,
+                           struct snd_pcm_hw_params *params)
+{
+       return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+}
+
+static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+       struct pcm_info *pi;
+
+       get_pcm_info(i2sdev, in, &pi, NULL);
+       if (pi->dbdma_ring.stopping)
+               i2sbus_wait_for_stop(i2sdev, pi);
+       snd_pcm_lib_free_pages(substream);
+       return 0;
+}
+
+static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream)
+{
+       return i2sbus_hw_free(substream, 0);
+}
+
+static int i2sbus_record_hw_free(struct snd_pcm_substream *substream)
+{
+       return i2sbus_hw_free(substream, 1);
+}
+
+static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
+{
+       /* whee. Hard work now. The user has selected a bitrate
+        * and bit format, so now we have to program our
+        * I2S controller appropriately. */
+       struct snd_pcm_runtime *runtime;
+       struct dbdma_cmd *command;
+       int i, periodsize, nperiods;
+       dma_addr_t offset;
+       struct bus_info bi;
+       struct codec_info_item *cii;
+       int sfr = 0;            /* serial format register */
+       int dws = 0;            /* data word sizes reg */
+       int input_16bit;
+       struct pcm_info *pi, *other;
+       int cnt;
+       int result = 0;
+       unsigned int cmd, stopaddr;
+
+       mutex_lock(&i2sdev->lock);
+
+       get_pcm_info(i2sdev, in, &pi, &other);
+
+       if (pi->dbdma_ring.running) {
+               result = -EBUSY;
+               goto out_unlock;
+       }
+       if (pi->dbdma_ring.stopping)
+               i2sbus_wait_for_stop(i2sdev, pi);
+
+       if (!pi->substream || !pi->substream->runtime) {
+               result = -EINVAL;
+               goto out_unlock;
+       }
+
+       runtime = pi->substream->runtime;
+       pi->active = 1;
+       if (other->active &&
+           ((i2sdev->format != runtime->format)
+            || (i2sdev->rate != runtime->rate))) {
+               result = -EINVAL;
+               goto out_unlock;
+       }
+
+       i2sdev->format = runtime->format;
+       i2sdev->rate = runtime->rate;
+
+       periodsize = snd_pcm_lib_period_bytes(pi->substream);
+       nperiods = pi->substream->runtime->periods;
+       pi->current_period = 0;
+
+       /* generate dbdma command ring first */
+       command = pi->dbdma_ring.cmds;
+       memset(command, 0, (nperiods + 2) * sizeof(struct dbdma_cmd));
+
+       /* commands to DMA to/from the ring */
+       /*
+        * For input, we need to do a graceful stop; if we abort
+        * the DMA, we end up with leftover bytes that corrupt
+        * the next recording.  To do this we set the S0 status
+        * bit and wait for the DMA controller to stop.  Each
+        * command has a branch condition to
+        * make it branch to a stop command if S0 is set.
+        * On input we also need to wait for the S7 bit to be
+        * set before turning off the DMA controller.
+        * In fact we do the graceful stop for output as well.
+        */
+       offset = runtime->dma_addr;
+       cmd = (in? INPUT_MORE: OUTPUT_MORE) | BR_IFSET | INTR_ALWAYS;
+       stopaddr = pi->dbdma_ring.bus_cmd_start +
+               (nperiods + 1) * sizeof(struct dbdma_cmd);
+       for (i = 0; i < nperiods; i++, command++, offset += periodsize) {
+               command->command = cpu_to_le16(cmd);
+               command->cmd_dep = cpu_to_le32(stopaddr);
+               command->phy_addr = cpu_to_le32(offset);
+               command->req_count = cpu_to_le16(periodsize);
+       }
+
+       /* branch back to beginning of ring */
+       command->command = cpu_to_le16(DBDMA_NOP | BR_ALWAYS);
+       command->cmd_dep = cpu_to_le32(pi->dbdma_ring.bus_cmd_start);
+       command++;
+
+       /* set stop command */
+       command->command = cpu_to_le16(DBDMA_STOP);
+
+       /* ok, let's set the serial format and stuff */
+       switch (runtime->format) {
+       /* 16 bit formats */
+       case SNDRV_PCM_FORMAT_S16_BE:
+       case SNDRV_PCM_FORMAT_U16_BE:
+               /* FIXME: if we add different bus factors we need to
+                * do more here!! */
+               bi.bus_factor = 0;
+               list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+                       bi.bus_factor = cii->codec->bus_factor;
+                       break;
+               }
+               if (!bi.bus_factor) {
+                       result = -ENODEV;
+                       goto out_unlock;
+               }
+               input_16bit = 1;
+               break;
+       case SNDRV_PCM_FORMAT_S32_BE:
+       case SNDRV_PCM_FORMAT_U32_BE:
+               /* force 64x bus speed, otherwise the data cannot be
+                * transferred quickly enough! */
+               bi.bus_factor = 64;
+               input_16bit = 0;
+               break;
+       default:
+               result = -EINVAL;
+               goto out_unlock;
+       }
+       /* we assume all sysclocks are the same! */
+       list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+               bi.sysclock_factor = cii->codec->sysclock_factor;
+               break;
+       }
+
+       if (clock_and_divisors(bi.sysclock_factor,
+                              bi.bus_factor,
+                              runtime->rate,
+                              &sfr) < 0) {
+               result = -EINVAL;
+               goto out_unlock;
+       }
+       switch (bi.bus_factor) {
+       case 32:
+               sfr |= I2S_SF_SERIAL_FORMAT_I2S_32X;
+               break;
+       case 64:
+               sfr |= I2S_SF_SERIAL_FORMAT_I2S_64X;
+               break;
+       }
+       /* FIXME: THIS ASSUMES MASTER ALL THE TIME */
+       sfr |= I2S_SF_SCLK_MASTER;
+
+       list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+               int err = 0;
+               if (cii->codec->prepare)
+                       err = cii->codec->prepare(cii, &bi, pi->substream);
+               if (err) {
+                       result = err;
+                       goto out_unlock;
+               }
+       }
+       /* codecs are fine with it, so set our clocks */
+       if (input_16bit)
+               dws =   (2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
+                       (2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
+                       I2S_DWS_DATA_IN_16BIT | I2S_DWS_DATA_OUT_16BIT;
+       else
+               dws =   (2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
+                       (2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
+                       I2S_DWS_DATA_IN_24BIT | I2S_DWS_DATA_OUT_24BIT;
+
+       /* early exit if already programmed correctly */
+       /* not locking these is fine since we touch them only in this function */
+       if (in_le32(&i2sdev->intfregs->serial_format) == sfr
+        && in_le32(&i2sdev->intfregs->data_word_sizes) == dws)
+               goto out_unlock;
+
+       /* let's notify the codecs about clocks going away.
+        * For now we only do mastering on the i2s cell... */
+       list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+               if (cii->codec->switch_clock)
+                       cii->codec->switch_clock(cii, CLOCK_SWITCH_PREPARE_SLAVE);
+
+       i2sbus_control_enable(i2sdev->control, i2sdev);
+       i2sbus_control_cell(i2sdev->control, i2sdev, 1);
+
+       out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
+
+       i2sbus_control_clock(i2sdev->control, i2sdev, 0);
+
+       msleep(1);
+
+       /* wait for clock stopped. This can apparently take a while... */
+       cnt = 100;
+       while (cnt-- &&
+           !(in_le32(&i2sdev->intfregs->intr_ctl) & I2S_PENDING_CLOCKS_STOPPED)) {
+               msleep(5);
+       }
+       out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
+
+       /* not locking these is fine since we touch them only in this function */
+       out_le32(&i2sdev->intfregs->serial_format, sfr);
+       out_le32(&i2sdev->intfregs->data_word_sizes, dws);
+
+        i2sbus_control_enable(i2sdev->control, i2sdev);
+        i2sbus_control_cell(i2sdev->control, i2sdev, 1);
+        i2sbus_control_clock(i2sdev->control, i2sdev, 1);
+       msleep(1);
+
+       list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+               if (cii->codec->switch_clock)
+                       cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE);
+
+ out_unlock:
+       mutex_unlock(&i2sdev->lock);
+       return result;
+}
+
+#ifdef CONFIG_PM
+void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev)
+{
+       i2sbus_pcm_prepare(i2sdev, 0);
+       i2sbus_pcm_prepare(i2sdev, 1);
+}
+#endif
+
+static int i2sbus_pcm_trigger(struct i2sbus_dev *i2sdev, int in, int cmd)
+{
+       struct codec_info_item *cii;
+       struct pcm_info *pi;
+       int result = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&i2sdev->low_lock, flags);
+
+       get_pcm_info(i2sdev, in, &pi, NULL);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+               if (pi->dbdma_ring.running) {
+                       result = -EALREADY;
+                       goto out_unlock;
+               }
+               list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+                       if (cii->codec->start)
+                               cii->codec->start(cii, pi->substream);
+               pi->dbdma_ring.running = 1;
+
+               if (pi->dbdma_ring.stopping) {
+                       /* Clear the S0 bit, then see if we stopped yet */
+                       out_le32(&pi->dbdma->control, 1 << 16);
+                       if (in_le32(&pi->dbdma->status) & ACTIVE) {
+                               /* possible race here? */
+                               udelay(10);
+                               if (in_le32(&pi->dbdma->status) & ACTIVE) {
+                                       pi->dbdma_ring.stopping = 0;
+                                       goto out_unlock; /* keep running */
+                               }
+                       }
+               }
+
+               /* make sure RUN, PAUSE and S0 bits are cleared */
+               out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
+
+               /* set branch condition select register */
+               out_le32(&pi->dbdma->br_sel, (1 << 16) | 1);
+
+               /* write dma command buffer address to the dbdma chip */
+               out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start);
+
+               /* initialize the frame count and current period */
+               pi->current_period = 0;
+               pi->frame_count = in_le32(&i2sdev->intfregs->frame_count);
+
+               /* set the DMA controller running */
+               out_le32(&pi->dbdma->control, (RUN << 16) | RUN);
+
+               /* off you go! */
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               if (!pi->dbdma_ring.running) {
+                       result = -EALREADY;
+                       goto out_unlock;
+               }
+               pi->dbdma_ring.running = 0;
+
+               /* Set the S0 bit to make the DMA branch to the stop cmd */
+               out_le32(&pi->dbdma->control, (1 << 16) | 1);
+               pi->dbdma_ring.stopping = 1;
+
+               list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+                       if (cii->codec->stop)
+                               cii->codec->stop(cii, pi->substream);
+               break;
+       default:
+               result = -EINVAL;
+               goto out_unlock;
+       }
+
+ out_unlock:
+       spin_unlock_irqrestore(&i2sdev->low_lock, flags);
+       return result;
+}
+
+static snd_pcm_uframes_t i2sbus_pcm_pointer(struct i2sbus_dev *i2sdev, int in)
+{
+       struct pcm_info *pi;
+       u32 fc;
+
+       get_pcm_info(i2sdev, in, &pi, NULL);
+
+       fc = in_le32(&i2sdev->intfregs->frame_count);
+       fc = fc - pi->frame_count;
+
+       if (fc >= pi->substream->runtime->buffer_size)
+               fc %= pi->substream->runtime->buffer_size;
+       return fc;
+}
+
+static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in)
+{
+       struct pcm_info *pi;
+       u32 fc, nframes;
+       u32 status;
+       int timeout, i;
+       int dma_stopped = 0;
+       struct snd_pcm_runtime *runtime;
+
+       spin_lock(&i2sdev->low_lock);
+       get_pcm_info(i2sdev, in, &pi, NULL);
+       if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping)
+               goto out_unlock;
+
+       i = pi->current_period;
+       runtime = pi->substream->runtime;
+       while (pi->dbdma_ring.cmds[i].xfer_status) {
+               if (le16_to_cpu(pi->dbdma_ring.cmds[i].xfer_status) & BT)
+                       /*
+                        * BT is the branch taken bit.  If it took a branch
+                        * it is because we set the S0 bit to make it
+                        * branch to the stop command.
+                        */
+                       dma_stopped = 1;
+               pi->dbdma_ring.cmds[i].xfer_status = 0;
+
+               if (++i >= runtime->periods) {
+                       i = 0;
+                       pi->frame_count += runtime->buffer_size;
+               }
+               pi->current_period = i;
+
+               /*
+                * Check the frame count.  The DMA tends to get a bit
+                * ahead of the frame counter, which confuses the core.
+                */
+               fc = in_le32(&i2sdev->intfregs->frame_count);
+               nframes = i * runtime->period_size;
+               if (fc < pi->frame_count + nframes)
+                       pi->frame_count = fc - nframes;
+       }
+
+       if (dma_stopped) {
+               timeout = 1000;
+               for (;;) {
+                       status = in_le32(&pi->dbdma->status);
+                       if (!(status & ACTIVE) && (!in || (status & 0x80)))
+                               break;
+                       if (--timeout <= 0) {
+                               printk(KERN_ERR "i2sbus: timed out "
+                                      "waiting for DMA to stop!\n");
+                               break;
+                       }
+                       udelay(1);
+               }
+
+               /* Turn off DMA controller, clear S0 bit */
+               out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
+
+               pi->dbdma_ring.stopping = 0;
+               if (pi->stop_completion)
+                       complete(pi->stop_completion);
+       }
+
+       if (!pi->dbdma_ring.running)
+               goto out_unlock;
+       spin_unlock(&i2sdev->low_lock);
+       /* may call _trigger again, hence needs to be unlocked */
+       snd_pcm_period_elapsed(pi->substream);
+       return;
+
+ out_unlock:
+       spin_unlock(&i2sdev->low_lock);
+}
+
+irqreturn_t i2sbus_tx_intr(int irq, void *devid)
+{
+       handle_interrupt((struct i2sbus_dev *)devid, 0);
+       return IRQ_HANDLED;
+}
+
+irqreturn_t i2sbus_rx_intr(int irq, void *devid)
+{
+       handle_interrupt((struct i2sbus_dev *)devid, 1);
+       return IRQ_HANDLED;
+}
+
+static int i2sbus_playback_open(struct snd_pcm_substream *substream)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+       if (!i2sdev)
+               return -EINVAL;
+       i2sdev->out.substream = substream;
+       return i2sbus_pcm_open(i2sdev, 0);
+}
+
+static int i2sbus_playback_close(struct snd_pcm_substream *substream)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+       int err;
+
+       if (!i2sdev)
+               return -EINVAL;
+       if (i2sdev->out.substream != substream)
+               return -EINVAL;
+       err = i2sbus_pcm_close(i2sdev, 0);
+       if (!err)
+               i2sdev->out.substream = NULL;
+       return err;
+}
+
+static int i2sbus_playback_prepare(struct snd_pcm_substream *substream)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+       if (!i2sdev)
+               return -EINVAL;
+       if (i2sdev->out.substream != substream)
+               return -EINVAL;
+       return i2sbus_pcm_prepare(i2sdev, 0);
+}
+
+static int i2sbus_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+       if (!i2sdev)
+               return -EINVAL;
+       if (i2sdev->out.substream != substream)
+               return -EINVAL;
+       return i2sbus_pcm_trigger(i2sdev, 0, cmd);
+}
+
+static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream
+                                                *substream)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+       if (!i2sdev)
+               return -EINVAL;
+       if (i2sdev->out.substream != substream)
+               return 0;
+       return i2sbus_pcm_pointer(i2sdev, 0);
+}
+
+static struct snd_pcm_ops i2sbus_playback_ops = {
+       .open =         i2sbus_playback_open,
+       .close =        i2sbus_playback_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    i2sbus_hw_params,
+       .hw_free =      i2sbus_playback_hw_free,
+       .prepare =      i2sbus_playback_prepare,
+       .trigger =      i2sbus_playback_trigger,
+       .pointer =      i2sbus_playback_pointer,
+};
+
+static int i2sbus_record_open(struct snd_pcm_substream *substream)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+       if (!i2sdev)
+               return -EINVAL;
+       i2sdev->in.substream = substream;
+       return i2sbus_pcm_open(i2sdev, 1);
+}
+
+static int i2sbus_record_close(struct snd_pcm_substream *substream)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+       int err;
+
+       if (!i2sdev)
+               return -EINVAL;
+       if (i2sdev->in.substream != substream)
+               return -EINVAL;
+       err = i2sbus_pcm_close(i2sdev, 1);
+       if (!err)
+               i2sdev->in.substream = NULL;
+       return err;
+}
+
+static int i2sbus_record_prepare(struct snd_pcm_substream *substream)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+       if (!i2sdev)
+               return -EINVAL;
+       if (i2sdev->in.substream != substream)
+               return -EINVAL;
+       return i2sbus_pcm_prepare(i2sdev, 1);
+}
+
+static int i2sbus_record_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+       if (!i2sdev)
+               return -EINVAL;
+       if (i2sdev->in.substream != substream)
+               return -EINVAL;
+       return i2sbus_pcm_trigger(i2sdev, 1, cmd);
+}
+
+static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream
+                                              *substream)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+       if (!i2sdev)
+               return -EINVAL;
+       if (i2sdev->in.substream != substream)
+               return 0;
+       return i2sbus_pcm_pointer(i2sdev, 1);
+}
+
+static struct snd_pcm_ops i2sbus_record_ops = {
+       .open =         i2sbus_record_open,
+       .close =        i2sbus_record_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    i2sbus_hw_params,
+       .hw_free =      i2sbus_record_hw_free,
+       .prepare =      i2sbus_record_prepare,
+       .trigger =      i2sbus_record_trigger,
+       .pointer =      i2sbus_record_pointer,
+};
+
+static void i2sbus_private_free(struct snd_pcm *pcm)
+{
+       struct i2sbus_dev *i2sdev = snd_pcm_chip(pcm);
+       struct codec_info_item *p, *tmp;
+
+       i2sdev->sound.pcm = NULL;
+       i2sdev->out.created = 0;
+       i2sdev->in.created = 0;
+       list_for_each_entry_safe(p, tmp, &i2sdev->sound.codec_list, list) {
+               printk(KERN_ERR "i2sbus: a codec didn't unregister!\n");
+               list_del(&p->list);
+               module_put(p->codec->owner);
+               kfree(p);
+       }
+       soundbus_dev_put(&i2sdev->sound);
+       module_put(THIS_MODULE);
+}
+
+int
+i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
+                   struct codec_info *ci, void *data)
+{
+       int err, in = 0, out = 0;
+       struct transfer_info *tmp;
+       struct i2sbus_dev *i2sdev = soundbus_dev_to_i2sbus_dev(dev);
+       struct codec_info_item *cii;
+
+       if (!dev->pcmname || dev->pcmid == -1) {
+               printk(KERN_ERR "i2sbus: pcm name and id must be set!\n");
+               return -EINVAL;
+       }
+
+       list_for_each_entry(cii, &dev->codec_list, list) {
+               if (cii->codec_data == data)
+                       return -EALREADY;
+       }
+
+       if (!ci->transfers || !ci->transfers->formats
+           || !ci->transfers->rates || !ci->usable)
+               return -EINVAL;
+
+       /* we currently code the i2s transfer on the clock, and support only
+        * 32 and 64 */
+       if (ci->bus_factor != 32 && ci->bus_factor != 64)
+               return -EINVAL;
+
+       /* If you want to fix this, you need to keep track of what transport infos
+        * are to be used, which codecs they belong to, and then fix all the
+        * sysclock/busclock stuff above to depend on which is usable */
+       list_for_each_entry(cii, &dev->codec_list, list) {
+               if (cii->codec->sysclock_factor != ci->sysclock_factor) {
+                       printk(KERN_DEBUG
+                              "cannot yet handle multiple different sysclocks!\n");
+                       return -EINVAL;
+               }
+               if (cii->codec->bus_factor != ci->bus_factor) {
+                       printk(KERN_DEBUG
+                              "cannot yet handle multiple different bus clocks!\n");
+                       return -EINVAL;
+               }
+       }
+
+       tmp = ci->transfers;
+       while (tmp->formats && tmp->rates) {
+               if (tmp->transfer_in)
+                       in = 1;
+               else
+                       out = 1;
+               tmp++;
+       }
+
+       cii = kzalloc(sizeof(struct codec_info_item), GFP_KERNEL);
+       if (!cii) {
+               printk(KERN_DEBUG "i2sbus: failed to allocate cii\n");
+               return -ENOMEM;
+       }
+
+       /* use the private data to point to the codec info */
+       cii->sdev = soundbus_dev_get(dev);
+       cii->codec = ci;
+       cii->codec_data = data;
+
+       if (!cii->sdev) {
+               printk(KERN_DEBUG
+                      "i2sbus: failed to get soundbus dev reference\n");
+               err = -ENODEV;
+               goto out_free_cii;
+       }
+
+       if (!try_module_get(THIS_MODULE)) {
+               printk(KERN_DEBUG "i2sbus: failed to get module reference!\n");
+               err = -EBUSY;
+               goto out_put_sdev;
+       }
+
+       if (!try_module_get(ci->owner)) {
+               printk(KERN_DEBUG
+                      "i2sbus: failed to get module reference to codec owner!\n");
+               err = -EBUSY;
+               goto out_put_this_module;
+       }
+
+       if (!dev->pcm) {
+               err = snd_pcm_new(card, dev->pcmname, dev->pcmid, 0, 0,
+                                 &dev->pcm);
+               if (err) {
+                       printk(KERN_DEBUG "i2sbus: failed to create pcm\n");
+                       goto out_put_ci_module;
+               }
+               dev->pcm->dev = &dev->ofdev.dev;
+       }
+
+       /* ALSA yet again sucks.
+        * If it is ever fixed, remove this line. See below. */
+       out = in = 1;
+
+       if (!i2sdev->out.created && out) {
+               if (dev->pcm->card != card) {
+                       /* eh? */
+                       printk(KERN_ERR
+                              "Can't attach same bus to different cards!\n");
+                       err = -EINVAL;
+                       goto out_put_ci_module;
+               }
+               err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1);
+               if (err)
+                       goto out_put_ci_module;
+               snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+                               &i2sbus_playback_ops);
+               i2sdev->out.created = 1;
+       }
+
+       if (!i2sdev->in.created && in) {
+               if (dev->pcm->card != card) {
+                       printk(KERN_ERR
+                              "Can't attach same bus to different cards!\n");
+                       err = -EINVAL;
+                       goto out_put_ci_module;
+               }
+               err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1);
+               if (err)
+                       goto out_put_ci_module;
+               snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE,
+                               &i2sbus_record_ops);
+               i2sdev->in.created = 1;
+       }
+
+       /* so we have to register the pcm after adding any substream
+        * to it because alsa doesn't create the devices for the
+        * substreams when we add them later.
+        * Therefore, force in and out on both busses (above) and
+        * register the pcm now instead of just after creating it.
+        */
+       err = snd_device_register(card, dev->pcm);
+       if (err) {
+               printk(KERN_ERR "i2sbus: error registering new pcm\n");
+               goto out_put_ci_module;
+       }
+       /* no errors any more, so let's add this to our list */
+       list_add(&cii->list, &dev->codec_list);
+
+       dev->pcm->private_data = i2sdev;
+       dev->pcm->private_free = i2sbus_private_free;
+
+       /* well, we really should support scatter/gather DMA */
+       snd_pcm_lib_preallocate_pages_for_all(
+               dev->pcm, SNDRV_DMA_TYPE_DEV,
+               snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)),
+               64 * 1024, 64 * 1024);
+
+       return 0;
+ out_put_ci_module:
+       module_put(ci->owner);
+ out_put_this_module:
+       module_put(THIS_MODULE);
+ out_put_sdev:
+       soundbus_dev_put(dev);
+ out_free_cii:
+       kfree(cii);
+       return err;
+}
+
+void i2sbus_detach_codec(struct soundbus_dev *dev, void *data)
+{
+       struct codec_info_item *cii = NULL, *i;
+
+       list_for_each_entry(i, &dev->codec_list, list) {
+               if (i->codec_data == data) {
+                       cii = i;
+                       break;
+               }
+       }
+       if (cii) {
+               list_del(&cii->list);
+               module_put(cii->codec->owner);
+               kfree(cii);
+       }
+       /* no more codecs, but still a pcm? */
+       if (list_empty(&dev->codec_list) && dev->pcm) {
+               /* the actual cleanup is done by the callback above! */
+               snd_device_free(dev->pcm->card, dev->pcm);
+       }
+}