This cleans up the apple onboard audio driver filenames.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
+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
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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 */
+++ /dev/null
-/*
- * 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);
+++ /dev/null
-/*
- * 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 */
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-/*
- 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 */
-};
-
+++ /dev/null
-/*
- * 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);
+++ /dev/null
-/*
- * 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 */
+++ /dev/null
-/*
- * 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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ 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 */
+};
+
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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);
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
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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(¬if->mutex);
+ if (notif->notify)
+ notif->notify(notif->data);
+ mutex_unlock(¬if->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,
+ &_mute_gpio,
+ &_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(¬if->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(¬if->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(¬if->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);
--- /dev/null
+/*
+ * 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(¬if->mutex);
+ if (notif->notify)
+ notif->notify(notif->data);
+ mutex_unlock(¬if->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(¬if->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(¬if->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(¬if->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);
+++ /dev/null
-/*
- * 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);
+++ /dev/null
-/*
- * 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 */
+++ /dev/null
-/*
- * 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);
+++ /dev/null
-/*
- * 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(¬if->mutex);
- if (notif->notify)
- notif->notify(notif->data);
- mutex_unlock(¬if->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,
- &_mute_gpio,
- &_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(¬if->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(¬if->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(¬if->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);
+++ /dev/null
-/*
- * 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(¬if->mutex);
- if (notif->notify)
- notif->notify(notif->data);
- mutex_unlock(¬if->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(¬if->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(¬if->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(¬if->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);
+snd-aoa-fabric-layout-objs += layout.o
+
obj-$(CONFIG_SND_AOA_FABRIC_LAYOUT) += snd-aoa-fabric-layout.o
--- /dev/null
+/*
+ * 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);
+++ /dev/null
-/*
- * 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);
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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+ 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);
+ force = 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 (!force) {
+ 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);
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-/*
- * 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;
- 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);
- force = 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 (!force) {
- 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);
+++ /dev/null
-/*
- * 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 */
+++ /dev/null
-/*
- * 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);
- }
-}
#include <asm/pmac_feature.h>
#include <asm/dbdma.h>
-#include "i2sbus-interface.h"
+#include "interface.h"
#include "../soundbus.h"
struct i2sbus_control {
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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);
+ }
+}