nvram polling for hardware mixer

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

 



Hi,

I've been hacking to make the nvram hardware buttons on thinkpad exposed
as a hardware mixer device. This requires the nvram to be polled in
kernel space, and then examined to see if the volume and mute status has
changed. I have attached code to export a simple mono tuner, but this
needs further testing and integration. I've also prepared a preliminary
patch to add the polling to the thinkpad_acpi driver.

Please review, and make sure I'm on the right lines. I also can't see
the input patches in the ibm-acpi-2.6 tree, so I've done this against
linus. This isn't for merging, it's just a preview for comments.

Thanks.

Richard.

--- origin/thinkpad_acpi.h	2007-05-28 22:59:03.000000000 +0100
+++ thinkpad_acpi.h	2007-05-28 23:26:09.000000000 +0100
@@ -39,6 +39,9 @@
 #include <linux/platform_device.h>
 #include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
+#include <linux/input.h>
+#include <linux/nvram.h>
+#include <linux/freezer.h>
 #include <asm/uaccess.h>
 
 #include <linux/dmi.h>
@@ -169,6 +172,7 @@ static int experimental;
 static u32 dbg_level;
 static int force_load;
 static char *ibm_thinkpad_ec_found;
+static int nvram_hz;
 
 static char* check_dmi_for_ec(void);
 static int thinkpad_acpi_module_init(void);
@@ -234,6 +238,27 @@ static struct {
 	u16 fan_ctrl_status_undef:1;
 } tp_features;
 
+/* NVRAM is a simple data structure that is polled, parsed and then compared
+ * to a previous version.
+ * Any differences are exposed as bit differences in the struct. */
+struct tp_nvram_state_struct {
+	char thinkpad_toggle;   /* ThinkPad button */
+	char zoom_toggle;       /* zoom toggle */
+	char display_toggle;    /* display toggle */
+	char home_toggle;       /* Home button */
+	char search_toggle;     /* Search button */
+	char mail_toggle;       /* Mail button */
+	char thinklight_toggle; /* ThinkLight */
+	char hibernate_toggle;  /* hibernation/suspend toggle */
+	char display_state;     /* display state */
+	char expand_toggle;     /* hv expansion state */
+	char brightness_level;  /* brightness level */
+	char brightness_toggle; /* brightness toggle */
+	char volume_level;      /* volume level */
+	char volume_toggle;     /* volume toggle */
+	char mute_toggle;       /* mute toggle */
+};
+
 static struct list_head tpacpi_all_drivers;
 
 static struct ibm_init_struct ibms_init[];
--- origin/thinkpad_acpi.c	2007-05-28 22:59:03.000000000 +0100
+++ thinkpad_acpi.c	2007-05-28 23:42:19.000000000 +0100
@@ -92,6 +92,11 @@ MODULE_LICENSE("GPL");
 /* Please remove this in year 2009 */
 MODULE_ALIAS("ibm_acpi");
 
+static struct semaphore thread_sem;
+static int thread_should_die;
+static int using_nvram_thread;
+static struct tp_nvram_state_struct last_nvram_state;
+
 #define __unused __attribute__ ((unused))
 
 /****************************************************************************
@@ -3871,6 +3876,131 @@ static struct ibm_struct fan_driver_data
 	.exit = fan_exit,
 };
 
+/*************************************************************************
+ * NVRAM polling
+ */
+
+static void thinkpad_read_nvram (struct tp_nvram_state_struct *state)
+{
+	char data[6];
+
+	/* read 6 bytes of nvram */
+	data[0] = nvram_read_byte(0x56);
+	data[1] = nvram_read_byte(0x57);
+	data[2] = nvram_read_byte(0x58);
+	data[3] = nvram_read_byte(0x59);
+	data[4] = nvram_read_byte(0x5e);
+	data[5] = nvram_read_byte(0x60);
+
+	/* clear existing */
+	memset (state, 0, sizeof (struct tp_nvram_state_struct));
+
+	/* process and extract states */
+	state->home_toggle       = ( data[0] & 0x01);
+	state->search_toggle     = ( data[0] & 0x02) >> 1;
+	state->mail_toggle       = ( data[0] & 0x04) >> 2;
+	state->thinkpad_toggle   = ( data[1] & 0x08) >> 3;
+	state->zoom_toggle       = (~data[1] & 0x20) >> 5;
+	state->display_toggle    = ( data[1] & 0x40) >> 6;
+	state->thinklight_toggle = ( data[2] & 0x10) >> 4;
+	state->expand_toggle     = ( data[3] & 0x10) >> 4;
+	state->brightness_level  = ( data[4] & 0x07);
+	state->brightness_toggle = ( data[4] & 0x20) >> 5;
+	state->volume_level      = ( data[5] & 0x0f);
+	state->volume_toggle     = ( data[5] & 0x80) >> 7;
+	state->mute_toggle       = ( data[5] & 0x40) >> 6;
+}
+
+static void set_mute (int enable)
+{
+	if (enable)
+		printk(IBM_INFO "Mute\n");
+	else
+		printk(IBM_INFO "Unmute\n");
+}
+
+static void set_brightness_level (int level)
+{
+	printk(IBM_INFO "Brightness level %i\n", level);
+}
+
+static void set_volume_level (int level)
+{
+	printk(IBM_INFO "Volume level %i\n", level);
+}
+
+static void emit_keycode (int keycode)
+{
+	printk(IBM_INFO "Keycode %i\n", keycode);
+}
+
+static void thinkpad_thread_poll_nvram(void)
+{
+	struct tp_nvram_state_struct new_nvram_state;
+
+	thinkpad_read_nvram (&new_nvram_state);
+
+	/* are they the same? */
+	if (memcmp (&new_nvram_state, &last_nvram_state, sizeof (struct tp_nvram_state_struct)) == 0)
+		return;
+
+	/* brightness has changed */
+	if (new_nvram_state.brightness_level != last_nvram_state.brightness_level)
+		set_brightness_level (new_nvram_state.brightness_level);
+	
+	/* volume has changed */
+	if (new_nvram_state.volume_level != last_nvram_state.volume_level)
+		set_volume_level (new_nvram_state.volume_level);
+
+	/* mute status has changed */
+	if (new_nvram_state.mute_toggle != last_nvram_state.mute_toggle)
+		set_mute (new_nvram_state.mute_toggle);
+
+	/* find new button presses that are not acpi events */
+	if (new_nvram_state.thinkpad_toggle != last_nvram_state.thinkpad_toggle) {
+		emit_keycode (KEY_COMPOSE); // thinkpad key
+	} else if (new_nvram_state.zoom_toggle != last_nvram_state.zoom_toggle) {
+		emit_keycode (KEY_ZOOM);
+	} else if (new_nvram_state.display_toggle != last_nvram_state.display_toggle) {
+		emit_keycode (KEY_SCREEN);
+	} else if (new_nvram_state.home_toggle != last_nvram_state.home_toggle) {
+		emit_keycode (KEY_HOME);
+	} else if (new_nvram_state.search_toggle != last_nvram_state.search_toggle) {
+		emit_keycode (KEY_SEARCH);
+	} else if (new_nvram_state.mail_toggle != last_nvram_state.mail_toggle) {
+		emit_keycode (KEY_MAIL);
+	}
+
+	/* store the new state */
+	memcpy (&last_nvram_state, &new_nvram_state, sizeof (struct tp_nvram_state_struct));
+}
+
+static int thinkpad_nvram_thread(void *data)
+{
+	daemonize("kthinkpadnvramd");
+	set_user_nice(current, 4);
+	thread_should_die = 0;
+
+	up(&thread_sem);
+
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ / nvram_hz);
+
+		if (thread_should_die)
+			break;
+
+		if (try_to_freeze())
+			continue;
+
+		thinkpad_thread_poll_nvram();
+	}
+	set_user_nice(current, -20);
+	up(&thread_sem);
+	return 0;
+}
+
+
 /****************************************************************************
  ****************************************************************************
  *
@@ -4193,6 +4323,9 @@ module_param(force_load, int, 0);
 static int fan_control_allowed;
 module_param_named(fan_control, fan_control_allowed, int, 0);
 
+static int nvram_hz = 4;
+module_param(nvram_hz, uint, 0400);
+
 #define IBM_PARAM(feature) \
 	module_param_call(feature, set_ibm_param, NULL, NULL, 0)
 
@@ -4278,6 +4411,33 @@ static int __init thinkpad_acpi_module_i
 		}
 	}
 
+	/* we poll the nvram state to find data we do not get events for */
+	if (nvram_hz > 0) {
+		struct tp_nvram_state_struct new_nvram_state;
+		using_nvram_thread = 1;
+
+		/* sanitise to something sane */
+		if (nvram_hz > 10)
+			nvram_hz = 10;
+		printk(IBM_INFO "klenovonvramd checks per second : %d\n", nvram_hz);
+
+		/* read the initial values */
+		thinkpad_read_nvram (&new_nvram_state);
+
+		/* store the last state */
+		memcpy (&last_nvram_state, &new_nvram_state, sizeof (struct tp_nvram_state_struct));
+
+		/* coldplug */
+		set_mute (new_nvram_state.mute_toggle);
+		set_volume_level (new_nvram_state.volume_level);
+		set_brightness_level (new_nvram_state.brightness_level);
+
+		/* create the thread */
+		init_MUTEX_LOCKED(&thread_sem);
+		kernel_thread(thinkpad_nvram_thread, NULL, CLONE_KERNEL);
+		down(&thread_sem);
+	}
+
 	return 0;
 }
 
@@ -4293,6 +4453,12 @@ static void thinkpad_acpi_module_exit(vo
 
 	dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
 
+	/* unload nvram thread */
+	if (using_nvram_thread) {
+		thread_should_die = 1;
+		down(&thread_sem);
+	}
+
 	if (tpacpi_hwmon)
 		hwmon_device_unregister(tpacpi_hwmon);
 
/*
 *  Thinkpad soundcard mixer interface
 *  Copyright (c) by Richard Hughes <richard@xxxxxxxxxxx>
 *
 *   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; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 */

#include <sound/driver.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include <sound/pcm.h>
#include <sound/initval.h>

MODULE_AUTHOR("Richard Hughes <richard@xxxxxxxxxxx>");
MODULE_DESCRIPTION("Thinkpad hardware mixer (NVRAM)");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{ALSA,ThinkPad soundcard}}");

static struct platform_device *platform_device;

struct thinkpad_snd {
	struct snd_card *card;
	spinlock_t mixer_lock;
	int mixer_volume;
};

static int thinkpad_snd_volume_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 = 14;
	return 0;
}
 
static int thinkpad_snd_volume_get(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
	struct thinkpad_snd *thinkpad = snd_kcontrol_chip(kcontrol);

	spin_lock_irq(&thinkpad->mixer_lock);
	ucontrol->value.integer.value[0] = thinkpad->mixer_volume;
	spin_unlock_irq(&thinkpad->mixer_lock);
	return 0;
}

static int thinkpad_snd_volume_put(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
	struct thinkpad_snd *thinkpad = snd_kcontrol_chip(kcontrol);
	int change;
	int volume;

	volume = ucontrol->value.integer.value[0];
	/* sanitise */
	if (volume < 0)
		volume = 0;
	if (volume > 14)
		volume = 14;

	spin_lock_irq(&thinkpad->mixer_lock);
	change = thinkpad->mixer_volume != volume;
	thinkpad->mixer_volume = volume;
	spin_unlock_irq(&thinkpad->mixer_lock);
	return change;
}

static const DECLARE_TLV_DB_SCALE(db_scale_thinkpad, -4500, 30, 0);

static int thinkpad_snd_capsrc_info(struct snd_kcontrol *kcontrol,
				    struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;
	return 0;
}
 
static int thinkpad_snd_capsrc_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
//	struct thinkpad_snd *thinkpad = snd_kcontrol_chip(kcontrol);
	return 0;
}

static int thinkpad_snd_capsrc_put(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
//	struct thinkpad_snd *thinkpad = snd_kcontrol_chip(kcontrol);
	int change = 1;
	int volume;

	volume = ucontrol->value.integer.value[0] & 1;
	return change;
}

static struct snd_kcontrol_new thinkpad_snd_control_volume = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
	.name = "Master Volume",
	.index = 0,
	.info = thinkpad_snd_volume_info,
	.get = thinkpad_snd_volume_get,
	.put = thinkpad_snd_volume_put,
	.tlv = { .p = db_scale_thinkpad }
};

static struct snd_kcontrol_new thinkpad_snd_control_volume_mute = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Master Volume Toggle",
	.index = 0,
	.info = thinkpad_snd_capsrc_info,
	.get = thinkpad_snd_capsrc_get,
	.put = thinkpad_snd_capsrc_put,
};

static int __devinit snd_card_thinkpad_new_mixer(struct thinkpad_snd *thinkpad)
{
	struct snd_card *card = thinkpad->card;
	int err;

	if (!card)
		return -ENOMEM;

	snd_assert(thinkpad != NULL, return -EINVAL);
	spin_lock_init(&thinkpad->mixer_lock);
	strcpy(card->mixername, "ThinkPad Mixer");

	if ((err = snd_ctl_add(card, snd_ctl_new1(&thinkpad_snd_control_volume, thinkpad))) < 0)
		return err;
	if ((err = snd_ctl_add(card, snd_ctl_new1(&thinkpad_snd_control_volume_mute, thinkpad))) < 0)
		return err;
	return 0;
}

static int __devinit thinkpad_snd_probe(struct platform_device *devptr)
{
	struct snd_card *card;
	struct thinkpad_snd *thinkpad;
	int err;
	int dev = devptr->id;

	card = snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE,
			    sizeof(struct thinkpad_snd));
	if (card == NULL)
		return -ENOMEM;

	thinkpad = card->private_data;
	thinkpad->card = card;

	err = snd_card_thinkpad_new_mixer(thinkpad);
	if (err < 0)
		goto __nodev;

	strcpy(card->driver, "ThinkPad");
	strcpy(card->shortname, "ThinkPad");
	sprintf(card->longname, "ThinkPad %i", dev + 1);

	snd_card_set_dev(card, &devptr->dev);

	if ((err = snd_card_register(card)) == 0) {
		platform_set_drvdata(devptr, card);
		return 0;
	}
__nodev:
	snd_card_free(card);
	return err;
}

static int __devexit thinkpad_snd_remove(struct platform_device *devptr)
{
	snd_card_free(platform_get_drvdata(devptr));
	platform_set_drvdata(devptr, NULL);
	return 0;
}

#ifdef CONFIG_PM
static int thinkpad_snd_resume(struct platform_device *pdev)
{
//	struct snd_card *card = platform_get_drvdata(pdev);
//	struct thinkpad_snd *thinkpad = card->private_data;

	/* just re-read the mixer state in case it changed */
	return 0;
}
#endif

#define THINKPAD_SND_DRIVER	"thinkpad_snd"

static struct platform_driver thinkpad_snd_driver = {
	.probe		= thinkpad_snd_probe,
	.remove		= __devexit_p(thinkpad_snd_remove),
#ifdef CONFIG_PM
	.resume		= thinkpad_snd_resume,
#endif
	.driver		= {
		.name	= THINKPAD_SND_DRIVER
	},
};

static void __init_or_module thinkpad_snd_unregister_all(void)
{
	if (!platform_device)
		return;
	printk(KERN_INFO "platform_device_unregister\n");
	platform_device_unregister(platform_device);
	printk(KERN_INFO "platform_driver_unregister\n");
	platform_driver_unregister(&thinkpad_snd_driver);
}

static int __init alsa_card_thinkpad_init(void)
{
	int err;

	err = platform_driver_register(&thinkpad_snd_driver);
	if (err < 0)
		return err;

	platform_device = platform_device_register_simple(THINKPAD_SND_DRIVER,
							  0, NULL, 0);
	if (!platform_get_drvdata(platform_device)) {
		platform_device_unregister(platform_device);
	}
	return 0;
}

static void __exit alsa_card_thinkpad_exit(void)
{
	thinkpad_snd_unregister_all();
}

module_init(alsa_card_thinkpad_init)
module_exit(alsa_card_thinkpad_exit)
-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
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