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