[alsa-devel] [RFC] [Patch 1/2] Add virtual master control
Daniel Jacobowitz
drow at false.org
Fri Mar 21 20:08:55 CET 2008
On Wed, Jan 30, 2008 at 04:27:07PM +0100, Takashi Iwai wrote:
> Because HD-audio is the only user, so far. I don't want to expand the
> core stuff (and kconfig) unless really needed.
I said I'd want this for ice1724 / vt1616, and I finally got time to
do it. What do you think of this patch? I'm happily using it.
From: Daniel Jacobowitz <dan at codesourcery.com>
Move VMASTER from HDA out to core, and enable it for VT1616.
Signed-off-by: Daniel Jacobowitz <dan at codesourcery.com>
diff --git a/include/sound/control.h b/include/sound/control.h
index e79baa6..3dc1291 100644
--- a/include/sound/control.h
+++ b/include/sound/control.h
@@ -169,4 +169,11 @@ int snd_ctl_boolean_mono_info(struct snd_kcontrol *kcontrol,
int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
+/*
+ * virtual master control
+ */
+struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
+ const unsigned int *tlv);
+int snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave);
+
#endif /* __SOUND_CONTROL_H */
diff --git a/sound/core/Kconfig b/sound/core/Kconfig
index 829ca38..b0528c4 100644
--- a/sound/core/Kconfig
+++ b/sound/core/Kconfig
@@ -16,6 +16,9 @@ config SND_RAWMIDI
tristate
depends on SND
+config SND_VMASTER
+ bool
+
config SND_SEQUENCER
tristate "Sequencer support"
depends on SND
diff --git a/sound/core/Makefile b/sound/core/Makefile
index 267039a..da8e685 100644
--- a/sound/core/Makefile
+++ b/sound/core/Makefile
@@ -6,6 +6,7 @@
snd-y := sound.o init.o memory.o info.o control.o misc.o device.o
snd-$(CONFIG_ISA_DMA_API) += isadma.o
snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o info_oss.o
+snd-$(CONFIG_SND_VMASTER) += vmaster.o
snd-pcm-objs := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
pcm_memory.o
diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c
new file mode 100644
index 0000000..7cfd8b8
--- /dev/null
+++ b/sound/core/vmaster.c
@@ -0,0 +1,368 @@
+/*
+ * Virtual master and slave controls
+ *
+ * Copyright (c) 2008 by Takashi Iwai <tiwai at suse.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+/*
+ * a subset of information returned via ctl info callback
+ */
+struct link_ctl_info {
+ int type; /* value type */
+ int count; /* item count */
+ int min_val, max_val; /* min, max values */
+};
+
+/*
+ * link master - this contains a list of slave controls that are
+ * identical types, i.e. info returns the same value type and value
+ * ranges, but may have different number of counts.
+ *
+ * The master control is so far only mono volume/switch for simplicity.
+ * The same value will be applied to all slaves.
+ */
+struct link_master {
+ struct list_head slaves;
+ struct link_ctl_info info;
+ int val; /* the master value */
+};
+
+/*
+ * link slave - this contains a slave control element
+ *
+ * It fakes the control callbacsk with additional attenuation by the
+ * master control. A slave may have either one or two channels.
+ */
+
+struct link_slave {
+ struct list_head list;
+ struct link_master *master;
+ struct link_ctl_info info;
+ int vals[2]; /* current values */
+ struct snd_kcontrol slave; /* the copy of original control entry */
+};
+
+/* get the slave ctl info and save the initial values */
+static int slave_init(struct link_slave *slave)
+{
+ struct snd_ctl_elem_info *uinfo;
+ struct snd_ctl_elem_value *uctl;
+ int err, ch;
+
+ if (slave->info.count)
+ return 0; /* already initialized */
+
+ uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL);
+ if (!uinfo)
+ return -ENOMEM;
+ uinfo->id = slave->slave.id;
+ err = slave->slave.info(&slave->slave, uinfo);
+ if (err < 0) {
+ kfree(uinfo);
+ return err;
+ }
+ slave->info.type = uinfo->type;
+ slave->info.count = uinfo->count;
+ if (slave->info.count > 2 ||
+ (slave->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER &&
+ slave->info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) {
+ snd_printk(KERN_ERR "invalid slave element\n");
+ kfree(uinfo);
+ return -EINVAL;
+ }
+ slave->info.min_val = uinfo->value.integer.min;
+ slave->info.max_val = uinfo->value.integer.max;
+ kfree(uinfo);
+
+ uctl = kmalloc(sizeof(*uctl), GFP_KERNEL);
+ if (!uctl)
+ return -ENOMEM;
+ uctl->id = slave->slave.id;
+ err = slave->slave.get(&slave->slave, uctl);
+ for (ch = 0; ch < slave->info.count; ch++)
+ slave->vals[ch] = uctl->value.integer.value[ch];
+ kfree(uctl);
+ return 0;
+}
+
+/* initialize master volume */
+static int master_init(struct link_master *master)
+{
+ struct link_slave *slave;
+
+ if (master->info.count)
+ return 0; /* already initialized */
+
+ list_for_each_entry(slave, &master->slaves, list) {
+ int err = slave_init(slave);
+ if (err < 0)
+ return err;
+ master->info = slave->info;
+ master->info.count = 1; /* always mono */
+ /* set full volume as default (= no attenuation) */
+ master->val = master->info.max_val;
+ return 0;
+ }
+ return -ENOENT;
+}
+
+static int slave_get_val(struct link_slave *slave,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int err, ch;
+
+ err = slave_init(slave);
+ if (err < 0)
+ return err;
+ for (ch = 0; ch < slave->info.count; ch++)
+ ucontrol->value.integer.value[ch] = slave->vals[ch];
+ return 0;
+}
+
+static int slave_put_val(struct link_slave *slave,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int err, ch, vol;
+
+ err = master_init(slave->master);
+ if (err < 0)
+ return err;
+
+ switch (slave->info.type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ for (ch = 0; ch < slave->info.count; ch++)
+ ucontrol->value.integer.value[ch] &=
+ !!slave->master->val;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ for (ch = 0; ch < slave->info.count; ch++) {
+ /* max master volume is supposed to be 0 dB */
+ vol = ucontrol->value.integer.value[ch];
+ vol += slave->master->val - slave->master->info.max_val;
+ if (vol < slave->info.min_val)
+ vol = slave->info.min_val;
+ else if (vol > slave->info.max_val)
+ vol = slave->info.max_val;
+ ucontrol->value.integer.value[ch] = vol;
+ }
+ break;
+ }
+ return slave->slave.put(&slave->slave, ucontrol);
+}
+
+/*
+ * ctl callbacks for slaves
+ */
+static int slave_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+ return slave->slave.info(&slave->slave, uinfo);
+}
+
+static int slave_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+ return slave_get_val(slave, ucontrol);
+}
+
+static int slave_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+ int err, ch, changed = 0;
+
+ err = slave_init(slave);
+ if (err < 0)
+ return err;
+ for (ch = 0; ch < slave->info.count; ch++) {
+ if (slave->vals[ch] != ucontrol->value.integer.value[ch]) {
+ changed = 1;
+ slave->vals[ch] = ucontrol->value.integer.value[ch];
+ }
+ }
+ if (!changed)
+ return 0;
+ return slave_put_val(slave, ucontrol);
+}
+
+static int slave_tlv_cmd(struct snd_kcontrol *kcontrol,
+ int op_flag, unsigned int size,
+ unsigned int __user *tlv)
+{
+ struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+ /* FIXME: this assumes that the max volume is 0 dB */
+ return slave->slave.tlv.c(&slave->slave, op_flag, size, tlv);
+}
+
+static void slave_free(struct snd_kcontrol *kcontrol)
+{
+ struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+ if (slave->slave.private_free)
+ slave->slave.private_free(&slave->slave);
+ if (slave->master)
+ list_del(&slave->list);
+ kfree(slave);
+}
+
+/*
+ * Add a slave control to the group with the given master control
+ *
+ * All slaves must be the same type (returning the same information
+ * via info callback). The fucntion doesn't check it, so it's your
+ * responsibility.
+ *
+ * Also, some additional limitations:
+ * - at most two channels
+ * - logarithmic volume control (dB level), no linear volume
+ * - master can only attenuate the volume, no gain
+ */
+int snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave)
+{
+ struct link_master *master_link = snd_kcontrol_chip(master);
+ struct link_slave *srec;
+
+ srec = kzalloc(sizeof(*srec) +
+ slave->count * sizeof(*slave->vd), GFP_KERNEL);
+ if (!srec)
+ return -ENOMEM;
+ srec->slave = *slave;
+ memcpy(srec->slave.vd, slave->vd, slave->count * sizeof(*slave->vd));
+ srec->master = master_link;
+
+ /* override callbacks */
+ slave->info = slave_info;
+ slave->get = slave_get;
+ slave->put = slave_put;
+ if (slave->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)
+ slave->tlv.c = slave_tlv_cmd;
+ slave->private_data = srec;
+ slave->private_free = slave_free;
+
+ list_add_tail(&srec->list, &master_link->slaves);
+ return 0;
+}
+
+EXPORT_SYMBOL(snd_ctl_add_slave);
+
+/*
+ * ctl callbacks for master controls
+ */
+static int master_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct link_master *master = snd_kcontrol_chip(kcontrol);
+ int ret;
+
+ ret = master_init(master);
+ if (ret < 0)
+ return ret;
+ uinfo->type = master->info.type;
+ uinfo->count = master->info.count;
+ uinfo->value.integer.min = master->info.min_val;
+ uinfo->value.integer.max = master->info.max_val;
+ return 0;
+}
+
+static int master_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct link_master *master = snd_kcontrol_chip(kcontrol);
+ int err = master_init(master);
+ if (err < 0)
+ return err;
+ ucontrol->value.integer.value[0] = master->val;
+ return 0;
+}
+
+static int master_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct link_master *master = snd_kcontrol_chip(kcontrol);
+ struct link_slave *slave;
+ struct snd_ctl_elem_value *uval;
+ int err, old_val;
+
+ err = master_init(master);
+ if (err < 0)
+ return err;
+ old_val = master->val;
+ if (ucontrol->value.integer.value[0] == old_val)
+ return 0;
+
+ uval = kmalloc(sizeof(*uval), GFP_KERNEL);
+ if (!uval)
+ return -ENOMEM;
+ list_for_each_entry(slave, &master->slaves, list) {
+ master->val = old_val;
+ uval->id = slave->slave.id;
+ slave_get_val(slave, uval);
+ master->val = ucontrol->value.integer.value[0];
+ slave_put_val(slave, uval);
+ }
+ kfree(uval);
+ return 1;
+}
+
+static void master_free(struct snd_kcontrol *kcontrol)
+{
+ struct link_master *master = snd_kcontrol_chip(kcontrol);
+ struct link_slave *slave;
+
+ list_for_each_entry(slave, &master->slaves, list)
+ slave->master = NULL;
+ kfree(master);
+}
+
+
+/*
+ * Create a virtual master control with the given name
+ */
+struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
+ const unsigned int *tlv)
+{
+ struct link_master *master;
+ struct snd_kcontrol *kctl;
+ struct snd_kcontrol_new knew;
+
+ memset(&knew, 0, sizeof(knew));
+ knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ knew.name = name;
+ knew.info = master_info;
+
+ master = kzalloc(sizeof(*master), GFP_KERNEL);
+ if (!master)
+ return NULL;
+ INIT_LIST_HEAD(&master->slaves);
+
+ kctl = snd_ctl_new1(&knew, master);
+ if (!kctl) {
+ kfree(master);
+ return NULL;
+ }
+ /* override some callbacks */
+ kctl->info = master_info;
+ kctl->get = master_get;
+ kctl->put = master_put;
+ kctl->private_free = master_free;
+
+ /* additional (constant) TLV read */
+ if (tlv) {
+ /* FIXME: this assumes that the max volume is 0 dB */
+ kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+ kctl->tlv.p = tlv;
+ }
+ return kctl;
+}
+
+EXPORT_SYMBOL(snd_ctl_make_virtual_master);
diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig
index 75d4fe0..154e0ae 100644
--- a/sound/drivers/Kconfig
+++ b/sound/drivers/Kconfig
@@ -27,6 +27,7 @@ config SND_AC97_CODEC
tristate
select SND_PCM
select AC97_BUS
+ select SND_VMASTER
config SND_DUMMY
tristate "Dummy (/dev/null) soundcard"
diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig
index 812085d..48296d9 100644
--- a/sound/pci/Kconfig
+++ b/sound/pci/Kconfig
@@ -517,6 +517,7 @@ config SND_HDA_INTEL
tristate "Intel HD Audio"
depends on SND
select SND_PCM
+ select SND_VMASTER
help
Say Y here to include support for Intel "High Definition
Audio" (Azalia) motherboard devices.
diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c
index 50c637e..2019602 100644
--- a/sound/pci/ac97/ac97_patch.c
+++ b/sound/pci/ac97/ac97_patch.c
@@ -3312,8 +3312,66 @@ AC97_SINGLE("Downmix LFE and Center to Front", 0x5a, 12, 1, 0),
AC97_SINGLE("Downmix Surround to Front", 0x5a, 11, 1, 0),
};
+static const char *slave_vols_vt1616[] = {
+ "Front Playback Volume",
+ "Surround Playback Volume",
+ "Center Playback Volume",
+ "LFE Playback Volume",
+ NULL
+};
+
+static const char *slave_sws_vt1616[] = {
+ "Front Playback Switch",
+ "Surround Playback Switch",
+ "Center Playback Switch",
+ "LFE Playback Switch",
+ NULL
+};
+
+/* find a mixer control element with the given name */
+static struct snd_kcontrol *snd_ac97_find_mixer_ctl(struct snd_ac97 *ac97,
+ const char *name)
+{
+ struct snd_ctl_elem_id id;
+ memset(&id, 0, sizeof(id));
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(id.name, name);
+ return snd_ctl_find_id(ac97->bus->card, &id);
+}
+
+/* create a virtual master control and add slaves */
+int snd_ac97_add_vmaster(struct snd_ac97 *ac97, char *name,
+ const unsigned int *tlv, const char **slaves)
+{
+ struct snd_kcontrol *kctl;
+ const char **s;
+ int err;
+
+ kctl = snd_ctl_make_virtual_master(name, tlv);
+ if (!kctl)
+ return -ENOMEM;
+ err = snd_ctl_add(ac97->bus->card, kctl);
+ if (err < 0)
+ return err;
+
+ for (s = slaves; *s; s++) {
+ struct snd_kcontrol *sctl;
+
+ sctl = snd_ac97_find_mixer_ctl(ac97, *s);
+ if (!sctl) {
+ snd_printdd("Cannot find slave %s, skipped\n", *s);
+ continue;
+ }
+ err = snd_ctl_add_slave(kctl, sctl);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
static int patch_vt1616_specific(struct snd_ac97 * ac97)
{
+ struct snd_kcontrol *kctl;
int err;
if (snd_ac97_try_bit(ac97, 0x5a, 9))
@@ -3321,6 +3379,24 @@ static int patch_vt1616_specific(struct snd_ac97 * ac97)
return err;
if ((err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[1], ARRAY_SIZE(snd_ac97_controls_vt1616) - 1)) < 0)
return err;
+
+ /* There is already a misnamed master switch. Rename it. */
+ kctl = snd_ac97_find_mixer_ctl(ac97, "Master Playback Volume");
+ if (!kctl)
+ return -EINVAL;
+
+ snd_ac97_rename_vol_ctl(ac97, "Master Playback", "Front Playback");
+
+ err = snd_ac97_add_vmaster(ac97, "Master Playback Volume",
+ kctl->tlv.p, slave_vols_vt1616);
+ if (err < 0)
+ return err;
+
+ err = snd_ac97_add_vmaster(ac97, "Master Playback Switch",
+ NULL, slave_sws_vt1616);
+ if (err < 0)
+ return err;
+
return 0;
}
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
index 9e0d8a1..ab0c726 100644
--- a/sound/pci/hda/Makefile
+++ b/sound/pci/hda/Makefile
@@ -2,7 +2,7 @@ snd-hda-intel-y := hda_intel.o
# since snd-hda-intel is the only driver using hda-codec,
# merge it into a single module although it was originally
# designed to be individual modules
-snd-hda-intel-y += hda_codec.o vmaster.o
+snd-hda-intel-y += hda_codec.o
snd-hda-intel-$(CONFIG_PROC_FS) += hda_proc.o
snd-hda-intel-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o
snd-hda-intel-$(CONFIG_SND_HDA_GENERIC) += hda_generic.o
diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h
index ad0014a..5a859d8 100644
--- a/sound/pci/hda/hda_local.h
+++ b/sound/pci/hda/hda_local.h
@@ -407,11 +407,4 @@ int snd_hda_check_amp_list_power(struct hda_codec *codec,
hda_nid_t nid);
#endif /* CONFIG_SND_HDA_POWER_SAVE */
-/*
- * virtual master control
- */
-struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
- const unsigned int *tlv);
-int snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave);
-
#endif /* __SOUND_HDA_LOCAL_H */
diff --git a/sound/pci/hda/vmaster.c b/sound/pci/hda/vmaster.c
deleted file mode 100644
index 2da49d2..0000000
--- a/sound/pci/hda/vmaster.c
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Virtual master and slave controls
- *
- * Copyright (c) 2008 by Takashi Iwai <tiwai at suse.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, version 2.
- *
- */
-
-#include <linux/slab.h>
-#include <sound/core.h>
-#include <sound/control.h>
-
-/*
- * a subset of information returned via ctl info callback
- */
-struct link_ctl_info {
- int type; /* value type */
- int count; /* item count */
- int min_val, max_val; /* min, max values */
-};
-
-/*
- * link master - this contains a list of slave controls that are
- * identical types, i.e. info returns the same value type and value
- * ranges, but may have different number of counts.
- *
- * The master control is so far only mono volume/switch for simplicity.
- * The same value will be applied to all slaves.
- */
-struct link_master {
- struct list_head slaves;
- struct link_ctl_info info;
- int val; /* the master value */
-};
-
-/*
- * link slave - this contains a slave control element
- *
- * It fakes the control callbacsk with additional attenuation by the
- * master control. A slave may have either one or two channels.
- */
-
-struct link_slave {
- struct list_head list;
- struct link_master *master;
- struct link_ctl_info info;
- int vals[2]; /* current values */
- struct snd_kcontrol slave; /* the copy of original control entry */
-};
-
-/* get the slave ctl info and save the initial values */
-static int slave_init(struct link_slave *slave)
-{
- struct snd_ctl_elem_info *uinfo;
- struct snd_ctl_elem_value *uctl;
- int err, ch;
-
- if (slave->info.count)
- return 0; /* already initialized */
-
- uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL);
- if (!uinfo)
- return -ENOMEM;
- uinfo->id = slave->slave.id;
- err = slave->slave.info(&slave->slave, uinfo);
- if (err < 0) {
- kfree(uinfo);
- return err;
- }
- slave->info.type = uinfo->type;
- slave->info.count = uinfo->count;
- if (slave->info.count > 2 ||
- (slave->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER &&
- slave->info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) {
- snd_printk(KERN_ERR "invalid slave element\n");
- kfree(uinfo);
- return -EINVAL;
- }
- slave->info.min_val = uinfo->value.integer.min;
- slave->info.max_val = uinfo->value.integer.max;
- kfree(uinfo);
-
- uctl = kmalloc(sizeof(*uctl), GFP_KERNEL);
- if (!uctl)
- return -ENOMEM;
- uctl->id = slave->slave.id;
- err = slave->slave.get(&slave->slave, uctl);
- for (ch = 0; ch < slave->info.count; ch++)
- slave->vals[ch] = uctl->value.integer.value[ch];
- kfree(uctl);
- return 0;
-}
-
-/* initialize master volume */
-static int master_init(struct link_master *master)
-{
- struct link_slave *slave;
-
- if (master->info.count)
- return 0; /* already initialized */
-
- list_for_each_entry(slave, &master->slaves, list) {
- int err = slave_init(slave);
- if (err < 0)
- return err;
- master->info = slave->info;
- master->info.count = 1; /* always mono */
- /* set full volume as default (= no attenuation) */
- master->val = master->info.max_val;
- return 0;
- }
- return -ENOENT;
-}
-
-static int slave_get_val(struct link_slave *slave,
- struct snd_ctl_elem_value *ucontrol)
-{
- int err, ch;
-
- err = slave_init(slave);
- if (err < 0)
- return err;
- for (ch = 0; ch < slave->info.count; ch++)
- ucontrol->value.integer.value[ch] = slave->vals[ch];
- return 0;
-}
-
-static int slave_put_val(struct link_slave *slave,
- struct snd_ctl_elem_value *ucontrol)
-{
- int err, ch, vol;
-
- err = master_init(slave->master);
- if (err < 0)
- return err;
-
- switch (slave->info.type) {
- case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
- for (ch = 0; ch < slave->info.count; ch++)
- ucontrol->value.integer.value[ch] &=
- !!slave->master->val;
- break;
- case SNDRV_CTL_ELEM_TYPE_INTEGER:
- for (ch = 0; ch < slave->info.count; ch++) {
- /* max master volume is supposed to be 0 dB */
- vol = ucontrol->value.integer.value[ch];
- vol += slave->master->val - slave->master->info.max_val;
- if (vol < slave->info.min_val)
- vol = slave->info.min_val;
- else if (vol > slave->info.max_val)
- vol = slave->info.max_val;
- ucontrol->value.integer.value[ch] = vol;
- }
- break;
- }
- return slave->slave.put(&slave->slave, ucontrol);
-}
-
-/*
- * ctl callbacks for slaves
- */
-static int slave_info(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
-{
- struct link_slave *slave = snd_kcontrol_chip(kcontrol);
- return slave->slave.info(&slave->slave, uinfo);
-}
-
-static int slave_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct link_slave *slave = snd_kcontrol_chip(kcontrol);
- return slave_get_val(slave, ucontrol);
-}
-
-static int slave_put(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct link_slave *slave = snd_kcontrol_chip(kcontrol);
- int err, ch, changed = 0;
-
- err = slave_init(slave);
- if (err < 0)
- return err;
- for (ch = 0; ch < slave->info.count; ch++) {
- if (slave->vals[ch] != ucontrol->value.integer.value[ch]) {
- changed = 1;
- slave->vals[ch] = ucontrol->value.integer.value[ch];
- }
- }
- if (!changed)
- return 0;
- return slave_put_val(slave, ucontrol);
-}
-
-static int slave_tlv_cmd(struct snd_kcontrol *kcontrol,
- int op_flag, unsigned int size,
- unsigned int __user *tlv)
-{
- struct link_slave *slave = snd_kcontrol_chip(kcontrol);
- /* FIXME: this assumes that the max volume is 0 dB */
- return slave->slave.tlv.c(&slave->slave, op_flag, size, tlv);
-}
-
-static void slave_free(struct snd_kcontrol *kcontrol)
-{
- struct link_slave *slave = snd_kcontrol_chip(kcontrol);
- if (slave->slave.private_free)
- slave->slave.private_free(&slave->slave);
- if (slave->master)
- list_del(&slave->list);
- kfree(slave);
-}
-
-/*
- * Add a slave control to the group with the given master control
- *
- * All slaves must be the same type (returning the same information
- * via info callback). The fucntion doesn't check it, so it's your
- * responsibility.
- *
- * Also, some additional limitations:
- * - at most two channels
- * - logarithmic volume control (dB level), no linear volume
- * - master can only attenuate the volume, no gain
- */
-int snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave)
-{
- struct link_master *master_link = snd_kcontrol_chip(master);
- struct link_slave *srec;
-
- srec = kzalloc(sizeof(*srec) +
- slave->count * sizeof(*slave->vd), GFP_KERNEL);
- if (!srec)
- return -ENOMEM;
- srec->slave = *slave;
- memcpy(srec->slave.vd, slave->vd, slave->count * sizeof(*slave->vd));
- srec->master = master_link;
-
- /* override callbacks */
- slave->info = slave_info;
- slave->get = slave_get;
- slave->put = slave_put;
- if (slave->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)
- slave->tlv.c = slave_tlv_cmd;
- slave->private_data = srec;
- slave->private_free = slave_free;
-
- list_add_tail(&srec->list, &master_link->slaves);
- return 0;
-}
-
-/*
- * ctl callbacks for master controls
- */
-static int master_info(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
-{
- struct link_master *master = snd_kcontrol_chip(kcontrol);
- int ret;
-
- ret = master_init(master);
- if (ret < 0)
- return ret;
- uinfo->type = master->info.type;
- uinfo->count = master->info.count;
- uinfo->value.integer.min = master->info.min_val;
- uinfo->value.integer.max = master->info.max_val;
- return 0;
-}
-
-static int master_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct link_master *master = snd_kcontrol_chip(kcontrol);
- int err = master_init(master);
- if (err < 0)
- return err;
- ucontrol->value.integer.value[0] = master->val;
- return 0;
-}
-
-static int master_put(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct link_master *master = snd_kcontrol_chip(kcontrol);
- struct link_slave *slave;
- struct snd_ctl_elem_value *uval;
- int err, old_val;
-
- err = master_init(master);
- if (err < 0)
- return err;
- old_val = master->val;
- if (ucontrol->value.integer.value[0] == old_val)
- return 0;
-
- uval = kmalloc(sizeof(*uval), GFP_KERNEL);
- if (!uval)
- return -ENOMEM;
- list_for_each_entry(slave, &master->slaves, list) {
- master->val = old_val;
- uval->id = slave->slave.id;
- slave_get_val(slave, uval);
- master->val = ucontrol->value.integer.value[0];
- slave_put_val(slave, uval);
- }
- kfree(uval);
- return 1;
-}
-
-static void master_free(struct snd_kcontrol *kcontrol)
-{
- struct link_master *master = snd_kcontrol_chip(kcontrol);
- struct link_slave *slave;
-
- list_for_each_entry(slave, &master->slaves, list)
- slave->master = NULL;
- kfree(master);
-}
-
-
-/*
- * Create a virtual master control with the given name
- */
-struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
- const unsigned int *tlv)
-{
- struct link_master *master;
- struct snd_kcontrol *kctl;
- struct snd_kcontrol_new knew;
-
- memset(&knew, 0, sizeof(knew));
- knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
- knew.name = name;
- knew.info = master_info;
-
- master = kzalloc(sizeof(*master), GFP_KERNEL);
- if (!master)
- return NULL;
- INIT_LIST_HEAD(&master->slaves);
-
- kctl = snd_ctl_new1(&knew, master);
- if (!kctl) {
- kfree(master);
- return NULL;
- }
- /* override some callbacks */
- kctl->info = master_info;
- kctl->get = master_get;
- kctl->put = master_put;
- kctl->private_free = master_free;
-
- /* additional (constant) TLV read */
- if (tlv) {
- /* FIXME: this assumes that the max volume is 0 dB */
- kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
- kctl->tlv.p = tlv;
- }
- return kctl;
-}
--
Daniel Jacobowitz
CodeSourcery
More information about the Alsa-devel
mailing list