Re: nvram polling for hardware mixer

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

 



On Mon, 2007-05-28 at 20:28 -0300, Henrique de Moraes Holschuh wrote: 
> On Mon, 28 May 2007, Richard Hughes wrote:
> > 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.
> 
> Thanks.  As usual, idea accepted, and let's work together to get it into
> shape for merging.

Sounds like a plan. Thanks for the quick response, I have fixed up some
of your suggestions in the attached patch.

> > 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.
> 
> The tree you need is at:
> http://repo.or.cz/w/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git input-hotkeys

Ahh, I checked out the wrong branch, thanks. 

> It is being updated as I fix the input-hotkey stuff, so it will get
> rewinded and you will need to rebase from time to time.  BTW, please read
> the patch descriptions, and suggest any changes you might like on how I
> should credit your work on them.

Pahh, don't worry about that too much. If some of the buttons get fixed
and the work gets upstream then this is credit enough :-)

> The above tree is based on 2.6.20, but I will need one based on 2.6.22-rc3
> (input API changed) for merging, so I will forward-port it as soon as it is
> nearly done.  Still, 2.6.20 is what I run right now, so it is my main
> development platform.  You *are* welcome to work in any kernel version you
> see fit, we can change whatever is needed when merging.

There are multiple other reasons to rebase, including backlight class
changes and the dmi matching stuff. I've included these patches in my
mega-patch, feel free to strip them out if you need to stick to an older
kernel.

> > +#include <linux/freezer.h>
> 
> Stay away from the freezer if you can help it.   Can we use the standard
> worker thread interface for this?  Using lax timers if possible, so that we
> are no-tick kernel friendly?  Nothing in thinkpad-acpi (so far) needs strict
> timings for polling or firing, not even the fan watchdog.

I don't think you can do sub-1 second delays using functions like
round_jiffies - and 1Hz is really slow for button presses. I have
converted to using a workqueue tho, and this is 100% nicer code.

> Although we do need at least 2Hz (and 4Hz seems better) for the nvram
> polling.  I don't know if the no-tick friendly timers can do that.
> 
> The open/close hooks should go into the git tree tonight.  When it does,
> please use them to enable/disable polling: if nothing is reading us, we
> don't poll.

Even when we are emitting events using input? I've left the code
polling, but it's trivial if you want to kill the polling until input is
claimed if you want.

> > +/* 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 */
> > +};
> 
> Make this a u32 bitfield, and use 0 and 1 for values, please.

Gotcha, thanks.

> > +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);
> 
> All magic constants need an appropriate #define, please.

Okay.

> > +	/* 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;
> 
> More magic constants...

Sure, I've converted these to #define'd masks, hope this is okay.

> > +	/* 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
> 
> We need to get this through the input driver keycode map, see above git
> tree...

Have done.

> > /*
> >  *  Thinkpad soundcard mixer interface
> >  *  Copyright (c) by Richard Hughes <richard@xxxxxxxxxxx>
> 
> I won't go into this one right now (I also have something like it around,
> but EC based), and I'd rather go over both codes later and merge a mix with
> the best ideas from both.

Sure, I've also added my hardware mixer into my patch and you can pick
and choose what you like.

> But my bandwidth is a bit limited, so I shall go at it after the input work
> is done, and after breaking the driver into various files (which I was
> planning to have done already, but the input work is more useful and far
> more interesting :p).

Yes, the thinkpad_acpi file is *huge*. Breaking this file up gets a big
+1 from me. The current patch also emits buttons for the thinkpad key
and any media keys you might have on the left hand side. I've now mapped
my "ThinkVantage" button to lock screen.

Screenshot here: http://people.freedesktop.org/~hughsient/temp/thinkpad-alsa-mixer.png

So, patch #2 attached for review. Thanks.

 thinkpad_acpi.c |  387 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
 thinkpad_acpi.h |   53 +++++++
 2 files changed, 426 insertions(+), 14 deletions(-)

Richard.

--- ../origin/thinkpad_acpi.h	2007-05-29 10:13:46.000000000 +0100
+++ thinkpad_acpi.h	2007-05-29 16:23:43.000000000 +0100
@@ -40,12 +40,19 @@
 #include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/input.h>
+#include <linux/nvram.h>
 #include <asm/uaccess.h>
 
 #include <linux/dmi.h>
 #include <linux/jiffies.h>
 #include <linux/workqueue.h>
 
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+
 #include <acpi/acpi_drivers.h>
 #include <acpi/acnamesp.h>
 
@@ -172,6 +179,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);
@@ -238,6 +246,46 @@ static struct {
 	u16 input_device_registered:1;
 } tp_features;
 
+/* NVRAM is a simple data structure that is polled and then compared
+ * to a previous version. */
+struct tp_nvram_state_struct {
+	u32 thinkpad_toggle:1;
+	u32 zoom_toggle:1;
+	u32 display_toggle:1;
+	u32 home_toggle:1;
+	u32 search_toggle:1;
+	u32 mail_toggle:1;
+	u32 thinklight_toggle:1;
+	u32 hibernate_toggle:1;
+	u32 display_state:1;
+	u32 brightness_level:3;
+	u32 brightness_toggle:1;
+	u32 volume_level:4;
+	u32 volume_toggle:1;
+	u32 mute_toggle:1;
+};
+
+/* addresses in the nvram that are of interest */
+#define TP_NVRAM_ADDR_BUTTONS1		0x56
+#define TP_NVRAM_ADDR_BUTTONS2		0x57
+#define TP_NVRAM_ADDR_THINKLIGHT	0x58
+#define TP_NVRAM_ADDR_BRIGHTNESS	0x5e
+#define TP_NVRAM_ADDR_MIXER		0x60
+
+#define TP_NVRAM_MASK_BUTTON_HOME	0x01
+#define TP_NVRAM_MASK_BUTTON_SEARCH	0x02
+#define TP_NVRAM_MASK_BUTTON_MAIL	0x04
+#define TP_NVRAM_MASK_BUTTON_THINKPAD	0x08
+#define TP_NVRAM_MASK_BUTTON_ZOOM	0x20
+#define TP_NVRAM_MASK_BUTTON_DISPLAY	0x40
+#define TP_NVRAM_MASK_BUTTON_THINKLIGHT	0x10
+#define TP_NVRAM_MASK_BUTTON_BRIGHTNESS	0x20
+#define TP_NVRAM_MASK_BUTTON_MUTE	0x40
+#define TP_NVRAM_MASK_BUTTON_VOLUME	0x80
+
+#define TP_NVRAM_MASK_LEVEL_BRIGHTNESS	0x07
+#define TP_NVRAM_MASK_LEVEL_VOLUME	0x0f
+
 static struct list_head tpacpi_all_drivers;
 
 static struct ibm_init_struct ibms_init[];
@@ -245,6 +293,11 @@ static int set_ibm_param(const char *val
 static int ibm_init(struct ibm_init_struct *iibm);
 static void ibm_exit(struct ibm_struct *ibm);
 
+/*
+ * mixer
+ */
+static void tp_mixer_mute_changed_cb(void);
+static void tp_mixer_volume_level_changed_cb(void);
 
 /*
  * procfs master subdriver
--- ../origin/thinkpad_acpi.c	2007-05-29 10:13:46.000000000 +0100
+++ thinkpad_acpi.c	2007-05-29 16:29:29.000000000 +0100
@@ -519,7 +519,20 @@ static struct platform_device *tpacpi_pd
 static struct class_device *tpacpi_hwmon;
 static struct input_dev *tpacpi_inputdev;
 
+#ifdef CONFIG_PM
+static int thinkpad_acpi_resume(struct platform_device *pdev)
+{
+	/* just re-read the mixer state in case it changed */
+	tp_mixer_mute_changed_cb();
+	tp_mixer_volume_level_changed_cb();
+	return 0;
+}
+#endif
+
 static struct platform_driver tpacpi_pdriver = {
+#ifdef CONFIG_PM
+	.resume		= thinkpad_acpi_resume,
+#endif
 	.driver = {
 		.name = IBM_DRVR_NAME,
 		.owner = THIS_MODULE,
@@ -735,7 +748,7 @@ static u16 hotkey_keycode_map[] = {
 	KEY_FN_F1, KEY_FN_F2, KEY_FN_F3, KEY_FN_F4, KEY_FN_F5, KEY_FN_F6,
 	KEY_FN_F7, KEY_FN_F8, KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_FN_F12,
 	/* backspace/ins/del/home */
-	KEY_FN_BACKSPACE, KEY_FN_INSERT, KEY_FN_DELETE, KEY_FN_HOME
+	KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN
 };
 
 static struct attribute_set *hotkey_dev_attributes;
@@ -896,7 +909,15 @@ static int __init hotkey_init(struct ibm
 			if (hotkey_keycode_map[i] != KEY_RESERVED)
 				set_bit(hotkey_keycode_map[i], tpacpi_inputdev->keybit);
 		}
-
+		/* are we polling nvram? */
+		if (nvram_hz > 0) {
+			set_bit(KEY_COMPOSE, tpacpi_inputdev->keybit); // thinkpad key
+			set_bit(KEY_ZOOM, tpacpi_inputdev->keybit);
+			set_bit(KEY_SCREEN, tpacpi_inputdev->keybit);
+			set_bit(KEY_HOME, tpacpi_inputdev->keybit);
+			set_bit(KEY_SEARCH, tpacpi_inputdev->keybit);
+			set_bit(KEY_MAIL, tpacpi_inputdev->keybit);
+		}
 	}
 
 	return (tp_features.hotkey)? 0 : 1;
@@ -921,6 +942,7 @@ static void hotkey_exit(void)
 
 static void tpacpi_input_send_key(unsigned int keycode)
 {
+	printk(IBM_INFO "Keycode %i\n", keycode);
 	if (keycode != KEY_RESERVED) {
 		input_report_key(tpacpi_inputdev, keycode, 1);
 		input_sync(tpacpi_inputdev);
@@ -2741,11 +2763,9 @@ static struct ibm_struct ecdump_driver_d
 
 static struct backlight_device *ibm_backlight_device;
 
-static struct backlight_properties ibm_backlight_data = {
-        .owner          = THIS_MODULE,
+static struct backlight_ops ibm_backlight_data = {
         .get_brightness = brightness_get,
         .update_status  = brightness_update_status,
-        .max_brightness = 7,
 };
 
 static int __init brightness_init(struct ibm_init_struct *iibm)
@@ -2767,8 +2787,9 @@ static int __init brightness_init(struct
 	}
 	vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n");
 
-	ibm_backlight_device->props->brightness = b;
-	brightness_update_status(ibm_backlight_device);
+	ibm_backlight_device->props.max_brightness = 7;
+	ibm_backlight_device->props.brightness = b;
+	backlight_update_status(ibm_backlight_device);
 
 	return 0;
 }
@@ -2786,9 +2807,9 @@ static void brightness_exit(void)
 static int brightness_update_status(struct backlight_device *bd)
 {
 	return brightness_set(
-		(bd->props->fb_blank == FB_BLANK_UNBLANK &&
-		 bd->props->power == FB_BLANK_UNBLANK) ?
-				bd->props->brightness : 0);
+		(bd->props.fb_blank == FB_BLANK_UNBLANK &&
+		 bd->props.power == FB_BLANK_UNBLANK) ?
+				bd->props.brightness : 0);
 }
 
 static int brightness_get(struct backlight_device *bd)
@@ -2876,16 +2897,36 @@ static struct ibm_struct brightness_driv
  * Volume subdriver
  */
 
+static int ec_volume_read (u8 *volume, u8 *mute)
+{
+	u8 data;
+
+	if (!acpi_ec_read(volume_offset, &data)) {
+		printk(IBM_ERR "Cannot read EC\n");
+		return -ENODEV;
+	}
+
+	/* volume stored in lsn, and mute in 7th bit */
+	if (volume) {
+		*volume = data & 0xf;
+	}
+	if (mute) {
+		*mute = (data & 0x40) > 0;
+	}
+	return 0;
+}
+
 static int volume_read(char *p)
 {
 	int len = 0;
-	u8 level;
+	u8 volume;
+	u8 mute;
 
-	if (!acpi_ec_read(volume_offset, &level)) {
+	if (ec_volume_read(&volume, &mute) < 0) {
 		len += sprintf(p + len, "level:\t\tunreadable\n");
 	} else {
-		len += sprintf(p + len, "level:\t\t%d\n", level & 0xf);
-		len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6));
+		len += sprintf(p + len, "level:\t\t%d\n", volume);
+		len += sprintf(p + len, "mute:\t\t%s\n", onoff(mute, 1));
 		len += sprintf(p + len, "commands:\tup, down, mute\n");
 		len += sprintf(p + len, "commands:\tlevel <level>"
 			       " (<level> is 0-15)\n");
@@ -2894,6 +2935,36 @@ static int volume_read(char *p)
 	return len;
 }
 
+/* if arguments are negative, then state is not altered */
+static int ec_volume_write (int volume, int mute)
+{
+	u8 data;
+
+	if (!acpi_ec_read(volume_offset, &data)) {
+		printk(IBM_ERR "Cannot read EC\n");
+		return -ENODEV;
+	}
+
+	/* volume stored in lsn, and mute in 7th bit */
+	if (volume >= 0) {
+		volume &= 0x0f;
+		data = data & ~TP_NVRAM_MASK_LEVEL_VOLUME;
+		data = data | volume;
+	}
+	if (mute >= 0) {
+		mute &= 0x01;
+		data = data & ~TP_NVRAM_MASK_BUTTON_MUTE;
+		data = data | mute;
+	}
+
+	if (!acpi_ec_write(volume_offset, data)) {
+		printk(IBM_ERR "Cannot write to EC\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
 static int volume_write(char *buf)
 {
 	int cmos_cmd, inc, i;
@@ -3937,6 +4008,209 @@ static struct ibm_struct fan_driver_data
 	.exit = fan_exit,
 };
 
+/*************************************************************************
+ * Hardware mixer support
+ */
+
+static struct snd_card *tp_snd_card;
+struct snd_kcontrol *tp_mixer_kcontrol_volume;
+struct snd_kcontrol *tp_mixer_kcontrol_mute;
+static struct tp_nvram_state_struct tp_nvram_state;
+
+static int mixer_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 mixer_volume_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	u8 level;
+	/* we can't use spin_lock_irq as we are using acpi */
+	if (ec_volume_read(&level, NULL) >= 0)
+		ucontrol->value.integer.value[0] = level;
+	return 0;
+}
+
+static int mixer_volume_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	int change;
+	int volume;
+	volume = ucontrol->value.integer.value[0];
+	change = tp_nvram_state.volume_level != volume;
+	ec_volume_write(volume, -1);
+	return change;
+}
+
+static int mixer_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 mixer_capsrc_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	u8 mute;
+	/* we can't use spin_lock_irq as we are using acpi */
+	if (ec_volume_read(NULL, &mute) >= 0)
+		ucontrol->value.integer.value[0] = mute;
+	return 0;
+}
+
+static int mixer_capsrc_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	int state;
+	state = ucontrol->value.integer.value[0] & 1;
+	ec_volume_write(-1, state);
+	return 1;
+}
+
+static struct snd_kcontrol_new mixer_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 = mixer_volume_info,
+	.get = mixer_volume_get,
+	.put = mixer_volume_put,
+};
+
+static struct snd_kcontrol_new mixer_control_mute = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Master Volume Mute",
+	.index = 0,
+	.info = mixer_capsrc_info,
+	.get = mixer_capsrc_get,
+	.put = mixer_capsrc_put,
+};
+
+
+/*************************************************************************
+ * NVRAM polling
+ */
+
+static void thinkpad_nvram_fire(struct work_struct *work);
+static struct workqueue_struct *thinkpad_nvram_wq;
+static DECLARE_DELAYED_WORK(thinkpad_nvram_work, thinkpad_nvram_fire);
+
+static void thinkpad_read_nvram(struct tp_nvram_state_struct *state)
+{
+	char data[5];
+
+	/* read 5 bytes of nvram */
+	data[0] = nvram_read_byte(TP_NVRAM_ADDR_BUTTONS1);
+	data[1] = nvram_read_byte(TP_NVRAM_ADDR_BUTTONS2);
+	data[2] = nvram_read_byte(TP_NVRAM_ADDR_THINKLIGHT);
+	data[3] = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
+	data[4] = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
+
+	/* clear existing */
+	memset(state, 0, sizeof(struct tp_nvram_state_struct));
+
+	/* process and extract states */
+	state->home_toggle       = (data[0] & TP_NVRAM_MASK_BUTTON_HOME) > 0;
+	state->search_toggle     = (data[0] & TP_NVRAM_MASK_BUTTON_SEARCH) > 0;
+	state->mail_toggle       = (data[0] & TP_NVRAM_MASK_BUTTON_MAIL) > 0;
+	state->thinkpad_toggle   = (data[1] & TP_NVRAM_MASK_BUTTON_THINKPAD) > 0;
+	state->zoom_toggle       = (data[1] & TP_NVRAM_MASK_BUTTON_ZOOM) > 0;
+	state->display_toggle    = (data[1] & TP_NVRAM_MASK_BUTTON_DISPLAY) > 0;
+	state->thinklight_toggle = (data[2] & TP_NVRAM_MASK_BUTTON_THINKLIGHT) > 0;
+	state->brightness_level  = (data[3] & TP_NVRAM_MASK_LEVEL_BRIGHTNESS);
+	state->brightness_toggle = (data[3] & TP_NVRAM_MASK_BUTTON_BRIGHTNESS) > 0;
+	state->volume_level      = (data[4] & TP_NVRAM_MASK_LEVEL_VOLUME);
+	state->mute_toggle       = (data[4] & TP_NVRAM_MASK_BUTTON_MUTE) > 0;
+	state->volume_toggle     = (data[4] & TP_NVRAM_MASK_BUTTON_VOLUME) > 0;
+}
+
+static void tp_brightness_level_changed_cb(int level)
+{
+	printk(IBM_INFO "Brightness level %i\n", level);
+}
+
+static void tp_mixer_mute_changed_cb(void)
+{
+	if (!tp_snd_card) {
+		printk(IBM_INFO "no sound card");
+		return;
+	}
+	/* get ALSA to re-read mixer state */
+	snd_ctl_notify(tp_snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+		       &tp_mixer_kcontrol_mute->id);
+}
+
+static void tp_mixer_volume_level_changed_cb(void)
+{
+	if (!tp_snd_card) {
+		printk(IBM_INFO "no sound card");
+		return;
+	}
+	/* get ALSA to re-read mixer state */
+	snd_ctl_notify(tp_snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+		       &tp_mixer_kcontrol_volume->id);
+}
+
+static void thinkpad_nvram_fire(struct work_struct *work)
+{
+	struct tp_nvram_state_struct nvram_new;
+
+	thinkpad_read_nvram(&nvram_new);
+
+	/* are they the same? */
+	if (memcmp(&nvram_new, &tp_nvram_state, sizeof (struct tp_nvram_state_struct)) == 0)
+		goto out;
+
+	/* brightness has changed */
+	if (nvram_new.brightness_level != tp_nvram_state.brightness_level)
+		tp_brightness_level_changed_cb(nvram_new.brightness_level);
+	
+	/* volume has changed */
+	if (nvram_new.volume_level != tp_nvram_state.volume_level) {
+		/* we have to update this here to avoid racing with ALSA */
+		tp_nvram_state.volume_level = nvram_new.volume_level;
+		tp_mixer_volume_level_changed_cb();
+	}
+
+	/* mute status has changed */
+	if (nvram_new.mute_toggle != tp_nvram_state.mute_toggle) {
+		tp_nvram_state.mute_toggle = nvram_new.mute_toggle;
+		tp_mixer_mute_changed_cb();
+	}
+
+	/* find new button presses that are not acpi events */
+	if (nvram_new.thinkpad_toggle != tp_nvram_state.thinkpad_toggle) {
+		tpacpi_input_send_key(KEY_COMPOSE); /* thinkpad key */
+	} else if (nvram_new.zoom_toggle != tp_nvram_state.zoom_toggle) {
+		tpacpi_input_send_key(KEY_ZOOM);
+	} else if (nvram_new.display_toggle != tp_nvram_state.display_toggle) {
+		tpacpi_input_send_key(KEY_SCREEN);
+	} else if (nvram_new.home_toggle != tp_nvram_state.home_toggle) {
+		tpacpi_input_send_key(KEY_HOME);
+	} else if (nvram_new.search_toggle != tp_nvram_state.search_toggle) {
+		tpacpi_input_send_key(KEY_SEARCH);
+	} else if (nvram_new.mail_toggle != tp_nvram_state.mail_toggle) {
+		tpacpi_input_send_key(KEY_MAIL);
+	}
+
+	/* store the new state */
+	memcpy(&tp_nvram_state, &nvram_new, sizeof (struct tp_nvram_state_struct));
+
+out:
+	/* reschedule another check */
+	queue_delayed_work(thinkpad_nvram_wq, &thinkpad_nvram_work, HZ / nvram_hz);
+}
+
 /****************************************************************************
  ****************************************************************************
  *
@@ -4259,6 +4533,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)
 
@@ -4280,6 +4557,43 @@ IBM_PARAM(brightness);
 IBM_PARAM(volume);
 IBM_PARAM(fan);
 
+static int __devinit mixer_create(void)
+{
+	int err;
+
+	tp_snd_card = snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0);
+	if (tp_snd_card == NULL)
+		return -ENOMEM;
+
+	strcpy(tp_snd_card->mixername, "ThinkPad Mixer");
+
+	/* add volume control */
+	tp_mixer_kcontrol_volume = snd_ctl_new1(&mixer_control_volume, NULL);
+	err = snd_ctl_add(tp_snd_card, tp_mixer_kcontrol_volume);
+	if (err < 0)
+		return err;
+
+	/* Add hardware mute control - we cannot just rely on the ALSA 'soft'
+	   mute support as the thinkpad only unmutes on button press
+	   and is done in hardware. Doing this allows 'soft' and 'hard' mutes */
+	tp_mixer_kcontrol_mute = snd_ctl_new1(&mixer_control_mute, NULL);
+	err = snd_ctl_add(tp_snd_card, tp_mixer_kcontrol_mute);
+	if (err < 0)
+		return err;
+
+	strcpy(tp_snd_card->driver, "ThinkPad");
+	strcpy(tp_snd_card->shortname, "ThinkPad");
+	sprintf(tp_snd_card->longname, "ThinkPad 0");
+
+	err = snd_card_register(tp_snd_card);
+	if (err == 0) {
+		return 0;
+	}
+
+	snd_card_free(tp_snd_card);
+	return err;
+}
+
 static int __init thinkpad_acpi_module_init(void)
 {
 	int ret, i;
@@ -4368,6 +4682,43 @@ static int __init thinkpad_acpi_module_i
 		tp_features.input_device_registered = 1;
 	}
 
+	/* we poll the nvram state to find data we do not get events for */
+	if (nvram_hz > 0) {
+		struct tp_nvram_state_struct nvram_new;
+
+		/* setup workqueue */
+		thinkpad_nvram_wq = create_singlethread_workqueue("klenovonvramd");
+		if (!thinkpad_nvram_wq) {
+			printk(IBM_ERR "failed to create workqueue\n");
+			return -ENOMEM;
+		}
+
+		/* 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(&nvram_new);
+
+		/* store the last state */
+		memcpy(&tp_nvram_state, &nvram_new, sizeof (struct tp_nvram_state_struct));
+
+		ret = mixer_create();
+		if (ret < 0) {
+			printk(IBM_ERR "failed to create mixer\n");
+			return -ENOMEM;
+		}
+
+		/* coldplug */
+		tp_mixer_mute_changed_cb();
+		tp_mixer_volume_level_changed_cb();
+		tp_brightness_level_changed_cb(nvram_new.brightness_level);
+
+		/* start polling after delay */
+		queue_delayed_work(thinkpad_nvram_wq, &thinkpad_nvram_work, HZ / nvram_hz);
+	}
+
 	return 0;
 }
 
@@ -4390,6 +4741,14 @@ static void thinkpad_acpi_module_exit(vo
 			input_free_device(tpacpi_inputdev);
 	}
 
+	if (thinkpad_nvram_wq) {
+		cancel_delayed_work(&thinkpad_nvram_work);
+		destroy_workqueue(thinkpad_nvram_wq);
+	}
+
+	if (tp_snd_card)
+		snd_card_free(tp_snd_card);
+
 	if (tpacpi_hwmon)
 		hwmon_device_unregister(tpacpi_hwmon);
 
-------------------------------------------------------------------------
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