All of lore.kernel.org
 help / color / mirror / Atom feed
* [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33
@ 2009-12-15 23:51 Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 1/8] thinkpad-acpi: sync input device EV_SW initial state Henrique de Moraes Holschuh
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown; +Cc: linux-acpi, ibm-acpi-devel

Len,

This is the second set of changes to thinkpad-acpi, for the merge
window.  Patch 1 has been reworked as per Dmitry's comments.  The other
patches are new.

Please apply to acpi-test.

Thank you!

Shortlog:

Alexey Dobriyan (1):
      thinkpad-acpi: convert to seq_file

Henrique de Moraes Holschuh (6):
      thinkpad-acpi: log initial state of rfkill switches
      thinkpad-acpi: volume subdriver rewrite
      thinkpad-acpi: support MUTE-only ThinkPads
      thinkpad-acpi: disable volume control
      thinkpad-acpi: basic ALSA mixer support (v2)
      thinkpad-acpi: bump version to 0.24

-- 
  "One disk to rule them all, One disk to find them. One disk to bring
  them all and in the darkness grind them. In the Land of Redmond
  where the shadows lie." -- The Silicon Valley Tarot
  Henrique Holschuh

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/8] thinkpad-acpi: sync input device EV_SW initial state
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
@ 2009-12-15 23:51 ` Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 2/8] thinkpad-acpi: log initial state of rfkill switches Henrique de Moraes Holschuh
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown
  Cc: linux-acpi, ibm-acpi-devel, Henrique de Moraes Holschuh,
	Alan Jenkins, Johannes Berg, Dmitry Torokhov

Before we register the input device, sync the input layer EV_SW state
through a call to input_report_switch(), to avoid issuing a gratuitous
event for the initial state of these switches.

This fixes some annoyances caused by the interaction with rfkill and
EV_SW SW_RFKILL_ALL events.

Reported-by: Kevin Locke <kevin@kevinlocke.name>
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Alan Jenkins <alan-jenkins@tuffmail.co.uk>
Cc: Johannes Berg <johannes@sipsolutions.net>
Cc: Dmitry Torokhov <dtor@mail.ru>
---
 drivers/platform/x86/thinkpad_acpi.c |   14 ++++++++++----
 1 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 53d6c33..3daf349 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -3186,6 +3186,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 	int res, i;
 	int status;
 	int hkeyv;
+	bool radiosw_state  = false;
+	bool tabletsw_state = false;
 
 	unsigned long quirks;
 
@@ -3291,6 +3293,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
 	if (dbg_wlswemul) {
 		tp_features.hotkey_wlsw = 1;
+		radiosw_state = !!tpacpi_wlsw_emulstate;
 		printk(TPACPI_INFO
 			"radio switch emulation enabled\n");
 	} else
@@ -3298,6 +3301,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 	/* Not all thinkpads have a hardware radio switch */
 	if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
 		tp_features.hotkey_wlsw = 1;
+		radiosw_state = !!status;
 		printk(TPACPI_INFO
 			"radio switch found; radios are %s\n",
 			enabled(status, 0));
@@ -3309,11 +3313,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 	/* For X41t, X60t, X61t Tablets... */
 	if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) {
 		tp_features.hotkey_tablet = 1;
+		tabletsw_state = !!(status & TP_HOTKEY_TABLET_MASK);
 		printk(TPACPI_INFO
 			"possible tablet mode switch found; "
 			"ThinkPad in %s mode\n",
-			(status & TP_HOTKEY_TABLET_MASK)?
-				"tablet" : "laptop");
+			(tabletsw_state) ? "tablet" : "laptop");
 		res = add_to_attr_set(hotkey_dev_attributes,
 				&dev_attr_hotkey_tablet_mode.attr);
 	}
@@ -3364,9 +3368,13 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 
 	if (tp_features.hotkey_wlsw) {
 		input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL);
+		input_report_switch(tpacpi_inputdev,
+				    SW_RFKILL_ALL, radiosw_state);
 	}
 	if (tp_features.hotkey_tablet) {
 		input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE);
+		input_report_switch(tpacpi_inputdev,
+				    SW_TABLET_MODE, tabletsw_state);
 	}
 
 	/* Do not issue duplicate brightness change events to
@@ -3433,8 +3441,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 	tpacpi_inputdev->close = &hotkey_inputdev_close;
 
 	hotkey_poll_setup_safe(true);
-	tpacpi_send_radiosw_update();
-	tpacpi_input_send_tabletsw();
 
 	return 0;
 
-- 
1.6.5.4


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/8] thinkpad-acpi: log initial state of rfkill switches
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 1/8] thinkpad-acpi: sync input device EV_SW initial state Henrique de Moraes Holschuh
@ 2009-12-15 23:51 ` Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 3/8] thinkpad-acpi: volume subdriver rewrite Henrique de Moraes Holschuh
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown
  Cc: linux-acpi, ibm-acpi-devel, Henrique de Moraes Holschuh, Josip Rodin

We already log the initial state of the hardware rfkill switch (WLSW),
might as well log the state of the softswitches as well.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Josip Rodin <joy+kernel@entuzijast.net>
---
 drivers/platform/x86/thinkpad_acpi.c |    7 ++++++-
 1 files changed, 6 insertions(+), 1 deletions(-)

diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 3daf349..05714ab 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -1264,6 +1264,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
 	struct tpacpi_rfk *atp_rfk;
 	int res;
 	bool sw_state = false;
+	bool hw_state;
 	int sw_status;
 
 	BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
@@ -1298,7 +1299,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
 			rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
 		}
 	}
-	rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state());
+	hw_state = tpacpi_rfk_check_hwblock_state();
+	rfkill_set_hw_state(atp_rfk->rfkill, hw_state);
 
 	res = rfkill_register(atp_rfk->rfkill);
 	if (res < 0) {
@@ -1311,6 +1313,9 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
 	}
 
 	tpacpi_rfkill_switches[id] = atp_rfk;
+
+	printk(TPACPI_INFO "rfkill switch %s: radio is %sblocked\n",
+		name, (sw_state || hw_state) ? "" : "un");
 	return 0;
 }
 
-- 
1.6.5.4


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/8] thinkpad-acpi: volume subdriver rewrite
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 1/8] thinkpad-acpi: sync input device EV_SW initial state Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 2/8] thinkpad-acpi: log initial state of rfkill switches Henrique de Moraes Holschuh
@ 2009-12-15 23:51 ` Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 4/8] thinkpad-acpi: support MUTE-only ThinkPads Henrique de Moraes Holschuh
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown
  Cc: linux-acpi, ibm-acpi-devel, Henrique de Moraes Holschuh,
	Lorne Applebaum, Matthew Garrett

I don't trust the coupled EC writes and SMI calls the current volume
control code does very much, although it is exactly what the IBM DSDTs
seem to do (they never do more than a single step though).

Change the driver to stop issuing SMIs, and just drive the EC directly
to the desired level (DSDTs seem to confirm this will work even on
very old models like the 570 and 600e/x).

We checkpoint directly to NVRAM (this can be turned off) at
suspend/shutdown/driver unload, which from what I can see in tbp,
should also work on every ThinkPad.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Lorne Applebaum <lorne.applebaum@gmail.com>
Cc: Matthew Garrett <mjg@redhat.com>
---
 Documentation/laptops/thinkpad-acpi.txt |   22 ++-
 drivers/platform/x86/thinkpad_acpi.c    |  340 +++++++++++++++++++++++++------
 2 files changed, 299 insertions(+), 63 deletions(-)

diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt
index f5056c7..96687d0 100644
--- a/Documentation/laptops/thinkpad-acpi.txt
+++ b/Documentation/laptops/thinkpad-acpi.txt
@@ -1092,22 +1092,33 @@ WARNING:
     its level up and down at every change.
 
 
-Volume control -- /proc/acpi/ibm/volume
----------------------------------------
+Volume control
+--------------
+
+procfs: /proc/acpi/ibm/volume
 
-This feature allows volume control on ThinkPad models which don't have
-a hardware volume knob. The available commands are:
+This feature allows volume control on ThinkPad models with a digital
+volume knob, as well as mute/unmute control.  The available commands are:
 
 	echo up   >/proc/acpi/ibm/volume
 	echo down >/proc/acpi/ibm/volume
 	echo mute >/proc/acpi/ibm/volume
 	echo 'level <level>' >/proc/acpi/ibm/volume
 
-The <level> number range is 0 to 15 although not all of them may be
+The <level> number range is 0 to 14 although not all of them may be
 distinct. The unmute the volume after the mute command, use either the
 up or down command (the level command will not unmute the volume).
 The current volume level and mute state is shown in the file.
 
+There are two strategies for volume control.  To select which one
+should be used, use the volume_mode module parameter: volume_mode=1
+selects EC mode, and volume_mode=3 selects EC mode with NVRAM backing
+(so that volume/mute changes are remembered across shutdown/reboot).
+
+The driver will operate in volume_mode=3 by default. If that does not
+work well on your ThinkPad model, please report this to
+ibm-acpi-devel@lists.sourceforge.net.
+
 The ALSA mixer interface to this feature is still missing, but patches
 to add it exist.  That problem should be addressed in the not so
 distant future.
@@ -1376,6 +1387,7 @@ to enable more than one output class, just add their values.
 	0x0008			HKEY event interface, hotkeys
 	0x0010			Fan control
 	0x0020			Backlight brightness
+	0x0040			Audio mixer/volume control
 
 There is also a kernel build option to enable more debugging
 information, which may be necessary to debug driver problems.
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 05714ab..a2f5312 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -231,6 +231,7 @@ enum tpacpi_hkey_event_t {
 #define TPACPI_DBG_HKEY		0x0008
 #define TPACPI_DBG_FAN		0x0010
 #define TPACPI_DBG_BRGHT	0x0020
+#define TPACPI_DBG_MIXER	0x0040
 
 #define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off")
 #define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
@@ -6375,21 +6376,260 @@ static struct ibm_struct brightness_driver_data = {
  * Volume subdriver
  */
 
-static int volume_offset = 0x30;
+/*
+ * IBM ThinkPads have a simple volume controller with MUTE gating.
+ * Very early Lenovo ThinkPads follow the IBM ThinkPad spec.
+ *
+ * Since the *61 series (and probably also the later *60 series), Lenovo
+ * ThinkPads only implement the MUTE gate.
+ *
+ * EC register 0x30
+ *   Bit 6: MUTE (1 mutes sound)
+ *   Bit 3-0: Volume
+ *   Other bits should be zero as far as we know.
+ *
+ * This is also stored in CMOS NVRAM, byte 0x60, bit 6 (MUTE), and
+ * bits 3-0 (volume).  Other bits in NVRAM may have other functions,
+ * such as bit 7 which is used to detect repeated presses of MUTE,
+ * and we leave them unchanged.
+ */
+
+enum {
+	TP_EC_AUDIO = 0x30,
+
+	/* TP_EC_AUDIO bits */
+	TP_EC_AUDIO_MUTESW = 6,
+
+	/* TP_EC_AUDIO bitmasks */
+	TP_EC_AUDIO_LVL_MSK = 0x0F,
+	TP_EC_AUDIO_MUTESW_MSK = (1 << TP_EC_AUDIO_MUTESW),
+
+	/* Maximum volume */
+	TP_EC_VOLUME_MAX = 14,
+};
+
+enum tpacpi_volume_access_mode {
+	TPACPI_VOL_MODE_AUTO = 0,	/* Not implemented yet */
+	TPACPI_VOL_MODE_EC,		/* Pure EC control */
+	TPACPI_VOL_MODE_UCMS_STEP,	/* UCMS step-based control: N/A */
+	TPACPI_VOL_MODE_ECNVRAM,	/* EC control w/ NVRAM store */
+	TPACPI_VOL_MODE_MAX
+};
+
+static enum tpacpi_volume_access_mode volume_mode =
+	TPACPI_VOL_MODE_MAX;
+
+
+/*
+ * Used to syncronize writers to TP_EC_AUDIO and
+ * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write
+ */
+static struct mutex volume_mutex;
+
+static void tpacpi_volume_checkpoint_nvram(void)
+{
+	u8 lec = 0;
+	u8 b_nvram;
+	const u8 ec_mask = TP_EC_AUDIO_LVL_MSK | TP_EC_AUDIO_MUTESW_MSK;
+
+	if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
+		return;
+
+	vdbg_printk(TPACPI_DBG_MIXER,
+		"trying to checkpoint mixer state to NVRAM...\n");
+
+	if (mutex_lock_killable(&volume_mutex) < 0)
+		return;
+
+	if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec)))
+		goto unlock;
+	lec &= ec_mask;
+	b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
+
+	if (lec != (b_nvram & ec_mask)) {
+		/* NVRAM needs update */
+		b_nvram &= ~ec_mask;
+		b_nvram |= lec;
+		nvram_write_byte(b_nvram, TP_NVRAM_ADDR_MIXER);
+		dbg_printk(TPACPI_DBG_MIXER,
+			   "updated NVRAM mixer status to 0x%02x (0x%02x)\n",
+			   (unsigned int) lec, (unsigned int) b_nvram);
+	} else {
+		vdbg_printk(TPACPI_DBG_MIXER,
+			   "NVRAM mixer status already is 0x%02x (0x%02x)\n",
+			   (unsigned int) lec, (unsigned int) b_nvram);
+	}
+
+unlock:
+	mutex_unlock(&volume_mutex);
+}
+
+static int volume_get_status_ec(u8 *status)
+{
+	u8 s;
+
+	if (!acpi_ec_read(TP_EC_AUDIO, &s))
+		return -EIO;
+
+	*status = s;
+
+	dbg_printk(TPACPI_DBG_MIXER, "status 0x%02x\n", s);
+
+	return 0;
+}
+
+static int volume_get_status(u8 *status)
+{
+	return volume_get_status_ec(status);
+}
+
+static int volume_set_status_ec(const u8 status)
+{
+	if (!acpi_ec_write(TP_EC_AUDIO, status))
+		return -EIO;
+
+	dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status);
+
+	return 0;
+}
+
+static int volume_set_status(const u8 status)
+{
+	return volume_set_status_ec(status);
+}
+
+static int volume_set_mute_ec(const bool mute)
+{
+	int rc;
+	u8 s, n;
+
+	if (mutex_lock_killable(&volume_mutex) < 0)
+		return -EINTR;
+
+	rc = volume_get_status_ec(&s);
+	if (rc)
+		goto unlock;
+
+	n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK :
+		     s & ~TP_EC_AUDIO_MUTESW_MSK;
+
+	if (n != s)
+		rc = volume_set_status_ec(n);
+
+unlock:
+	mutex_unlock(&volume_mutex);
+	return rc;
+}
+
+static int volume_set_mute(const bool mute)
+{
+	dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n",
+		   (mute) ? "" : "un");
+	return volume_set_mute_ec(mute);
+}
+
+static int volume_set_volume_ec(const u8 vol)
+{
+	int rc;
+	u8 s, n;
+
+	if (vol > TP_EC_VOLUME_MAX)
+		return -EINVAL;
+
+	if (mutex_lock_killable(&volume_mutex) < 0)
+		return -EINTR;
+
+	rc = volume_get_status_ec(&s);
+	if (rc)
+		goto unlock;
+
+	n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol;
+
+	if (n != s)
+		rc = volume_set_status_ec(n);
+
+unlock:
+	mutex_unlock(&volume_mutex);
+	return rc;
+}
+
+static int volume_set_volume(const u8 vol)
+{
+	dbg_printk(TPACPI_DBG_MIXER,
+		   "trying to set volume level to %hu\n", vol);
+	return volume_set_volume_ec(vol);
+}
+
+static void volume_suspend(pm_message_t state)
+{
+	tpacpi_volume_checkpoint_nvram();
+}
+
+static void volume_shutdown(void)
+{
+	tpacpi_volume_checkpoint_nvram();
+}
+
+static void volume_exit(void)
+{
+	tpacpi_volume_checkpoint_nvram();
+}
+
+static int __init volume_init(struct ibm_init_struct *iibm)
+{
+	vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
+
+	mutex_init(&volume_mutex);
+
+	/*
+	 * Check for module parameter bogosity, note that we
+	 * init volume_mode to TPACPI_VOL_MODE_MAX in order to be
+	 * able to detect "unspecified"
+	 */
+	if (volume_mode > TPACPI_VOL_MODE_MAX)
+		return -EINVAL;
+
+	if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) {
+		printk(TPACPI_ERR
+			"UCMS step volume mode not implemented, "
+			"please contact %s\n", TPACPI_MAIL);
+		return 1;
+	}
+
+	if (volume_mode == TPACPI_VOL_MODE_AUTO ||
+	    volume_mode == TPACPI_VOL_MODE_MAX) {
+		volume_mode = TPACPI_VOL_MODE_ECNVRAM;
+
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+				"driver auto-selected volume_mode=%d\n",
+				volume_mode);
+	} else {
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+				"using user-supplied volume_mode=%d\n",
+				volume_mode);
+	}
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+			"volume is supported\n");
+
+	return 0;
+}
 
 static int volume_read(char *p)
 {
 	int len = 0;
-	u8 level;
+	u8 status;
 
-	if (!acpi_ec_read(volume_offset, &level)) {
+	if (volume_get_status(&status) < 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",
+				status & TP_EC_AUDIO_LVL_MSK);
+		len += sprintf(p + len, "mute:\t\t%s\n",
+				onoff(status, TP_EC_AUDIO_MUTESW));
 		len += sprintf(p + len, "commands:\tup, down, mute\n");
 		len += sprintf(p + len, "commands:\tlevel <level>"
-			       " (<level> is 0-15)\n");
+			       " (<level> is 0-%d)\n", TP_EC_VOLUME_MAX);
 	}
 
 	return len;
@@ -6397,77 +6637,55 @@ static int volume_read(char *p)
 
 static int volume_write(char *buf)
 {
-	int cmos_cmd, inc, i;
-	u8 level, mute;
-	int new_level, new_mute;
+	u8 s;
+	u8 new_level, new_mute;
+	int l;
 	char *cmd;
+	int rc;
 
-	while ((cmd = next_cmd(&buf))) {
-		if (!acpi_ec_read(volume_offset, &level))
-			return -EIO;
-		new_mute = mute = level & 0x40;
-		new_level = level = level & 0xf;
+	rc = volume_get_status(&s);
+	if (rc < 0)
+		return rc;
 
+	new_level = s & TP_EC_AUDIO_LVL_MSK;
+	new_mute  = s & TP_EC_AUDIO_MUTESW_MSK;
+
+	while ((cmd = next_cmd(&buf))) {
 		if (strlencmp(cmd, "up") == 0) {
-			if (mute)
+			if (new_mute)
 				new_mute = 0;
-			else
-				new_level = level == 15 ? 15 : level + 1;
+			else if (new_level < TP_EC_VOLUME_MAX)
+				new_level++;
 		} else if (strlencmp(cmd, "down") == 0) {
-			if (mute)
+			if (new_mute)
 				new_mute = 0;
-			else
-				new_level = level == 0 ? 0 : level - 1;
-		} else if (sscanf(cmd, "level %d", &new_level) == 1 &&
-			   new_level >= 0 && new_level <= 15) {
-			/* new_level set */
+			else if (new_level > 0)
+				new_level--;
+		} else if (sscanf(cmd, "level %u", &l) == 1 &&
+			   l >= 0 && l <= TP_EC_VOLUME_MAX) {
+				new_level = l;
 		} else if (strlencmp(cmd, "mute") == 0) {
-			new_mute = 0x40;
+			new_mute = TP_EC_AUDIO_MUTESW_MSK;
 		} 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 */
+	tpacpi_disclose_usertask("procfs volume",
+				"%smute and set level to %d\n",
+				new_mute ? "" : "un", new_level);
 
-			cmos_cmd = (new_mute) ?
-				   TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP;
+	rc = volume_set_status(new_mute | new_level);
 
-			if (issue_thinkpad_cmos_command(cmos_cmd) ||
-			    !acpi_ec_write(volume_offset, level + new_mute))
-				return -EIO;
-		}
-	}
-
-	return 0;
+	return (rc == -EINTR) ? -ERESTARTSYS : rc;
 }
 
 static struct ibm_struct volume_driver_data = {
 	.name = "volume",
 	.read = volume_read,
 	.write = volume_write,
+	.exit = volume_exit,
+	.suspend = volume_suspend,
+	.shutdown = volume_shutdown,
 };
 
 /*************************************************************************
@@ -8121,6 +8339,7 @@ static struct ibm_init_struct ibms_init[] __initdata = {
 		.data = &brightness_driver_data,
 	},
 	{
+		.init = volume_init,
 		.data = &volume_driver_data,
 	},
 	{
@@ -8186,6 +8405,11 @@ MODULE_PARM_DESC(hotkey_report_mode,
 		 "used for backwards compatibility with userspace, "
 		 "see documentation");
 
+module_param_named(volume_mode, volume_mode, uint, 0444);
+MODULE_PARM_DESC(volume_mode,
+		 "Selects volume control strategy: "
+		 "0=auto, 1=EC, 2=N/A, 3=EC+NVRAM");
+
 #define TPACPI_PARAM(feature) \
 	module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
 	MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
-- 
1.6.5.4


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 4/8] thinkpad-acpi: support MUTE-only ThinkPads
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
                   ` (2 preceding siblings ...)
  2009-12-15 23:51 ` [PATCH 3/8] thinkpad-acpi: volume subdriver rewrite Henrique de Moraes Holschuh
@ 2009-12-15 23:51 ` Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 5/8] thinkpad-acpi: disable volume control Henrique de Moraes Holschuh
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown
  Cc: linux-acpi, ibm-acpi-devel, Henrique de Moraes Holschuh,
	Lorne Applebaum, Matthew Garrett

Lenovo removed the extra mixer since the T61 and thereabouts.
Newer Lenovo models only have the mute gate function, and leave
the volume control to the HDA mixer.

Until a way to automatically query the firmware about its audio
control capabilities is discovered (there might not be any), use a
white/black list.

We will likely need to ask T60 (old and new model) and Z60/Z61 users
whether they have volume control to populate the black/white list.
Meanwhile, provide a volume_capabilities parameter that can be used to
override the defaults.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Lorne Applebaum <lorne.applebaum@gmail.com>
Cc: Matthew Garrett <mjg@redhat.com>
---
 Documentation/laptops/thinkpad-acpi.txt |   19 ++++-
 drivers/platform/x86/thinkpad_acpi.c    |  159 +++++++++++++++++++++++++-----
 2 files changed, 149 insertions(+), 29 deletions(-)

diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt
index 96687d0..bd87682 100644
--- a/Documentation/laptops/thinkpad-acpi.txt
+++ b/Documentation/laptops/thinkpad-acpi.txt
@@ -1098,18 +1098,31 @@ Volume control
 procfs: /proc/acpi/ibm/volume
 
 This feature allows volume control on ThinkPad models with a digital
-volume knob, as well as mute/unmute control.  The available commands are:
+volume knob (when available, not all models have it), as well as
+mute/unmute control.  The available commands are:
 
 	echo up   >/proc/acpi/ibm/volume
 	echo down >/proc/acpi/ibm/volume
 	echo mute >/proc/acpi/ibm/volume
+	echo unmute >/proc/acpi/ibm/volume
 	echo 'level <level>' >/proc/acpi/ibm/volume
 
 The <level> number range is 0 to 14 although not all of them may be
 distinct. The unmute the volume after the mute command, use either the
-up or down command (the level command will not unmute the volume).
+up or down command (the level command will not unmute the volume), or
+the unmute command.
+
 The current volume level and mute state is shown in the file.
 
+You can use the volume_capabilities parameter to tell the driver
+whether your thinkpad has volume control or mute-only control:
+volume_capabilities=1 for mixers with mute and volume control,
+volume_capabilities=2 for mixers with only mute control.
+
+If the driver misdetects the capabilities for your ThinkPad model,
+please report this to ibm-acpi-devel@lists.sourceforge.net, so that we
+can update the driver.
+
 There are two strategies for volume control.  To select which one
 should be used, use the volume_mode module parameter: volume_mode=1
 selects EC mode, and volume_mode=3 selects EC mode with NVRAM backing
@@ -1450,3 +1463,5 @@ Sysfs interface changelog:
 		is deprecated and marked for removal.
 
 0x020600:	Marker for backlight change event support.
+
+0x020700:	Support for mute-only mixers.
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index a2f5312..4d909d5 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -22,7 +22,7 @@
  */
 
 #define TPACPI_VERSION "0.23"
-#define TPACPI_SYSFS_VERSION 0x020600
+#define TPACPI_SYSFS_VERSION 0x020700
 
 /*
  *  Changelog:
@@ -299,6 +299,7 @@ static struct {
 	u32 fan_ctrl_status_undef:1;
 	u32 second_fan:1;
 	u32 beep_needs_two_args:1;
+	u32 mixer_no_level_control:1;
 	u32 input_device_registered:1;
 	u32 platform_drv_registered:1;
 	u32 platform_drv_attrs_registered:1;
@@ -426,6 +427,12 @@ static void tpacpi_log_usertask(const char * const what)
 	  .ec = TPACPI_MATCH_ANY,		\
 	  .quirks = (__quirk) }
 
+#define TPACPI_QEC_LNV(__id1, __id2, __quirk)	\
+	{ .vendor = PCI_VENDOR_ID_LENOVO,	\
+	  .bios = TPACPI_MATCH_ANY,		\
+	  .ec = TPID(__id1, __id2),		\
+	  .quirks = (__quirk) }
+
 struct tpacpi_quirk {
 	unsigned int vendor;
 	u16 bios;
@@ -6416,9 +6423,17 @@ enum tpacpi_volume_access_mode {
 	TPACPI_VOL_MODE_MAX
 };
 
+enum tpacpi_volume_capabilities {
+	TPACPI_VOL_CAP_AUTO = 0,	/* Use white/blacklist */
+	TPACPI_VOL_CAP_VOLMUTE,		/* Output vol and mute */
+	TPACPI_VOL_CAP_MUTEONLY,	/* Output mute only */
+	TPACPI_VOL_CAP_MAX
+};
+
 static enum tpacpi_volume_access_mode volume_mode =
 	TPACPI_VOL_MODE_MAX;
 
+static enum tpacpi_volume_capabilities volume_capabilities;
 
 /*
  * Used to syncronize writers to TP_EC_AUDIO and
@@ -6430,7 +6445,7 @@ static void tpacpi_volume_checkpoint_nvram(void)
 {
 	u8 lec = 0;
 	u8 b_nvram;
-	const u8 ec_mask = TP_EC_AUDIO_LVL_MSK | TP_EC_AUDIO_MUTESW_MSK;
+	u8 ec_mask;
 
 	if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
 		return;
@@ -6438,6 +6453,11 @@ static void tpacpi_volume_checkpoint_nvram(void)
 	vdbg_printk(TPACPI_DBG_MIXER,
 		"trying to checkpoint mixer state to NVRAM...\n");
 
+	if (tp_features.mixer_no_level_control)
+		ec_mask = TP_EC_AUDIO_MUTESW_MSK;
+	else
+		ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK;
+
 	if (mutex_lock_killable(&volume_mutex) < 0)
 		return;
 
@@ -6575,8 +6595,36 @@ static void volume_exit(void)
 	tpacpi_volume_checkpoint_nvram();
 }
 
+#define TPACPI_VOL_Q_MUTEONLY	0x0001	/* Mute-only control available */
+#define TPACPI_VOL_Q_LEVEL	0x0002  /* Volume control available */
+
+static const struct tpacpi_quirk volume_quirk_table[] __initconst = {
+	/* Whitelist volume level on all IBM by default */
+	{ .vendor = PCI_VENDOR_ID_IBM,
+	  .bios   = TPACPI_MATCH_ANY,
+	  .ec     = TPACPI_MATCH_ANY,
+	  .quirks = TPACPI_VOL_Q_LEVEL },
+
+	/* Lenovo models with volume control (needs confirmation) */
+	TPACPI_QEC_LNV('7', 'C', TPACPI_VOL_Q_LEVEL), /* R60/i */
+	TPACPI_QEC_LNV('7', 'E', TPACPI_VOL_Q_LEVEL), /* R60e/i */
+	TPACPI_QEC_LNV('7', '9', TPACPI_VOL_Q_LEVEL), /* T60/p */
+	TPACPI_QEC_LNV('7', 'B', TPACPI_VOL_Q_LEVEL), /* X60/s */
+	TPACPI_QEC_LNV('7', 'J', TPACPI_VOL_Q_LEVEL), /* X60t */
+	TPACPI_QEC_LNV('7', '7', TPACPI_VOL_Q_LEVEL), /* Z60 */
+	TPACPI_QEC_LNV('7', 'F', TPACPI_VOL_Q_LEVEL), /* Z61 */
+
+	/* Whitelist mute-only on all Lenovo by default */
+	{ .vendor = PCI_VENDOR_ID_LENOVO,
+	  .bios   = TPACPI_MATCH_ANY,
+	  .ec	  = TPACPI_MATCH_ANY,
+	  .quirks = TPACPI_VOL_Q_MUTEONLY }
+};
+
 static int __init volume_init(struct ibm_init_struct *iibm)
 {
+	unsigned long quirks;
+
 	vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
 
 	mutex_init(&volume_mutex);
@@ -6596,6 +6644,36 @@ static int __init volume_init(struct ibm_init_struct *iibm)
 		return 1;
 	}
 
+	if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
+		return -EINVAL;
+
+	quirks = tpacpi_check_quirks(volume_quirk_table,
+				     ARRAY_SIZE(volume_quirk_table));
+
+	switch (volume_capabilities) {
+	case TPACPI_VOL_CAP_AUTO:
+		if (quirks & TPACPI_VOL_Q_MUTEONLY)
+			tp_features.mixer_no_level_control = 1;
+		else if (quirks & TPACPI_VOL_Q_LEVEL)
+			tp_features.mixer_no_level_control = 0;
+		else
+			return 1; /* no mixer */
+		break;
+	case TPACPI_VOL_CAP_VOLMUTE:
+		tp_features.mixer_no_level_control = 0;
+		break;
+	case TPACPI_VOL_CAP_MUTEONLY:
+		tp_features.mixer_no_level_control = 1;
+		break;
+	default:
+		return 1;
+	}
+
+	if (volume_capabilities != TPACPI_VOL_CAP_AUTO)
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+				"using user-supplied volume_capabilities=%d\n",
+				volume_capabilities);
+
 	if (volume_mode == TPACPI_VOL_MODE_AUTO ||
 	    volume_mode == TPACPI_VOL_MODE_MAX) {
 		volume_mode = TPACPI_VOL_MODE_ECNVRAM;
@@ -6610,7 +6688,8 @@ static int __init volume_init(struct ibm_init_struct *iibm)
 	}
 
 	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
-			"volume is supported\n");
+			"mute is supported, volume control is %s\n",
+			str_supported(!tp_features.mixer_no_level_control));
 
 	return 0;
 }
@@ -6623,13 +6702,21 @@ static int volume_read(char *p)
 	if (volume_get_status(&status) < 0) {
 		len += sprintf(p + len, "level:\t\tunreadable\n");
 	} else {
-		len += sprintf(p + len, "level:\t\t%d\n",
-				status & TP_EC_AUDIO_LVL_MSK);
+		if (tp_features.mixer_no_level_control)
+			len += sprintf(p + len, "level:\t\tunsupported\n");
+		else
+			len += sprintf(p + len, "level:\t\t%d\n",
+					status & TP_EC_AUDIO_LVL_MSK);
+
 		len += sprintf(p + len, "mute:\t\t%s\n",
 				onoff(status, TP_EC_AUDIO_MUTESW));
-		len += sprintf(p + len, "commands:\tup, down, mute\n");
-		len += sprintf(p + len, "commands:\tlevel <level>"
+
+		len += sprintf(p + len, "commands:\tunmute, mute\n");
+		if (!tp_features.mixer_no_level_control) {
+			len += sprintf(p + len, "commands:\tup, down\n");
+			len += sprintf(p + len, "commands:\tlevel <level>"
 			       " (<level> is 0-%d)\n", TP_EC_VOLUME_MAX);
+		}
 	}
 
 	return len;
@@ -6651,30 +6738,43 @@ static int volume_write(char *buf)
 	new_mute  = s & TP_EC_AUDIO_MUTESW_MSK;
 
 	while ((cmd = next_cmd(&buf))) {
-		if (strlencmp(cmd, "up") == 0) {
-			if (new_mute)
-				new_mute = 0;
-			else if (new_level < TP_EC_VOLUME_MAX)
-				new_level++;
-		} else if (strlencmp(cmd, "down") == 0) {
-			if (new_mute)
-				new_mute = 0;
-			else if (new_level > 0)
-				new_level--;
-		} else if (sscanf(cmd, "level %u", &l) == 1 &&
-			   l >= 0 && l <= TP_EC_VOLUME_MAX) {
-				new_level = l;
-		} else if (strlencmp(cmd, "mute") == 0) {
+		if (!tp_features.mixer_no_level_control) {
+			if (strlencmp(cmd, "up") == 0) {
+				if (new_mute)
+					new_mute = 0;
+				else if (new_level < TP_EC_VOLUME_MAX)
+					new_level++;
+				continue;
+			} else if (strlencmp(cmd, "down") == 0) {
+				if (new_mute)
+					new_mute = 0;
+				else if (new_level > 0)
+					new_level--;
+				continue;
+			} else if (sscanf(cmd, "level %u", &l) == 1 &&
+				   l >= 0 && l <= TP_EC_VOLUME_MAX) {
+					new_level = l;
+				continue;
+			}
+		}
+		if (strlencmp(cmd, "mute") == 0)
 			new_mute = TP_EC_AUDIO_MUTESW_MSK;
-		} else
+		else if (strlencmp(cmd, "unmute") == 0)
+			new_mute = 0;
+		else
 			return -EINVAL;
 	}
 
-	tpacpi_disclose_usertask("procfs volume",
-				"%smute and set level to %d\n",
-				new_mute ? "" : "un", new_level);
-
-	rc = volume_set_status(new_mute | new_level);
+	if (tp_features.mixer_no_level_control) {
+		tpacpi_disclose_usertask("procfs volume", "%smute\n",
+					new_mute ? "" : "un");
+		rc = volume_set_mute(!!new_mute);
+	} else {
+		tpacpi_disclose_usertask("procfs volume",
+					"%smute and set level to %d\n",
+					new_mute ? "" : "un", new_level);
+		rc = volume_set_status(new_mute | new_level);
+	}
 
 	return (rc == -EINTR) ? -ERESTARTSYS : rc;
 }
@@ -8410,6 +8510,11 @@ MODULE_PARM_DESC(volume_mode,
 		 "Selects volume control strategy: "
 		 "0=auto, 1=EC, 2=N/A, 3=EC+NVRAM");
 
+module_param_named(volume_capabilities, volume_capabilities, uint, 0444);
+MODULE_PARM_DESC(volume_capabilities,
+		 "Selects the mixer capabilites: "
+		 "0=auto, 1=volume and mute, 2=mute only");
+
 #define TPACPI_PARAM(feature) \
 	module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
 	MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
-- 
1.6.5.4


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 5/8] thinkpad-acpi: disable volume control
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
                   ` (3 preceding siblings ...)
  2009-12-15 23:51 ` [PATCH 4/8] thinkpad-acpi: support MUTE-only ThinkPads Henrique de Moraes Holschuh
@ 2009-12-15 23:51 ` Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 6/8] thinkpad-acpi: basic ALSA mixer support (v2) Henrique de Moraes Holschuh
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown
  Cc: linux-acpi, ibm-acpi-devel, Henrique de Moraes Holschuh,
	Lorne Applebaum, Matthew Garrett

Disable volume control by default.  It can be enabled at module load
time by a module parameter (volume_control=1).

The audio control mixer that thinkpad-acpi interacts with is fully
functional without any drivers, and operated by hotkeys.

The idea behind the console audio control is that the human operator
is the only one that can interact with it.  The ThinkVantage suite in
Windows does not allow any software-based overrides, and only does OSD
(on-screen-display) functions.

The Linux driver will, with the addition of the ALSA interface, try to
follow and enforce the ThinkVantage UI design:

The user is supposed to use the keyboard hotkeys to interact with the
console audio control.  The kernel and the desktop environment is
supposed to cooperate to provide proper user feedback through
on-screen-display functions.

Distros are urged to not to enable volume control by default.
Enabling this must be a local admin's decision.  This is the reason
why there is no Kconfig option.

Keep in mind that all ThinkPads have a normal, main mixer (AC97 or
HDA) for regular software-based audio control.  We are not talking
about that mixer here.

Advanced users are, of course, free to enable volume control and do as
they please.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Lorne Applebaum <lorne.applebaum@gmail.com>
Cc: Matthew Garrett <mjg@redhat.com>
---
 Documentation/laptops/thinkpad-acpi.txt |   13 ++++++++
 drivers/platform/x86/thinkpad_acpi.c    |   47 +++++++++++++++++++++++++++---
 2 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt
index bd87682..6a58143 100644
--- a/Documentation/laptops/thinkpad-acpi.txt
+++ b/Documentation/laptops/thinkpad-acpi.txt
@@ -1097,6 +1097,18 @@ Volume control
 
 procfs: /proc/acpi/ibm/volume
 
+NOTE: by default, the volume control interface operates in read-only
+mode, as it is supposed to be used for on-screen-display purposes.
+The read/write mode can be enabled through the use of the
+"volume_control=1" module parameter.
+
+NOTE: distros are urged to not enable volume_control by default, this
+should be done by the local admin only.  The ThinkPad UI is for the
+console audio control to be done through the volume keys only, and for
+the desktop environment to just provide on-screen-display feedback.
+Software volume control should be done only in the main AC97/HDA
+mixer.
+
 This feature allows volume control on ThinkPad models with a digital
 volume knob (when available, not all models have it), as well as
 mute/unmute control.  The available commands are:
@@ -1465,3 +1477,4 @@ Sysfs interface changelog:
 0x020600:	Marker for backlight change event support.
 
 0x020700:	Support for mute-only mixers.
+		Volume control in read-only mode by default.
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 4d909d5..2d74926 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -311,6 +311,7 @@ static struct {
 
 static struct {
 	u16 hotkey_mask_ff:1;
+	u16 volume_ctrl_forbidden:1;
 } tp_warned;
 
 struct thinkpad_id_data {
@@ -6434,6 +6435,7 @@ static enum tpacpi_volume_access_mode volume_mode =
 	TPACPI_VOL_MODE_MAX;
 
 static enum tpacpi_volume_capabilities volume_capabilities;
+static int volume_control_allowed;
 
 /*
  * Used to syncronize writers to TP_EC_AUDIO and
@@ -6449,6 +6451,8 @@ static void tpacpi_volume_checkpoint_nvram(void)
 
 	if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
 		return;
+	if (!volume_control_allowed)
+		return;
 
 	vdbg_printk(TPACPI_DBG_MIXER,
 		"trying to checkpoint mixer state to NVRAM...\n");
@@ -6691,6 +6695,12 @@ static int __init volume_init(struct ibm_init_struct *iibm)
 			"mute is supported, volume control is %s\n",
 			str_supported(!tp_features.mixer_no_level_control));
 
+	printk(TPACPI_INFO
+		"Console audio control enabled, mode: %s\n",
+		(volume_control_allowed) ?
+			"override (read/write)" :
+			"monitor (read only)");
+
 	return 0;
 }
 
@@ -6711,11 +6721,16 @@ static int volume_read(char *p)
 		len += sprintf(p + len, "mute:\t\t%s\n",
 				onoff(status, TP_EC_AUDIO_MUTESW));
 
-		len += sprintf(p + len, "commands:\tunmute, mute\n");
-		if (!tp_features.mixer_no_level_control) {
-			len += sprintf(p + len, "commands:\tup, down\n");
-			len += sprintf(p + len, "commands:\tlevel <level>"
-			       " (<level> is 0-%d)\n", TP_EC_VOLUME_MAX);
+		if (volume_control_allowed) {
+			len += sprintf(p + len, "commands:\tunmute, mute\n");
+			if (!tp_features.mixer_no_level_control) {
+				len += sprintf(p + len,
+					       "commands:\tup, down\n");
+				len += sprintf(p + len,
+					       "commands:\tlevel <level>"
+					       " (<level> is 0-%d)\n",
+					       TP_EC_VOLUME_MAX);
+			}
 		}
 	}
 
@@ -6730,6 +6745,23 @@ static int volume_write(char *buf)
 	char *cmd;
 	int rc;
 
+	/*
+	 * We do allow volume control at driver startup, so that the
+	 * user can set initial state through the volume=... parameter hack.
+	 */
+	if (!volume_control_allowed && tpacpi_lifecycle != TPACPI_LIFE_INIT) {
+		if (unlikely(!tp_warned.volume_ctrl_forbidden)) {
+			tp_warned.volume_ctrl_forbidden = 1;
+			printk(TPACPI_NOTICE
+				"Console audio control in monitor mode, "
+				"changes are not allowed.\n");
+			printk(TPACPI_NOTICE
+				"Use the volume_control=1 module parameter "
+				"to enable volume control\n");
+		}
+		return -EPERM;
+	}
+
 	rc = volume_get_status(&s);
 	if (rc < 0)
 		return rc;
@@ -8515,6 +8547,11 @@ MODULE_PARM_DESC(volume_capabilities,
 		 "Selects the mixer capabilites: "
 		 "0=auto, 1=volume and mute, 2=mute only");
 
+module_param_named(volume_control, volume_control_allowed, bool, 0444);
+MODULE_PARM_DESC(volume_control,
+		 "Enables software override for the console audio "
+		 "control when true");
+
 #define TPACPI_PARAM(feature) \
 	module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
 	MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
-- 
1.6.5.4


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 6/8] thinkpad-acpi: basic ALSA mixer support (v2)
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
                   ` (4 preceding siblings ...)
  2009-12-15 23:51 ` [PATCH 5/8] thinkpad-acpi: disable volume control Henrique de Moraes Holschuh
@ 2009-12-15 23:51 ` Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 7/8] thinkpad-acpi: convert to seq_file Henrique de Moraes Holschuh
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown
  Cc: linux-acpi, ibm-acpi-devel, Henrique de Moraes Holschuh,
	Lorne Applebaum, Matthew Garrett

Add the basic ALSA mixer functionality.  The mixer is event-driven,
and will work fine on IBM ThinkPads.  I expect Lenovo ThinkPads will
cause some trouble with the event interface.

Heavily based on work by Lorne Applebaum <lorne.applebaum@gmail.com>
and ideas from Matthew Garrett <mjg@redhat.com>.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Lorne Applebaum <lorne.applebaum@gmail.com>
Cc: Matthew Garrett <mjg@redhat.com>
---
 Documentation/laptops/thinkpad-acpi.txt |    7 +-
 drivers/platform/x86/thinkpad_acpi.c    |  237 ++++++++++++++++++++++++++++++-
 2 files changed, 239 insertions(+), 5 deletions(-)

diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt
index 6a58143..b4ed30c 100644
--- a/Documentation/laptops/thinkpad-acpi.txt
+++ b/Documentation/laptops/thinkpad-acpi.txt
@@ -1096,6 +1096,7 @@ Volume control
 --------------
 
 procfs: /proc/acpi/ibm/volume
+ALSA: "ThinkPad Console Audio Control", default ID: "ThinkPadEC"
 
 NOTE: by default, the volume control interface operates in read-only
 mode, as it is supposed to be used for on-screen-display purposes.
@@ -1144,9 +1145,8 @@ The driver will operate in volume_mode=3 by default. If that does not
 work well on your ThinkPad model, please report this to
 ibm-acpi-devel@lists.sourceforge.net.
 
-The ALSA mixer interface to this feature is still missing, but patches
-to add it exist.  That problem should be addressed in the not so
-distant future.
+The driver supports the standard ALSA module parameters.  If the ALSA
+mixer is disabled, the driver will disable all volume functionality.
 
 
 Fan control and monitoring: fan speed, fan enable/disable
@@ -1478,3 +1478,4 @@ Sysfs interface changelog:
 
 0x020700:	Support for mute-only mixers.
 		Volume control in read-only mode by default.
+		Marker for ALSA mixer support.
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 2d74926..e0fbe73 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -76,6 +76,10 @@
 #include <linux/jiffies.h>
 #include <linux/workqueue.h>
 
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
 #include <acpi/acpi_drivers.h>
 
 #include <linux/pci_ids.h>
@@ -6402,6 +6406,22 @@ static struct ibm_struct brightness_driver_data = {
  * and we leave them unchanged.
  */
 
+#define TPACPI_ALSA_DRVNAME  "ThinkPad EC"
+#define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control"
+#define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME
+
+static int alsa_index = SNDRV_DEFAULT_IDX1;
+static char *alsa_id = "ThinkPadEC";
+static int alsa_enable = SNDRV_DEFAULT_ENABLE1;
+
+struct tpacpi_alsa_data {
+	struct snd_card *card;
+	struct snd_ctl_elem_id *ctl_mute_id;
+	struct snd_ctl_elem_id *ctl_vol_id;
+};
+
+static struct snd_card *alsa_card;
+
 enum {
 	TP_EC_AUDIO = 0x30,
 
@@ -6584,11 +6604,104 @@ static int volume_set_volume(const u8 vol)
 	return volume_set_volume_ec(vol);
 }
 
+static void volume_alsa_notify_change(void)
+{
+	struct tpacpi_alsa_data *d;
+
+	if (alsa_card && alsa_card->private_data) {
+		d = alsa_card->private_data;
+		if (d->ctl_mute_id)
+			snd_ctl_notify(alsa_card,
+					SNDRV_CTL_EVENT_MASK_VALUE,
+					d->ctl_mute_id);
+		if (d->ctl_vol_id)
+			snd_ctl_notify(alsa_card,
+					SNDRV_CTL_EVENT_MASK_VALUE,
+					d->ctl_vol_id);
+	}
+}
+
+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 = TP_EC_VOLUME_MAX;
+	return 0;
+}
+
+static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u8 s;
+	int rc;
+
+	rc = volume_get_status(&s);
+	if (rc < 0)
+		return rc;
+
+	ucontrol->value.integer.value[0] = s & TP_EC_AUDIO_LVL_MSK;
+	return 0;
+}
+
+static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	return volume_set_volume(ucontrol->value.integer.value[0]);
+}
+
+#define volume_alsa_mute_info snd_ctl_boolean_mono_info
+
+static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u8 s;
+	int rc;
+
+	rc = volume_get_status(&s);
+	if (rc < 0)
+		return rc;
+
+	ucontrol->value.integer.value[0] =
+				(s & TP_EC_AUDIO_MUTESW_MSK) ? 0 : 1;
+	return 0;
+}
+
+static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	return volume_set_mute(!ucontrol->value.integer.value[0]);
+}
+
+static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Console Playback Volume",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = volume_alsa_vol_info,
+	.get = volume_alsa_vol_get,
+};
+
+static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Console Playback Switch",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = volume_alsa_mute_info,
+	.get = volume_alsa_mute_get,
+};
+
 static void volume_suspend(pm_message_t state)
 {
 	tpacpi_volume_checkpoint_nvram();
 }
 
+static void volume_resume(void)
+{
+	volume_alsa_notify_change();
+}
+
 static void volume_shutdown(void)
 {
 	tpacpi_volume_checkpoint_nvram();
@@ -6596,9 +6709,87 @@ static void volume_shutdown(void)
 
 static void volume_exit(void)
 {
+	if (alsa_card) {
+		snd_card_free(alsa_card);
+		alsa_card = NULL;
+	}
+
 	tpacpi_volume_checkpoint_nvram();
 }
 
+static int __init volume_create_alsa_mixer(void)
+{
+	struct snd_card *card;
+	struct tpacpi_alsa_data *data;
+	struct snd_kcontrol *ctl_vol;
+	struct snd_kcontrol *ctl_mute;
+	int rc;
+
+	rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE,
+			    sizeof(struct tpacpi_alsa_data), &card);
+	if (rc < 0)
+		return rc;
+	if (!card)
+		return -ENOMEM;
+
+	BUG_ON(!card->private_data);
+	data = card->private_data;
+	data->card = card;
+
+	strlcpy(card->driver, TPACPI_ALSA_DRVNAME,
+		sizeof(card->driver));
+	strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME,
+		sizeof(card->shortname));
+	snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s",
+		 (thinkpad_id.ec_version_str) ?
+			thinkpad_id.ec_version_str : "(unknown)");
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at EC reg 0x%02x, fw %s", card->shortname, TP_EC_AUDIO,
+		 (thinkpad_id.ec_version_str) ?
+			thinkpad_id.ec_version_str : "unknown");
+
+	if (volume_control_allowed) {
+		volume_alsa_control_vol.put = volume_alsa_vol_put;
+		volume_alsa_control_vol.access =
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+
+		volume_alsa_control_mute.put = volume_alsa_mute_put;
+		volume_alsa_control_mute.access =
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	}
+
+	if (!tp_features.mixer_no_level_control) {
+		ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL);
+		rc = snd_ctl_add(card, ctl_vol);
+		if (rc < 0) {
+			printk(TPACPI_ERR
+				"Failed to create ALSA volume control\n");
+			goto err_out;
+		}
+		data->ctl_vol_id = &ctl_vol->id;
+	}
+
+	ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL);
+	rc = snd_ctl_add(card, ctl_mute);
+	if (rc < 0) {
+		printk(TPACPI_ERR "Failed to create ALSA mute control\n");
+		goto err_out;
+	}
+	data->ctl_mute_id = &ctl_mute->id;
+
+	snd_card_set_dev(card, &tpacpi_pdev->dev);
+	rc = snd_card_register(card);
+
+err_out:
+	if (rc < 0) {
+		snd_card_free(card);
+		card = NULL;
+	}
+
+	alsa_card = card;
+	return rc;
+}
+
 #define TPACPI_VOL_Q_MUTEONLY	0x0001	/* Mute-only control available */
 #define TPACPI_VOL_Q_LEVEL	0x0002  /* Volume control available */
 
@@ -6628,6 +6819,7 @@ static const struct tpacpi_quirk volume_quirk_table[] __initconst = {
 static int __init volume_init(struct ibm_init_struct *iibm)
 {
 	unsigned long quirks;
+	int rc;
 
 	vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
 
@@ -6651,6 +6843,17 @@ static int __init volume_init(struct ibm_init_struct *iibm)
 	if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
 		return -EINVAL;
 
+	/*
+	 * The ALSA mixer is our primary interface.
+	 * When disabled, don't install the subdriver at all
+	 */
+	if (!alsa_enable) {
+		vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+			    "ALSA mixer disabled by parameter, "
+			    "not loading volume subdriver...\n");
+		return 1;
+	}
+
 	quirks = tpacpi_check_quirks(volume_quirk_table,
 				     ARRAY_SIZE(volume_quirk_table));
 
@@ -6695,12 +6898,26 @@ static int __init volume_init(struct ibm_init_struct *iibm)
 			"mute is supported, volume control is %s\n",
 			str_supported(!tp_features.mixer_no_level_control));
 
+	rc = volume_create_alsa_mixer();
+	if (rc) {
+		printk(TPACPI_ERR
+			"Could not create the ALSA mixer interface\n");
+		return rc;
+	}
+
 	printk(TPACPI_INFO
 		"Console audio control enabled, mode: %s\n",
 		(volume_control_allowed) ?
 			"override (read/write)" :
 			"monitor (read only)");
 
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+		"registering volume hotkeys as change notification\n");
+	tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
+			| TP_ACPI_HKEY_VOLUP_MASK
+			| TP_ACPI_HKEY_VOLDWN_MASK
+			| TP_ACPI_HKEY_MUTE_MASK);
+
 	return 0;
 }
 
@@ -6807,6 +7024,7 @@ static int volume_write(char *buf)
 					new_mute ? "" : "un", new_level);
 		rc = volume_set_status(new_mute | new_level);
 	}
+	volume_alsa_notify_change();
 
 	return (rc == -EINTR) ? -ERESTARTSYS : rc;
 }
@@ -6817,6 +7035,7 @@ static struct ibm_struct volume_driver_data = {
 	.write = volume_write,
 	.exit = volume_exit,
 	.suspend = volume_suspend,
+	.resume = volume_resume,
 	.shutdown = volume_shutdown,
 };
 
@@ -8115,10 +8334,16 @@ static void tpacpi_driver_event(const unsigned int hkey_event)
 			tpacpi_brightness_notify_change();
 		}
 	}
+	if (alsa_card) {
+		switch (hkey_event) {
+		case TP_HKEY_EV_VOL_UP:
+		case TP_HKEY_EV_VOL_DOWN:
+		case TP_HKEY_EV_VOL_MUTE:
+			volume_alsa_notify_change();
+		}
+	}
 }
 
-
-
 static void hotkey_driver_event(const unsigned int scancode)
 {
 	tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
@@ -8552,6 +8777,14 @@ MODULE_PARM_DESC(volume_control,
 		 "Enables software override for the console audio "
 		 "control when true");
 
+/* ALSA module API parameters */
+module_param_named(index, alsa_index, int, 0444);
+MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");
+module_param_named(id, alsa_id, charp, 0444);
+MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer");
+module_param_named(enable, alsa_enable, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer");
+
 #define TPACPI_PARAM(feature) \
 	module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
 	MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
-- 
1.6.5.4


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 7/8] thinkpad-acpi: convert to seq_file
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
                   ` (5 preceding siblings ...)
  2009-12-15 23:51 ` [PATCH 6/8] thinkpad-acpi: basic ALSA mixer support (v2) Henrique de Moraes Holschuh
@ 2009-12-15 23:51 ` Henrique de Moraes Holschuh
  2009-12-15 23:51 ` [PATCH 8/8] thinkpad-acpi: bump version to 0.24 Henrique de Moraes Holschuh
  2009-12-16  4:59 ` [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Len Brown
  8 siblings, 0 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown
  Cc: linux-acpi, ibm-acpi-devel, Alexey Dobriyan, Henrique de Moraes Holschuh

From: Alexey Dobriyan <adobriyan@gmail.com>

Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
---
 drivers/platform/x86/thinkpad_acpi.c |  285 ++++++++++++++++------------------
 1 files changed, 132 insertions(+), 153 deletions(-)

diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index e0fbe73..c21c35e 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -61,6 +61,7 @@
 
 #include <linux/nvram.h>
 #include <linux/proc_fs.h>
+#include <linux/seq_file.h>
 #include <linux/sysfs.h>
 #include <linux/backlight.h>
 #include <linux/fb.h>
@@ -261,7 +262,7 @@ struct tp_acpi_drv_struct {
 struct ibm_struct {
 	char *name;
 
-	int (*read) (char *);
+	int (*read) (struct seq_file *);
 	int (*write) (char *);
 	void (*exit) (void);
 	void (*resume) (void);
@@ -789,36 +790,25 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
  ****************************************************************************
  ****************************************************************************/
 
-static int dispatch_procfs_read(char *page, char **start, off_t off,
-			int count, int *eof, void *data)
+static int dispatch_proc_show(struct seq_file *m, void *v)
 {
-	struct ibm_struct *ibm = data;
-	int len;
+	struct ibm_struct *ibm = m->private;
 
 	if (!ibm || !ibm->read)
 		return -EINVAL;
+	return ibm->read(m);
+}
 
-	len = ibm->read(page);
-	if (len < 0)
-		return len;
-
-	if (len <= off + count)
-		*eof = 1;
-	*start = page + off;
-	len -= off;
-	if (len > count)
-		len = count;
-	if (len < 0)
-		len = 0;
-
-	return len;
+static int dispatch_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, dispatch_proc_show, PDE(inode)->data);
 }
 
-static int dispatch_procfs_write(struct file *file,
+static ssize_t dispatch_proc_write(struct file *file,
 			const char __user *userbuf,
-			unsigned long count, void *data)
+			size_t count, loff_t *pos)
 {
-	struct ibm_struct *ibm = data;
+	struct ibm_struct *ibm = PDE(file->f_path.dentry->d_inode)->data;
 	char *kernbuf;
 	int ret;
 
@@ -847,6 +837,15 @@ static int dispatch_procfs_write(struct file *file,
 	return ret;
 }
 
+static const struct file_operations dispatch_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= dispatch_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.write		= dispatch_proc_write,
+};
+
 static char *next_cmd(char **cmds)
 {
 	char *start = *cmds;
@@ -1401,12 +1400,10 @@ static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
 }
 
 /* procfs -------------------------------------------------------------- */
-static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p)
+static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m)
 {
-	int len = 0;
-
 	if (id >= TPACPI_RFK_SW_MAX)
-		len += sprintf(p + len, "status:\t\tnot supported\n");
+		seq_printf(m, "status:\t\tnot supported\n");
 	else {
 		int status;
 
@@ -1420,13 +1417,13 @@ static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p)
 				return status;
 		}
 
-		len += sprintf(p + len, "status:\t\t%s\n",
+		seq_printf(m, "status:\t\t%s\n",
 				(status == TPACPI_RFK_RADIO_ON) ?
 					"enabled" : "disabled");
-		len += sprintf(p + len, "commands:\tenable, disable\n");
+		seq_printf(m, "commands:\tenable, disable\n");
 	}
 
-	return len;
+	return 0;
 }
 
 static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf)
@@ -1904,14 +1901,11 @@ static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
 	return 0;
 }
 
-static int thinkpad_acpi_driver_read(char *p)
+static int thinkpad_acpi_driver_read(struct seq_file *m)
 {
-	int len = 0;
-
-	len += sprintf(p + len, "driver:\t\t%s\n", TPACPI_DESC);
-	len += sprintf(p + len, "version:\t%s\n", TPACPI_VERSION);
-
-	return len;
+	seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
+	seq_printf(m, "version:\t%s\n", TPACPI_VERSION);
+	return 0;
 }
 
 static struct ibm_struct thinkpad_acpi_driver_data = {
@@ -3759,14 +3753,13 @@ static void hotkey_resume(void)
 }
 
 /* procfs -------------------------------------------------------------- */
-static int hotkey_read(char *p)
+static int hotkey_read(struct seq_file *m)
 {
 	int res, status;
-	int len = 0;
 
 	if (!tp_features.hotkey) {
-		len += sprintf(p + len, "status:\t\tnot supported\n");
-		return len;
+		seq_printf(m, "status:\t\tnot supported\n");
+		return 0;
 	}
 
 	if (mutex_lock_killable(&hotkey_mutex))
@@ -3778,17 +3771,16 @@ static int hotkey_read(char *p)
 	if (res)
 		return res;
 
-	len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0));
+	seq_printf(m, "status:\t\t%s\n", enabled(status, 0));
 	if (hotkey_all_mask) {
-		len += sprintf(p + len, "mask:\t\t0x%08x\n", hotkey_user_mask);
-		len += sprintf(p + len,
-			       "commands:\tenable, disable, reset, <mask>\n");
+		seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask);
+		seq_printf(m, "commands:\tenable, disable, reset, <mask>\n");
 	} else {
-		len += sprintf(p + len, "mask:\t\tnot supported\n");
-		len += sprintf(p + len, "commands:\tenable, disable, reset\n");
+		seq_printf(m, "mask:\t\tnot supported\n");
+		seq_printf(m, "commands:\tenable, disable, reset\n");
 	}
 
-	return len;
+	return 0;
 }
 
 static void hotkey_enabledisable_warn(bool enable)
@@ -4054,9 +4046,9 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
 }
 
 /* procfs -------------------------------------------------------------- */
-static int bluetooth_read(char *p)
+static int bluetooth_read(struct seq_file *m)
 {
-	return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, p);
+	return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, m);
 }
 
 static int bluetooth_write(char *buf)
@@ -4244,9 +4236,9 @@ static int __init wan_init(struct ibm_init_struct *iibm)
 }
 
 /* procfs -------------------------------------------------------------- */
-static int wan_read(char *p)
+static int wan_read(struct seq_file *m)
 {
-	return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, p);
+	return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, m);
 }
 
 static int wan_write(char *buf)
@@ -4621,14 +4613,13 @@ static int video_expand_toggle(void)
 	/* not reached */
 }
 
-static int video_read(char *p)
+static int video_read(struct seq_file *m)
 {
 	int status, autosw;
-	int len = 0;
 
 	if (video_supported == TPACPI_VIDEO_NONE) {
-		len += sprintf(p + len, "status:\t\tnot supported\n");
-		return len;
+		seq_printf(m, "status:\t\tnot supported\n");
+		return 0;
 	}
 
 	status = video_outputsw_get();
@@ -4639,20 +4630,20 @@ static int video_read(char *p)
 	if (autosw < 0)
 		return autosw;
 
-	len += sprintf(p + len, "status:\t\tsupported\n");
-	len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0));
-	len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1));
+	seq_printf(m, "status:\t\tsupported\n");
+	seq_printf(m, "lcd:\t\t%s\n", enabled(status, 0));
+	seq_printf(m, "crt:\t\t%s\n", enabled(status, 1));
 	if (video_supported == TPACPI_VIDEO_NEW)
-		len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3));
-	len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0));
-	len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n");
-	len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n");
+		seq_printf(m, "dvi:\t\t%s\n", enabled(status, 3));
+	seq_printf(m, "auto:\t\t%s\n", enabled(autosw, 0));
+	seq_printf(m, "commands:\tlcd_enable, lcd_disable\n");
+	seq_printf(m, "commands:\tcrt_enable, crt_disable\n");
 	if (video_supported == TPACPI_VIDEO_NEW)
-		len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n");
-	len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n");
-	len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n");
+		seq_printf(m, "commands:\tdvi_enable, dvi_disable\n");
+	seq_printf(m, "commands:\tauto_enable, auto_disable\n");
+	seq_printf(m, "commands:\tvideo_switch, expand_toggle\n");
 
-	return len;
+	return 0;
 }
 
 static int video_write(char *buf)
@@ -4844,25 +4835,24 @@ static void light_exit(void)
 		flush_workqueue(tpacpi_wq);
 }
 
-static int light_read(char *p)
+static int light_read(struct seq_file *m)
 {
-	int len = 0;
 	int status;
 
 	if (!tp_features.light) {
-		len += sprintf(p + len, "status:\t\tnot supported\n");
+		seq_printf(m, "status:\t\tnot supported\n");
 	} else if (!tp_features.light_status) {
-		len += sprintf(p + len, "status:\t\tunknown\n");
-		len += sprintf(p + len, "commands:\ton, off\n");
+		seq_printf(m, "status:\t\tunknown\n");
+		seq_printf(m, "commands:\ton, off\n");
 	} else {
 		status = light_get_status();
 		if (status < 0)
 			return status;
-		len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0));
-		len += sprintf(p + len, "commands:\ton, off\n");
+		seq_printf(m, "status:\t\t%s\n", onoff(status, 0));
+		seq_printf(m, "commands:\ton, off\n");
 	}
 
-	return len;
+	return 0;
 }
 
 static int light_write(char *buf)
@@ -4940,20 +4930,18 @@ static void cmos_exit(void)
 	device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
 }
 
-static int cmos_read(char *p)
+static int cmos_read(struct seq_file *m)
 {
-	int len = 0;
-
 	/* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
 	   R30, R31, T20-22, X20-21 */
 	if (!cmos_handle)
-		len += sprintf(p + len, "status:\t\tnot supported\n");
+		seq_printf(m, "status:\t\tnot supported\n");
 	else {
-		len += sprintf(p + len, "status:\t\tsupported\n");
-		len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n");
+		seq_printf(m, "status:\t\tsupported\n");
+		seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n");
 	}
 
-	return len;
+	return 0;
 }
 
 static int cmos_write(char *buf)
@@ -5328,15 +5316,13 @@ static int __init led_init(struct ibm_init_struct *iibm)
 	((s) == TPACPI_LED_OFF ? "off" : \
 		((s) == TPACPI_LED_ON ? "on" : "blinking"))
 
-static int led_read(char *p)
+static int led_read(struct seq_file *m)
 {
-	int len = 0;
-
 	if (!led_supported) {
-		len += sprintf(p + len, "status:\t\tnot supported\n");
-		return len;
+		seq_printf(m, "status:\t\tnot supported\n");
+		return 0;
 	}
-	len += sprintf(p + len, "status:\t\tsupported\n");
+	seq_printf(m, "status:\t\tsupported\n");
 
 	if (led_supported == TPACPI_LED_570) {
 		/* 570 */
@@ -5345,15 +5331,15 @@ static int led_read(char *p)
 			status = led_get_status(i);
 			if (status < 0)
 				return -EIO;
-			len += sprintf(p + len, "%d:\t\t%s\n",
+			seq_printf(m, "%d:\t\t%s\n",
 				       i, str_led_status(status));
 		}
 	}
 
-	len += sprintf(p + len, "commands:\t"
+	seq_printf(m, "commands:\t"
 		       "<led> on, <led> off, <led> blink (<led> is 0-15)\n");
 
-	return len;
+	return 0;
 }
 
 static int led_write(char *buf)
@@ -5426,18 +5412,16 @@ static int __init beep_init(struct ibm_init_struct *iibm)
 	return (beep_handle)? 0 : 1;
 }
 
-static int beep_read(char *p)
+static int beep_read(struct seq_file *m)
 {
-	int len = 0;
-
 	if (!beep_handle)
-		len += sprintf(p + len, "status:\t\tnot supported\n");
+		seq_printf(m, "status:\t\tnot supported\n");
 	else {
-		len += sprintf(p + len, "status:\t\tsupported\n");
-		len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n");
+		seq_printf(m, "status:\t\tsupported\n");
+		seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
 	}
 
-	return len;
+	return 0;
 }
 
 static int beep_write(char *buf)
@@ -5798,9 +5782,8 @@ static void thermal_exit(void)
 	}
 }
 
-static int thermal_read(char *p)
+static int thermal_read(struct seq_file *m)
 {
-	int len = 0;
 	int n, i;
 	struct ibm_thermal_sensors_struct t;
 
@@ -5808,16 +5791,16 @@ static int thermal_read(char *p)
 	if (unlikely(n < 0))
 		return n;
 
-	len += sprintf(p + len, "temperatures:\t");
+	seq_printf(m, "temperatures:\t");
 
 	if (n > 0) {
 		for (i = 0; i < (n - 1); i++)
-			len += sprintf(p + len, "%d ", t.temp[i] / 1000);
-		len += sprintf(p + len, "%d\n", t.temp[i] / 1000);
+			seq_printf(m, "%d ", t.temp[i] / 1000);
+		seq_printf(m, "%d\n", t.temp[i] / 1000);
 	} else
-		len += sprintf(p + len, "not supported\n");
+		seq_printf(m, "not supported\n");
 
-	return len;
+	return 0;
 }
 
 static struct ibm_struct thermal_driver_data = {
@@ -5832,39 +5815,38 @@ static struct ibm_struct thermal_driver_data = {
 
 static u8 ecdump_regs[256];
 
-static int ecdump_read(char *p)
+static int ecdump_read(struct seq_file *m)
 {
-	int len = 0;
 	int i, j;
 	u8 v;
 
-	len += sprintf(p + len, "EC      "
+	seq_printf(m, "EC      "
 		       " +00 +01 +02 +03 +04 +05 +06 +07"
 		       " +08 +09 +0a +0b +0c +0d +0e +0f\n");
 	for (i = 0; i < 256; i += 16) {
-		len += sprintf(p + len, "EC 0x%02x:", i);
+		seq_printf(m, "EC 0x%02x:", i);
 		for (j = 0; j < 16; j++) {
 			if (!acpi_ec_read(i + j, &v))
 				break;
 			if (v != ecdump_regs[i + j])
-				len += sprintf(p + len, " *%02x", v);
+				seq_printf(m, " *%02x", v);
 			else
-				len += sprintf(p + len, "  %02x", v);
+				seq_printf(m, "  %02x", v);
 			ecdump_regs[i + j] = v;
 		}
-		len += sprintf(p + len, "\n");
+		seq_putc(m, '\n');
 		if (j != 16)
 			break;
 	}
 
 	/* These are way too dangerous to advertise openly... */
 #if 0
-	len += sprintf(p + len, "commands:\t0x<offset> 0x<value>"
+	seq_printf(m, "commands:\t0x<offset> 0x<value>"
 		       " (<offset> is 00-ff, <value> is 00-ff)\n");
-	len += sprintf(p + len, "commands:\t0x<offset> <value>  "
+	seq_printf(m, "commands:\t0x<offset> <value>  "
 		       " (<offset> is 00-ff, <value> is 0-255)\n");
 #endif
-	return len;
+	return 0;
 }
 
 static int ecdump_write(char *buf)
@@ -6317,23 +6299,22 @@ static void brightness_exit(void)
 	tpacpi_brightness_checkpoint_nvram();
 }
 
-static int brightness_read(char *p)
+static int brightness_read(struct seq_file *m)
 {
-	int len = 0;
 	int level;
 
 	level = brightness_get(NULL);
 	if (level < 0) {
-		len += sprintf(p + len, "level:\t\tunreadable\n");
+		seq_printf(m, "level:\t\tunreadable\n");
 	} else {
-		len += sprintf(p + len, "level:\t\t%d\n", level);
-		len += sprintf(p + len, "commands:\tup, down\n");
-		len += sprintf(p + len, "commands:\tlevel <level>"
+		seq_printf(m, "level:\t\t%d\n", level);
+		seq_printf(m, "commands:\tup, down\n");
+		seq_printf(m, "commands:\tlevel <level>"
 			       " (<level> is 0-%d)\n",
 			       (tp_features.bright_16levels) ? 15 : 7);
 	}
 
-	return len;
+	return 0;
 }
 
 static int brightness_write(char *buf)
@@ -6921,29 +6902,28 @@ static int __init volume_init(struct ibm_init_struct *iibm)
 	return 0;
 }
 
-static int volume_read(char *p)
+static int volume_read(struct seq_file *m)
 {
-	int len = 0;
 	u8 status;
 
 	if (volume_get_status(&status) < 0) {
-		len += sprintf(p + len, "level:\t\tunreadable\n");
+		seq_printf(m, "level:\t\tunreadable\n");
 	} else {
 		if (tp_features.mixer_no_level_control)
-			len += sprintf(p + len, "level:\t\tunsupported\n");
+			seq_printf(m, "level:\t\tunsupported\n");
 		else
-			len += sprintf(p + len, "level:\t\t%d\n",
+			seq_printf(m, "level:\t\t%d\n",
 					status & TP_EC_AUDIO_LVL_MSK);
 
-		len += sprintf(p + len, "mute:\t\t%s\n",
+		seq_printf(m, "mute:\t\t%s\n",
 				onoff(status, TP_EC_AUDIO_MUTESW));
 
 		if (volume_control_allowed) {
-			len += sprintf(p + len, "commands:\tunmute, mute\n");
+			seq_printf(m, "commands:\tunmute, mute\n");
 			if (!tp_features.mixer_no_level_control) {
-				len += sprintf(p + len,
+				seq_printf(m,
 					       "commands:\tup, down\n");
-				len += sprintf(p + len,
+				seq_printf(m,
 					       "commands:\tlevel <level>"
 					       " (<level> is 0-%d)\n",
 					       TP_EC_VOLUME_MAX);
@@ -6951,7 +6931,7 @@ static int volume_read(char *p)
 		}
 	}
 
-	return len;
+	return 0;
 }
 
 static int volume_write(char *buf)
@@ -8113,9 +8093,8 @@ static void fan_resume(void)
 	}
 }
 
-static int fan_read(char *p)
+static int fan_read(struct seq_file *m)
 {
-	int len = 0;
 	int rc;
 	u8 status;
 	unsigned int speed = 0;
@@ -8127,7 +8106,7 @@ static int fan_read(char *p)
 		if (rc < 0)
 			return rc;
 
-		len += sprintf(p + len, "status:\t\t%s\n"
+		seq_printf(m, "status:\t\t%s\n"
 			       "level:\t\t%d\n",
 			       (status != 0) ? "enabled" : "disabled", status);
 		break;
@@ -8138,54 +8117,54 @@ static int fan_read(char *p)
 		if (rc < 0)
 			return rc;
 
-		len += sprintf(p + len, "status:\t\t%s\n",
+		seq_printf(m, "status:\t\t%s\n",
 			       (status != 0) ? "enabled" : "disabled");
 
 		rc = fan_get_speed(&speed);
 		if (rc < 0)
 			return rc;
 
-		len += sprintf(p + len, "speed:\t\t%d\n", speed);
+		seq_printf(m, "speed:\t\t%d\n", speed);
 
 		if (status & TP_EC_FAN_FULLSPEED)
 			/* Disengaged mode takes precedence */
-			len += sprintf(p + len, "level:\t\tdisengaged\n");
+			seq_printf(m, "level:\t\tdisengaged\n");
 		else if (status & TP_EC_FAN_AUTO)
-			len += sprintf(p + len, "level:\t\tauto\n");
+			seq_printf(m, "level:\t\tauto\n");
 		else
-			len += sprintf(p + len, "level:\t\t%d\n", status);
+			seq_printf(m, "level:\t\t%d\n", status);
 		break;
 
 	case TPACPI_FAN_NONE:
 	default:
-		len += sprintf(p + len, "status:\t\tnot supported\n");
+		seq_printf(m, "status:\t\tnot supported\n");
 	}
 
 	if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
-		len += sprintf(p + len, "commands:\tlevel <level>");
+		seq_printf(m, "commands:\tlevel <level>");
 
 		switch (fan_control_access_mode) {
 		case TPACPI_FAN_WR_ACPI_SFAN:
-			len += sprintf(p + len, " (<level> is 0-7)\n");
+			seq_printf(m, " (<level> is 0-7)\n");
 			break;
 
 		default:
-			len += sprintf(p + len, " (<level> is 0-7, "
+			seq_printf(m, " (<level> is 0-7, "
 				       "auto, disengaged, full-speed)\n");
 			break;
 		}
 	}
 
 	if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
-		len += sprintf(p + len, "commands:\tenable, disable\n"
+		seq_printf(m, "commands:\tenable, disable\n"
 			       "commands:\twatchdog <timeout> (<timeout> "
 			       "is 0 (off), 1-120 (seconds))\n");
 
 	if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
-		len += sprintf(p + len, "commands:\tspeed <speed>"
+		seq_printf(m, "commands:\tspeed <speed>"
 			       " (<speed> is 0-65535)\n");
 
-	return len;
+	return 0;
 }
 
 static int fan_write_cmd_level(const char *cmd, int *rc)
@@ -8472,19 +8451,19 @@ static int __init ibm_init(struct ibm_init_struct *iibm)
 		"%s installed\n", ibm->name);
 
 	if (ibm->read) {
-		entry = create_proc_entry(ibm->name,
-					  S_IFREG | S_IRUGO | S_IWUSR,
-					  proc_dir);
+		mode_t mode;
+
+		mode = S_IRUGO;
+		if (ibm->write)
+			mode |= S_IWUSR;
+		entry = proc_create_data(ibm->name, mode, proc_dir,
+					 &dispatch_proc_fops, ibm);
 		if (!entry) {
 			printk(TPACPI_ERR "unable to create proc entry %s\n",
 			       ibm->name);
 			ret = -ENODEV;
 			goto err_out;
 		}
-		entry->data = ibm;
-		entry->read_proc = &dispatch_procfs_read;
-		if (ibm->write)
-			entry->write_proc = &dispatch_procfs_write;
 		ibm->flags.proc_created = 1;
 	}
 
-- 
1.6.5.4


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 8/8] thinkpad-acpi: bump version to 0.24
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
                   ` (6 preceding siblings ...)
  2009-12-15 23:51 ` [PATCH 7/8] thinkpad-acpi: convert to seq_file Henrique de Moraes Holschuh
@ 2009-12-15 23:51 ` Henrique de Moraes Holschuh
  2009-12-16  4:59 ` [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Len Brown
  8 siblings, 0 replies; 10+ messages in thread
From: Henrique de Moraes Holschuh @ 2009-12-15 23:51 UTC (permalink / raw)
  To: Len Brown; +Cc: linux-acpi, ibm-acpi-devel, Henrique de Moraes Holschuh

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
---
 Documentation/laptops/thinkpad-acpi.txt |    4 ++--
 drivers/platform/x86/thinkpad_acpi.c    |    2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt
index b4ed30c..169091f 100644
--- a/Documentation/laptops/thinkpad-acpi.txt
+++ b/Documentation/laptops/thinkpad-acpi.txt
@@ -1,7 +1,7 @@
 		     ThinkPad ACPI Extras Driver
 
-                            Version 0.23
-                          April 10th, 2009
+                            Version 0.24
+                        December 11th,  2009
 
                Borislav Deianov <borislav@users.sf.net>
              Henrique de Moraes Holschuh <hmh@hmh.eng.br>
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index c21c35e..af23525 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -21,7 +21,7 @@
  *  02110-1301, USA.
  */
 
-#define TPACPI_VERSION "0.23"
+#define TPACPI_VERSION "0.24"
 #define TPACPI_SYSFS_VERSION 0x020700
 
 /*
-- 
1.6.5.4


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33
  2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
                   ` (7 preceding siblings ...)
  2009-12-15 23:51 ` [PATCH 8/8] thinkpad-acpi: bump version to 0.24 Henrique de Moraes Holschuh
@ 2009-12-16  4:59 ` Len Brown
  8 siblings, 0 replies; 10+ messages in thread
From: Len Brown @ 2009-12-16  4:59 UTC (permalink / raw)
  To: Henrique de Moraes Holschuh; +Cc: linux-acpi, ibm-acpi-devel

applied to acpi-test

thanks,
Len Brown, Intel Open Source Technology Center


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2009-12-16  4:59 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-12-15 23:51 [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Henrique de Moraes Holschuh
2009-12-15 23:51 ` [PATCH 1/8] thinkpad-acpi: sync input device EV_SW initial state Henrique de Moraes Holschuh
2009-12-15 23:51 ` [PATCH 2/8] thinkpad-acpi: log initial state of rfkill switches Henrique de Moraes Holschuh
2009-12-15 23:51 ` [PATCH 3/8] thinkpad-acpi: volume subdriver rewrite Henrique de Moraes Holschuh
2009-12-15 23:51 ` [PATCH 4/8] thinkpad-acpi: support MUTE-only ThinkPads Henrique de Moraes Holschuh
2009-12-15 23:51 ` [PATCH 5/8] thinkpad-acpi: disable volume control Henrique de Moraes Holschuh
2009-12-15 23:51 ` [PATCH 6/8] thinkpad-acpi: basic ALSA mixer support (v2) Henrique de Moraes Holschuh
2009-12-15 23:51 ` [PATCH 7/8] thinkpad-acpi: convert to seq_file Henrique de Moraes Holschuh
2009-12-15 23:51 ` [PATCH 8/8] thinkpad-acpi: bump version to 0.24 Henrique de Moraes Holschuh
2009-12-16  4:59 ` [GIT PATCH] thinkpad-acpi second set of changes for 2.6.33 Len Brown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.