Re: ALSA mixer volume control

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

 



Hi Again,

Here's an update of my ALSA patch.  It has a few changes:
- Fixed a bug which probably made it not work on a lot (if not most) systems
- Made the ALSA control read-only by default since a lot of volume
button handlers have a bad habit of trying to change the volume.  This
can be changed by a module parameter
- Added a kernel config option and code guards.

Still To Do (need help with):
- volume_alsa_notify_change() is not called when the buttons are push.
 I'm not sure how to put it into the new hotkey code.
- Maybe something should be done about the default hotkey mask so that
the volume buttons can be used.

I'm not sure why my last patches didn't get any comments.  Were they
that bad?  Please let me know if this patch is also horrible.

Thanks,
Lorne

On Fri, Jan 30, 2009 at 7:49 PM, Lorne Applebaum
<lorne.applebaum@xxxxxxxxx> wrote:
> Hi Henrique,
>
> Here are the rest of the changes.
>
> One comment/question for these: I created a function
> volume_alsa_notify_change() to be called when that the volume has been
> changed outside of alsa callbacks (to in turn notify software mixers
> to change their sliders, etc).  Since I'm not completely familiar with
> the driver, I'm not sure I placed it everywhere it is needed.  In
> particular, I wonder about putting a call in
> hotkey_compare_and_issue_event.  I put one in there initially, but it
> didn't do anything on my laptop.
>
> Any comments on this or the previous patch are welcome.
>
> Thanks,
> Lorne
>
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index ce09476..d12bd24 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -145,4 +145,14 @@ config THINKPAD_ACPI_HOTKEY_POLL
 	  If you are not sure, say Y here.  The driver enables polling only if
 	  it is strictly necessary to do so.
 
+config THINKPAD_ACPI_ALSA_VOL
+       bool "Create an ALSA volume mixer for speakers"
+       depends on THINKPAD_ACPI && SND
+       default y
+       ---help---
+         Allows the driver to create a ALSA sound mixer to interface with the
+	 speaker volume.
+
+	 If you are not sure, say N here.
+
 endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index b37515d..9220a81 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -80,6 +80,12 @@
 
 #include <linux/pci_ids.h>
 
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_VOL
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#endif
+
 
 /* ThinkPad CMOS commands */
 #define TP_CMOS_VOLUME_DOWN	0
@@ -331,6 +337,15 @@ static int dbg_uwbemul;
 static int tpacpi_uwb_emulstate;
 #endif
 
+/************************************************************************
+ *
+ * Function prototypes for calls needed throughout driver
+ *
+ ************************************************************************/
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_VOL
+static void volume_alsa_notify_change(void);
+#endif
+
 
 /*************************************************************************
  *  Debugging helpers
@@ -6269,6 +6284,246 @@ static struct ibm_struct brightness_driver_data = {
  */
 
 static int volume_offset = 0x30;
+static struct mutex volume_write_mutex;
+static int volume_cmos_set_level_mute(u8 new_level, u8 new_mute);
+
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_VOL
+#define VOLUME_ALSA_DRIVER_NAME "Thinkpad Vol"
+#define VOLUME_ALSA_SHORT_NAME "Speaker"
+#define VOLUME_ALSA_LONG_NAME "Thinkpad Speaker Volume"
+
+static int alsavol_writable;
+
+static struct snd_card * volume_alsa_card;
+static struct snd_ctl_elem_id * volume_alsa_mixer_vol_id;
+static struct snd_ctl_elem_id * volume_alsa_mixer_mute_id;
+
+static void volume_alsa_notify_change(void){
+	if(volume_alsa_mixer_vol_id)
+		snd_ctl_notify(volume_alsa_card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       volume_alsa_mixer_vol_id);
+	if(volume_alsa_mixer_mute_id)
+		snd_ctl_notify(volume_alsa_card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       volume_alsa_mixer_mute_id);
+	return;
+}
+
+/* ALSA Callbacks */
+static int volume_alsa_vol_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 = 15;
+	return 0;
+}
+
+static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	u8 level;
+	if (!acpi_ec_read(volume_offset, &level))
+		return -EIO;
+	
+	ucontrol->value.integer.value[0] = level & 0xf;
+	return 0;
+}
+
+static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u8 level;
+	if (!acpi_ec_read(volume_offset, &level))
+		return -EIO;
+
+	/* ALSA control is anti-mute.  ie., 1=>mute off */
+	ucontrol->value.integer.value[0] = (level & 0x40) ? 0 : 1;
+	return 0;
+}
+
+static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	u8 cur_level;
+	u8 new_level;
+	u8 mute;
+	int changed = 0;
+	if (!acpi_ec_read(volume_offset, &cur_level))
+		return -EIO;
+
+	mute = cur_level & 0x40;
+	cur_level = cur_level & 0xf;
+	new_level = (u8)(ucontrol->value.integer.value[0]);
+	if (cur_level != new_level) {
+		mutex_lock(&volume_write_mutex);
+		if(volume_cmos_set_level_mute(new_level, mute) ||
+		   !acpi_ec_write(volume_offset, new_level + mute)) {
+			mutex_unlock(&volume_write_mutex);
+			return -EIO;
+		}
+		mutex_unlock(&volume_write_mutex);
+		changed = 1;
+	}
+	return changed;
+}
+
+static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u8 cur_mute, new_mute, level;
+	int changed = 0;
+	if (!acpi_ec_read(volume_offset, &cur_mute))
+		return -EIO;
+
+	level = cur_mute & 0xf;
+	cur_mute = cur_mute & 0x40;
+	/* ALSA control is anti-mute.  ie., 1=>mute off */
+	new_mute = (ucontrol->value.integer.value[0]) ? 0 : 0x40;
+	if (cur_mute != new_mute) {
+		mutex_lock(&volume_write_mutex);
+		if(volume_cmos_set_level_mute(level, new_mute) ||
+		   !acpi_ec_write(volume_offset, level + new_mute))	{
+			mutex_unlock(&volume_write_mutex);
+			return -EIO;
+		}
+		mutex_unlock(&volume_write_mutex);
+		changed = 1;
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Playback Volume",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = volume_alsa_vol_info,
+	.get = volume_alsa_vol_get,
+	.put = volume_alsa_vol_put
+};
+
+static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Playback Switch",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = snd_ctl_boolean_mono_info, /* Use builtin ALSA def */
+	.get = volume_alsa_mute_get,
+	.put = volume_alsa_mute_put
+};
+/* end ALSA Callbacks */
+#endif
+
+static int __init volume_init(struct ibm_init_struct *iibm)
+{
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_VOL
+	struct snd_kcontrol * vol_ctl;
+	struct snd_kcontrol * mute_ctl;
+
+	volume_alsa_mixer_vol_id = NULL;
+	volume_alsa_mixer_mute_id = NULL;
+#endif
+
+	vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
+
+	mutex_init(&volume_write_mutex);
+	
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_VOL
+	/* ALSA initializations. */
+	volume_alsa_card = snd_card_new(-1,NULL,THIS_MODULE,0);
+
+	if(!volume_alsa_card){
+		vdbg_printk(TPACPI_DBG_INIT, "Failed to create ALSA card\n");
+		return 0;
+	}
+	
+	strcpy(volume_alsa_card->driver, VOLUME_ALSA_DRIVER_NAME);
+	strcpy(volume_alsa_card->shortname, VOLUME_ALSA_SHORT_NAME);
+	strcpy(volume_alsa_card->longname, VOLUME_ALSA_LONG_NAME);
+	strcpy(volume_alsa_card->mixername, VOLUME_ALSA_DRIVER_NAME);
+
+	
+	/* Create Controls */
+	if(alsavol_writable){
+	  volume_alsa_control_vol.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	  volume_alsa_control_mute.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	}
+	vol_ctl = snd_ctl_new1(&volume_alsa_control_vol,NULL);
+	if(snd_ctl_add(volume_alsa_card,vol_ctl)){
+		vdbg_printk(TPACPI_DBG_INIT, "Failed to create ALSA volume mixer\n");
+		snd_card_free(volume_alsa_card);
+		return 0;
+	}
+
+	mute_ctl = snd_ctl_new1(&volume_alsa_control_mute,NULL);
+	if(snd_ctl_add(volume_alsa_card,mute_ctl)){
+		vdbg_printk(TPACPI_DBG_INIT, "Failed to create ALSA mute mixer\n");
+		snd_card_free(volume_alsa_card);
+		return 0;
+	}
+
+	snd_card_set_dev(volume_alsa_card, &tpacpi_pdev->dev);
+
+	if(snd_card_register(volume_alsa_card)){
+		vdbg_printk(TPACPI_DBG_INIT, "Failed to register ALSA card\n");
+		snd_card_free(volume_alsa_card);
+	}
+
+	volume_alsa_mixer_vol_id = &vol_ctl->id;
+	volume_alsa_mixer_mute_id = &mute_ctl->id;
+#endif
+	return 0;
+}
+
+
+static int volume_cmos_set_level_mute(u8 new_level, u8 new_mute)
+{
+	int cmos_cmd, inc, i;
+	u8 old_level, old_mute;
+
+	if (!acpi_ec_read(volume_offset, &old_level))
+		return -EIO;
+	old_mute = old_level & 0x40;
+	old_level = old_level & 0xf;
+
+	if (new_level != old_level) {
+		/* Also takes care of mute */
+		
+		cmos_cmd = (new_level > old_level) ?
+			TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN;
+		inc = new_level > old_level ? 1 : -1;
+		
+		/* If mute is enabled the first CMOS command will unmute.
+		   Get it over and done with. */
+		if (old_mute && (issue_thinkpad_cmos_command(cmos_cmd)))
+			return -EIO;
+		
+		for (i = old_level; i != new_level; i += inc)
+			if (issue_thinkpad_cmos_command(cmos_cmd))
+				return -EIO;
+
+		/* If mute should still be set.  Fix the unmuting by the
+		   commands */
+		if (new_mute && (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE)))
+			return -EIO;
+		/* The mute possibilities have been taken care of. Can return. */
+		return 0;
+	}
+
+	if (new_mute != old_mute) {
+		/* level doesn't change */
+		
+		cmos_cmd = (new_mute) ?
+			TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP;
+		
+		if (issue_thinkpad_cmos_command(cmos_cmd))
+			return -EIO;
+	}
+	
+	return 0;	
+}
+
 
 static int volume_read(char *p)
 {
@@ -6290,77 +6545,64 @@ static int volume_read(char *p)
 
 static int volume_write(char *buf)
 {
-	int cmos_cmd, inc, i;
-	u8 level, mute;
+	u8 level;
 	int new_level, new_mute;
 	char *cmd;
 
-	while ((cmd = next_cmd(&buf))) {
-		if (!acpi_ec_read(volume_offset, &level))
-			return -EIO;
-		new_mute = mute = level & 0x40;
-		new_level = level = level & 0xf;
-
+	if (!acpi_ec_read(volume_offset, &level))
+		return -EIO;
+	new_mute = level & 0x40;
+	new_level = level & 0xf;
+	
+	while((cmd = next_cmd(&buf))){
 		if (strlencmp(cmd, "up") == 0) {
-			if (mute)
-				new_mute = 0;
-			else
-				new_level = level == 15 ? 15 : level + 1;
+			/* Only change the level if not unmuting */
+			if(!new_mute)
+				new_level = (new_level >= 15) ? 15 : new_level + 1;
+			new_mute = 0;
 		} else if (strlencmp(cmd, "down") == 0) {
-			if (mute)
-				new_mute = 0;
-			else
-				new_level = level == 0 ? 0 : level - 1;
+			/* Only change the level if not unmuting */
+			if(!new_mute)
+				new_level = (new_level <= 0) ? 0 : new_level - 1;
+			new_mute = 0;
 		} else if (sscanf(cmd, "level %d", &new_level) == 1 &&
 			   new_level >= 0 && new_level <= 15) {
-			/* new_level set */
+			/* Level is set but can be changed by commands later in
+			   the buffer */
 		} else if (strlencmp(cmd, "mute") == 0) {
 			new_mute = 0x40;
 		} else
-			return -EINVAL;
-
-		if (new_level != level) {
-			/* mute doesn't change */
-
-			cmos_cmd = (new_level > level) ?
-					TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN;
-			inc = new_level > level ? 1 : -1;
-
-			if (mute && (issue_thinkpad_cmos_command(cmos_cmd) ||
-				     !acpi_ec_write(volume_offset, level)))
-				return -EIO;
-
-			for (i = level; i != new_level; i += inc)
-				if (issue_thinkpad_cmos_command(cmos_cmd) ||
-				    !acpi_ec_write(volume_offset, i + inc))
-					return -EIO;
-
-			if (mute &&
-			    (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) ||
-			     !acpi_ec_write(volume_offset, new_level + mute))) {
-				return -EIO;
-			}
-		}
-
-		if (new_mute != mute) {
-			/* level doesn't change */
-
-			cmos_cmd = (new_mute) ?
-				   TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP;
+		  return -EINVAL;
+	}
 
-			if (issue_thinkpad_cmos_command(cmos_cmd) ||
-			    !acpi_ec_write(volume_offset, level + new_mute))
-				return -EIO;
-		}
+	mutex_lock(&volume_write_mutex);
+	if(volume_cmos_set_level_mute(new_level, new_mute) ||
+	   !acpi_ec_write(volume_offset, new_level + new_mute))
+	{
+		mutex_unlock(&volume_write_mutex);
+		return -EIO;
 	}
+	mutex_unlock(&volume_write_mutex);
+
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_VOL
+	volume_alsa_notify_change();
+#endif
 
 	return 0;
 }
 
+static void volume_exit(void)
+{
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_VOL
+	snd_card_free(volume_alsa_card);
+#endif
+}
+
 static struct ibm_struct volume_driver_data = {
 	.name = "volume",
 	.read = volume_read,
 	.write = volume_write,
+	.exit = volume_exit
 };
 
 /*************************************************************************
@@ -7894,6 +8136,7 @@ static struct ibm_init_struct ibms_init[] __initdata = {
 		.data = &brightness_driver_data,
 	},
 	{
+		.init = volume_init,
 		.data = &volume_driver_data,
 	},
 	{
@@ -7959,6 +8202,13 @@ MODULE_PARM_DESC(hotkey_report_mode,
 		 "used for backwards compatibility with userspace, "
 		 "see documentation");
 
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_VOL
+module_param(alsavol_writable, bool, 0);
+MODULE_PARM_DESC(alsavol_writable,
+		 "When set to 1, allows the ALSA volume control to write "
+		 "the speaker volume rather than simply read it.");
+#endif
+
 #define TPACPI_PARAM(feature) \
 	module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
 	MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
------------------------------------------------------------------------------
Stay on top of everything new and different, both inside and 
around Java (TM) technology - register by April 22, and save
$200 on the JavaOne (SM) conference, June 2-5, 2009, San Francisco.
300 plus technical and hands-on sessions. Register today. 
Use priority code J9JMT32. http://p.sf.net/sfu/p
_______________________________________________
ibm-acpi-devel mailing list
ibm-acpi-devel@xxxxxxxxxxxxxxxxxxxxx
https://lists.sourceforge.net/lists/listinfo/ibm-acpi-devel

[Index of Archives]     [Linux ACPI]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Photo]     [Yosemite Photos]     [Yosemite Advice]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Device Mapper]

  Powered by Linux