Re: Ganged volume

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sat, Oct 14, 2006 at 04:37:37PM -0400, Daniel Jacobowitz wrote:
> Here's an implementation of a ganged volume control.  I suspect it
> could be improved with advice from a maintainer; I'm happy to revise
> it.  It works in every way I've been able to test it.  I suspect plenty
> of other devices could benefit from this; probably even some not AC97
> based, so maybe it ought to move elsewhere, but at least it's a start.

Hello again,

Takashi pointed out that I leaked memory on unload in my last patch.
Here's a version which doesn't.  I'm sure it can be improved further,
but I don't know what to do beyond this.  I'm glad to revise it if
anyone wants to give me suggestions; I'd really like kernel.org to
support my (Shuttle SN25P) volume controls.

---

Implement a ganged volume control for VT1617A.

This patch renamed the existing Master volume controls to Front, and
implements a single control which adjusts all of Front, Surround, Center,
and LFE, allowing uniform volume control.  It is only wired up for the
VT1617A, but should be useful elsewhere.

It also connects the extra vt1616 controls for the vt1617a, which is
necessary to control the rear speakers.

Signed-off-by: Daniel Jacobowitz <drow@xxxxxxxxx>

diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c
index d2994cb..9fe0800 100644
--- a/sound/pci/ac97/ac97_codec.c
+++ b/sound/pci/ac97/ac97_codec.c
@@ -2587,6 +2587,285 @@ int snd_ac97_swap_ctl(struct snd_ac97 *ac97, const char *s1, const char *s2, con
 	return -ENOENT;
 }
 
+static int snd_ac97_gang_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	struct ac97_gang *gang;
+	struct ac97_gang_member *gang_member;
+	int ret;
+
+	/* Just return info for the first member...  */
+	gang = (struct ac97_gang *)kcontrol->private_value;
+	BUG_ON (list_empty(&gang->members));
+	list_for_each_entry(gang_member, &gang->members, list)
+		break;
+	ret = gang_member->orig->info(gang_member->orig, uinfo);
+
+	/* ...except always mono.  */
+	uinfo->count = 1;
+	return ret;
+}
+
+static int snd_ac97_gang_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct ac97_gang *gang;
+
+	gang = (struct ac97_gang *)kcontrol->private_value;
+	ucontrol->value.integer.value[0] = gang->value;
+	return 0;
+}
+
+static int snd_ac97_gang_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct ac97_gang *gang;
+	struct ac97_gang_member *gang_member;
+	int err, ret;
+
+	gang = (struct ac97_gang *)kcontrol->private_value;
+	ret = (gang->value != ucontrol->value.integer.value[0]);
+	gang->value = ucontrol->value.integer.value[0];
+
+	/* Update all the affected controls.  */
+	list_for_each_entry(gang_member, &gang->members, list) {
+		if ((err = gang_member->member->get (gang_member->member, ucontrol)) < 0)
+			return err;
+		if ((err = gang_member->member->put (gang_member->member, ucontrol)) < 0)
+			return err;
+	}
+	return ret;
+}
+
+static void snd_ac97_free_gang(struct snd_kcontrol *kcontrol)
+{
+	struct ac97_gang *gang;
+	struct ac97_gang_member *gang_member, *n;
+
+	gang = (struct ac97_gang *) kcontrol->private_value;
+	list_for_each_entry_safe(gang_member, n, &gang->members, list)
+		kfree(gang_member);
+
+	kfree(gang);
+}
+
+static struct snd_kcontrol *snd_ac97_gang_new(char *name, struct snd_ac97 *ac97)
+{
+	struct snd_kcontrol_new template;
+	struct ac97_gang *gang;
+	struct snd_kcontrol *ret;
+
+	gang = kzalloc(sizeof(struct ac97_gang), GFP_KERNEL);
+	if (gang == NULL)
+		return NULL;
+	INIT_LIST_HEAD(&gang->members);
+	gang->min = gang->max = -1;
+
+	memset(&template, 0, sizeof(template));
+	template.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	template.name = name;
+	template.index = ac97->num;
+	template.private_value = (unsigned long) gang;
+	template.info = snd_ac97_gang_info;
+	template.get = snd_ac97_gang_get;
+	template.put = snd_ac97_gang_put;
+
+	ret = snd_ctl_new1(&template, ac97);
+	if (ret == NULL)
+		kfree(gang);
+	else
+		ret->private_free = snd_ac97_free_gang;
+	return ret;
+}
+
+static int snd_ac97_gang_member_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	struct ac97_gang_member *gang_member;
+
+	gang_member = (struct ac97_gang_member *)kcontrol->private_value;
+	return gang_member->orig->info(gang_member->orig, uinfo);
+}
+
+static int snd_ac97_gang_member_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct ac97_gang_member *gang_member;
+	int i;
+
+	gang_member = (struct ac97_gang_member *)kcontrol->private_value;
+	for (i = 0; i < gang_member->count; i++)
+		ucontrol->value.integer.value[i] = gang_member->values[i];
+	return 0;
+}
+
+static int snd_ac97_gang_member_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct ac97_gang_member *gang_member;
+	int i, ret = 0, err;
+
+	gang_member = (struct ac97_gang_member *)kcontrol->private_value;
+	for (i = 0; i < gang_member->count; i++) {
+		long new_value = ucontrol->value.integer.value[i];
+
+		/* Save the new value.  */
+		ret |= (gang_member->values[i] != new_value);
+		gang_member->values[i] = new_value;
+
+		/* Adjust by the master value (always towards quieter
+		   or muted).  */
+		new_value += gang_member->leader->value;
+		new_value -= gang_member->leader->max;
+
+		/* Clip.  */
+		if (new_value < gang_member->min)
+			new_value = gang_member->min;
+		if (new_value > gang_member->max)
+			new_value = gang_member->max;
+
+		ucontrol->value.integer.value[i] = new_value;
+	}
+	err = gang_member->orig->put(gang_member->orig, ucontrol);
+	return (err < 0) ? err : ret;
+}
+
+static int snd_ac97_gang_add(struct snd_kcontrol *master, struct snd_kcontrol *member)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(master);
+	struct ac97_gang *leader = (struct ac97_gang *)master->private_value;
+	struct ac97_gang_member *gang_member;
+	struct snd_kcontrol *orig_member;
+	struct snd_ctl_elem_value *val;
+	struct snd_ctl_elem_info *info;
+	unsigned int size;
+	int i, err, count;
+	long min, max;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (info == NULL)
+		goto out;
+	if (member->info(member, info) < 0) {
+		kfree (info);
+		goto out;
+	}
+	BUG_ON(info->type != SNDRV_CTL_ELEM_TYPE_BOOLEAN &&
+	       info->type != SNDRV_CTL_ELEM_TYPE_INTEGER);
+	min = info->value.integer.min;
+	max = info->value.integer.max;
+	count = info->count;
+	kfree(info);
+
+	val = kzalloc(sizeof(*val), GFP_KERNEL);
+	if (val == NULL)
+		goto out;
+	val->id = member->id;
+	err = snd_ctl_elem_read(ac97->bus->card, val);
+	if (err < 0)
+		goto out_value;
+
+	size = sizeof(*gang_member) + sizeof(long) * count;
+	gang_member = kzalloc(size, GFP_KERNEL);
+	if (gang_member == NULL)
+		goto out_value;
+
+	orig_member = snd_ctl_new(member, 0);
+	if (orig_member == NULL)
+		goto out_gang;
+
+	list_add_tail(&gang_member->list, &leader->members);
+	if (leader->min == -1) {
+		leader->min = min;
+		leader->max = max;
+	}
+
+	gang_member->leader = leader;
+	gang_member->member = member;
+	gang_member->orig = orig_member;
+	gang_member->min = min;
+	gang_member->max = max;
+	gang_member->count = count;
+
+	for (i = 0; i < count; i++) {
+		gang_member->values[i] = val->value.integer.value[i];
+	}
+	kfree (val);
+
+	member->private_value = (unsigned long)gang_member;
+	member->info = snd_ac97_gang_member_info;
+	member->get = snd_ac97_gang_member_get;
+	member->put = snd_ac97_gang_member_put;
+	return 0;
+
+ out_gang:
+	kfree(gang_member);
+ out_value:
+	kfree(val);
+ out:
+	return -ENOMEM;
+}
+
+int snd_ac97_gang_volume(struct snd_ac97 *ac97)
+{
+	static const char *names[] = {
+		"Front",
+		"Surround",
+		"Center",
+		"LFE"
+	};
+	struct snd_kcontrol *master;
+	int any, err, i;
+
+	/* Is there already a misnamed master switch?  Rename it.  */
+	snd_ac97_rename_vol_ctl(ac97, "Master Playback", "Front Playback");
+
+	/* Create some new ganged controls.  */
+	master = snd_ac97_gang_new("Master Playback Volume", ac97);
+	if (master == NULL)
+		return -ENOMEM;
+	for (i = 0; i < ARRAY_SIZE(names); i++) {
+		struct snd_kcontrol *member;
+		member = ctl_find(ac97, names[i], "Playback Volume");
+		if (member == NULL)
+			continue;
+
+		err = snd_ac97_gang_add(master, member);
+		if (err)
+			return err;
+		any = 1;
+	}
+	if (any) {
+		err = snd_ctl_add(ac97->bus->card, master);
+		if (err)
+			return err;
+	} else
+		snd_ctl_free_one(master);
+
+	any = 0;
+	master = snd_ac97_gang_new("Master Playback Switch", ac97);
+	if (master == NULL)
+		return -ENOMEM;
+	for (i = 0; i < ARRAY_SIZE(names); i++) {
+		struct snd_kcontrol *member;
+		member = ctl_find(ac97, names[i], "Playback Switch");
+		if (member == NULL)
+			continue;
+
+		err = snd_ac97_gang_add(master, member);
+		if (err)
+			return err;
+		any = 1;
+	}
+	if (any) {
+		err = snd_ctl_add(ac97->bus->card, master);
+		if (err)
+			return err;
+	} else
+		snd_ctl_free_one(master);
+
+	return 0;
+}
+
 #if 1
 /* bind hp and master controls instead of using only hp control */
 static int bind_hp_volsw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
diff --git a/sound/pci/ac97/ac97_local.h b/sound/pci/ac97/ac97_local.h
index a6244c7..c1b529d 100644
--- a/sound/pci/ac97/ac97_local.h
+++ b/sound/pci/ac97/ac97_local.h
@@ -56,6 +56,21 @@ struct ac97_enum {
   .get = snd_ac97_get_enum_double, .put = snd_ac97_put_enum_double, \
   .private_value = (unsigned long)&xenum }
 
+/* ganged control */
+struct ac97_gang {
+	struct list_head members;
+	long min, max;
+	long value;
+};
+
+struct ac97_gang_member {
+	struct list_head list;
+	struct ac97_gang *leader;
+	struct snd_kcontrol *member, *orig;
+	long min, max, count;
+	long values[0];
+};
+
 /* ac97_codec.c */
 extern const struct snd_kcontrol_new snd_ac97_controls_3d[];
 extern const struct snd_kcontrol_new snd_ac97_controls_spdif[];
@@ -78,6 +93,8 @@ int snd_ac97_put_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_
 int snd_ac97_update_bits_nolock(struct snd_ac97 *ac97, unsigned short reg,
 				unsigned short mask, unsigned short value);
 
+int snd_ac97_gang_volume(struct snd_ac97 *ac97);
+
 /* ac97_proc.c */
 #ifdef CONFIG_PROC_FS
 void snd_ac97_bus_proc_init(struct snd_ac97_bus * ac97);
diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c
index e813968..025a60e 100644
--- a/sound/pci/ac97/ac97_patch.c
+++ b/sound/pci/ac97/ac97_patch.c
@@ -2797,6 +2797,8 @@ 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;
+	if ((err = snd_ac97_gang_volume(ac97) < 0))
+		return err;
 	return 0;
 }
 
@@ -2821,6 +2823,7 @@ int patch_vt1617a(struct snd_ac97 * ac97)
 	snd_ac97_write_cache(ac97, 0x5c, 0x20);
 	ac97->ext_id |= AC97_EI_SPDIF;	/* force the detection of spdif */
 	ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000;
+	ac97->build_ops = &patch_vt1616_ops;
 	return 0;
 }
 


-- 
Daniel Jacobowitz
CodeSourcery

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys - and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Alsa-devel mailing list
Alsa-devel@xxxxxxxxxxxxxxxxxxxxx
https://lists.sourceforge.net/lists/listinfo/alsa-devel

[Index of Archives]     [ALSA User]     [Linux Audio Users]     [Kernel Archive]     [Asterisk PBX]     [Photo Sharing]     [Linux Sound]     [Video 4 Linux]     [Gimp]     [Yosemite News]

  Powered by Linux