From a9f8eacca4e9e8693de9b896c1fa7aadaa9402e8 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:21 +0000 Subject: [PATCH 01/17] thinkpad-acpi: fix default brightness_mode for R50e/R51 According to a report, the R50e wants EC-based brightness control, even if it uses an Intel GPU. The current driver default was reported to not work at all. This bug can be worked around by the "brightness_mode=3" module parameter. Change the default of the R50e and R51 2xxx models (which use the same EC firmware, 1V) to TPACPI_BRGHT_Q_EC, but keep TPACPI_BRGHT_Q_ASK set for now, as I'd like to get more reports. This fixes a regression caused by commit 59fe4fe34d7afdf63208124f313be9056feaa2f4, "thinkpad-acpi: fix incorrect use of TPACPI_BRGHT_MODE_ECNVRAM" Kernel 2.6.31 also needs this fix. Reported-by: Ferenc Wagner Tested-by: Ferenc Wagner Signed-off-by: Henrique de Moraes Holschuh Cc: stable@kernel.org Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index a848c7e20aeb..9c6d5a929e96 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -6123,8 +6123,8 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = { /* Models with Intel Extreme Graphics 2 */ TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC), - TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC), - TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC), + TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), + TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), /* Models with Intel GMA900 */ TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC), /* T43, R52 */ From 208b996b6c460285650d39b2330f8ef82c007d10 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:22 +0000 Subject: [PATCH 02/17] thinkpad-acpi: preserve rfkill state across suspend/resume Since the rfkill rework in 2.6.31, the driver is always resuming with the radios disabled. Change thinkpad-acpi to ask the firmware to resume with the radios in the last state. This fixes the Bluetooth and WWAN rfkill switches. Note that it means we respect the firmware's oddities. Should the user toggle the hardware rfkill switch on and off, it might cause the radios to resume enabled. UWB is an unknown quantity since it has nowhere the same level of firmware support (no control over state storage in NVRAM, for example), and might need further fixing. Testers welcome. This change fixes a regression from 2.6.30. Reported-by: Jerone Young Reported-by: Ian Molton Signed-off-by: Henrique de Moraes Holschuh Tested-by: Ian Molton Cc: stable@kernel.org Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 32 +++++----------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 9c6d5a929e96..1ee734c14cc1 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3866,15 +3866,6 @@ enum { #define TPACPI_RFK_BLUETOOTH_SW_NAME "tpacpi_bluetooth_sw" -static void bluetooth_suspend(pm_message_t state) -{ - /* Try to make sure radio will resume powered off */ - if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd", - TP_ACPI_BLTH_PWR_OFF_ON_RESUME)) - vdbg_printk(TPACPI_DBG_RFKILL, - "bluetooth power down on resume request failed\n"); -} - static int bluetooth_get_status(void) { int status; @@ -3908,10 +3899,9 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state) #endif /* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */ + status = TP_ACPI_BLUETOOTH_RESUMECTRL; if (state == TPACPI_RFK_RADIO_ON) - status = TP_ACPI_BLUETOOTH_RADIOSSW; - else - status = 0; + status |= TP_ACPI_BLUETOOTH_RADIOSSW; if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) return -EIO; @@ -4050,7 +4040,6 @@ static struct ibm_struct bluetooth_driver_data = { .read = bluetooth_read, .write = bluetooth_write, .exit = bluetooth_exit, - .suspend = bluetooth_suspend, .shutdown = bluetooth_shutdown, }; @@ -4068,15 +4057,6 @@ enum { #define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw" -static void wan_suspend(pm_message_t state) -{ - /* Try to make sure radio will resume powered off */ - if (!acpi_evalf(NULL, NULL, "\\WGSV", "qvd", - TP_ACPI_WGSV_PWR_OFF_ON_RESUME)) - vdbg_printk(TPACPI_DBG_RFKILL, - "WWAN power down on resume request failed\n"); -} - static int wan_get_status(void) { int status; @@ -4109,11 +4089,10 @@ static int wan_set_status(enum tpacpi_rfkill_state state) } #endif - /* We make sure to keep TP_ACPI_WANCARD_RESUMECTRL off */ + /* We make sure to set TP_ACPI_WANCARD_RESUMECTRL */ + status = TP_ACPI_WANCARD_RESUMECTRL; if (state == TPACPI_RFK_RADIO_ON) - status = TP_ACPI_WANCARD_RADIOSSW; - else - status = 0; + status |= TP_ACPI_WANCARD_RADIOSSW; if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) return -EIO; @@ -4251,7 +4230,6 @@ static struct ibm_struct wan_driver_data = { .read = wan_read, .write = wan_write, .exit = wan_exit, - .suspend = wan_suspend, .shutdown = wan_shutdown, }; From 90765c6aee568137521ba19347c744b5abde8161 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:23 +0000 Subject: [PATCH 03/17] thinkpad-acpi: fix some version quirks Update some of the BIOS/EC version quirks. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 1ee734c14cc1..6160813d906a 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -1779,7 +1779,7 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = { TPV_QL1('7', '9', 'E', '3', '5', '0'), /* T60/p */ TPV_QL1('7', 'C', 'D', '2', '2', '2'), /* R60, R60i */ - TPV_QL0('7', 'E', 'D', '0'), /* R60e, R60i */ + TPV_QL1('7', 'E', 'D', '0', '1', '5'), /* R60e, R60i */ /* BIOS FW BIOS VERS EC FW EC VERS */ TPV_QI2('1', 'W', '9', '0', '1', 'V', '2', '8'), /* R50e (1) */ @@ -1795,8 +1795,8 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = { TPV_QI1('7', '4', '6', '4', '2', '7'), /* X41 (0) */ TPV_QI1('7', '5', '6', '0', '2', '0'), /* X41t (0) */ - TPV_QL0('7', 'B', 'D', '7'), /* X60/s */ - TPV_QL0('7', 'J', '3', '0'), /* X60t */ + TPV_QL1('7', 'B', 'D', '7', '4', '0'), /* X60/s */ + TPV_QL1('7', 'J', '3', '0', '1', '3'), /* X60t */ /* (0) - older versions lack DMI EC fw string and functionality */ /* (1) - older versions known to lack functionality */ From 347a26860e2293b1347996876d3550499c7bb31f Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:24 +0000 Subject: [PATCH 04/17] thinkpad-acpi: issue backlight class events Take advantage of the new events capabilities of the backlight class to notify userspace of backlight changes. This depends on "backlight: Allow drivers to update the core, and generate events on changes", by Matthew Garrett. Signed-off-by: Henrique de Moraes Holschuh Cc: Matthew Garrett Cc: Richard Purdie Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 51 ++++++------------------- drivers/platform/x86/thinkpad_acpi.c | 24 +++++++++++- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index aafcaa634191..f5056c7fb5be 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -460,6 +460,8 @@ event code Key Notes For Lenovo ThinkPads with a new BIOS, it has to be handled either by the ACPI OSI, or by userspace. + The driver does the right thing, + never mess with this. 0x1011 0x10 FN+END Brightness down. See brightness up for details. @@ -582,46 +584,15 @@ with hotkey_report_mode. Brightness hotkey notes: -These are the current sane choices for brightness key mapping in -thinkpad-acpi: +Don't mess with the brightness hotkeys in a Thinkpad. If you want +notifications for OSD, use the sysfs backlight class event support. -For IBM and Lenovo models *without* ACPI backlight control (the ones on -which thinkpad-acpi will autoload its backlight interface by default, -and on which ACPI video does not export a backlight interface): - -1. Don't enable or map the brightness hotkeys in thinkpad-acpi, as - these older firmware versions unfortunately won't respect the hotkey - mask for brightness keys anyway, and always reacts to them. This - usually work fine, unless X.org drivers are doing something to block - the BIOS. In that case, use (3) below. This is the default mode of - operation. - -2. Enable the hotkeys, but map them to something else that is NOT - KEY_BRIGHTNESS_UP/DOWN or any other keycode that would cause - userspace to try to change the backlight level, and use that as an - on-screen-display hint. - -3. IF AND ONLY IF X.org drivers find a way to block the firmware from - automatically changing the brightness, enable the hotkeys and map - them to KEY_BRIGHTNESS_UP and KEY_BRIGHTNESS_DOWN, and feed that to - something that calls xbacklight. thinkpad-acpi will not be able to - change brightness in that case either, so you should disable its - backlight interface. - -For Lenovo models *with* ACPI backlight control: - -1. Load up ACPI video and use that. ACPI video will report ACPI - events for brightness change keys. Do not mess with thinkpad-acpi - defaults in this case. thinkpad-acpi should not have anything to do - with backlight events in a scenario where ACPI video is loaded: - brightness hotkeys must be disabled, and the backlight interface is - to be kept disabled as well. This is the default mode of operation. - -2. Do *NOT* load up ACPI video, enable the hotkeys in thinkpad-acpi, - and map them to KEY_BRIGHTNESS_UP and KEY_BRIGHTNESS_DOWN. Process - these keys on userspace somehow (e.g. by calling xbacklight). - The driver will do this automatically if it detects that ACPI video - has been disabled. +The driver will issue KEY_BRIGHTNESS_UP and KEY_BRIGHTNESS_DOWN events +automatically for the cases were userspace has to do something to +implement brightness changes. When you override these events, you will +either fail to handle properly the ThinkPads that require explicit +action to change backlight brightness, or the ThinkPads that require +that no action be taken to work properly. Bluetooth @@ -1465,3 +1436,5 @@ Sysfs interface changelog: and it is always able to disable hot keys. Very old thinkpads are properly supported. hotkey_bios_mask is deprecated and marked for removal. + +0x020600: Marker for backlight change event support. diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 6160813d906a..44061367a10d 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 0x020500 +#define TPACPI_SYSFS_VERSION 0x020600 /* * Changelog: @@ -6073,6 +6073,12 @@ static int brightness_get(struct backlight_device *bd) return status & TP_EC_BACKLIGHT_LVLMSK; } +static void tpacpi_brightness_notify_change(void) +{ + backlight_force_update(ibm_backlight_device, + BACKLIGHT_UPDATE_HOTKEY); +} + static struct backlight_ops ibm_backlight_data = { .get_brightness = brightness_get, .update_status = brightness_update_status, @@ -6227,6 +6233,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm) ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK; backlight_update_status(ibm_backlight_device); + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT, + "brightness: registering brightness hotkeys " + "as change notification\n"); + tpacpi_hotkey_driver_mask_set(hotkey_driver_mask + | TP_ACPI_HKEY_BRGHTUP_MASK + | TP_ACPI_HKEY_BRGHTDWN_MASK);; return 0; } @@ -6303,6 +6315,9 @@ static int brightness_write(char *buf) * Doing it this way makes the syscall restartable in case of EINTR */ rc = brightness_set(level); + if (!rc && ibm_backlight_device) + backlight_force_update(ibm_backlight_device, + BACKLIGHT_UPDATE_SYSFS); return (rc == -EINTR)? -ERESTARTSYS : rc; } @@ -7702,6 +7717,13 @@ static struct ibm_struct fan_driver_data = { */ static void tpacpi_driver_event(const unsigned int hkey_event) { + if (ibm_backlight_device) { + switch (hkey_event) { + case TP_HKEY_EV_BRGHT_UP: + case TP_HKEY_EV_BRGHT_DOWN: + tpacpi_brightness_notify_change(); + } + } } From 6b30eb7d211840ba1a03f855d9e7b80a921368f2 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:25 +0000 Subject: [PATCH 05/17] thinkpad-acpi: silence bogus complain during rmmod Fix this bogus warning during module shutdown, when backlight event reporting is enabled: "thinkpad_acpi: required events 0x00018000 not enabled!" Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 44061367a10d..86418cd3e59d 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2189,7 +2189,8 @@ static int hotkey_mask_set(u32 mask) fwmask, hotkey_acpi_mask); } - hotkey_mask_warn_incomplete_mask(); + if (tpacpi_lifecycle != TPACPI_LIFE_EXITING) + hotkey_mask_warn_incomplete_mask(); return rc; } From d112ef95d4ec1ee7fe7123e3f21e4aac0d57570c Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:26 +0000 Subject: [PATCH 06/17] thinkpad-acpi: adopt input device Properly init the parent field of the input device. Thanks to Alan Jenkins, who noted this problem in a different driver. Reported-by: Alan Jenkins Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 86418cd3e59d..dd8bd072c79a 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -8375,6 +8375,7 @@ static int __init thinkpad_acpi_module_init(void) PCI_VENDOR_ID_IBM; tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT; tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION; + tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev; } for (i = 0; i < ARRAY_SIZE(ibms_init); i++) { ret = ibm_init(&ibms_init[i]); From b09c72259e88cec3d602aef987a3209297f3a9c2 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:27 +0000 Subject: [PATCH 07/17] thinkpad-acpi: expose module parameters Export the normal (non-command) module paramenters as mode 0444, so that they will show up in sysfs. These parameters must not be changed at runtime as a rule, with very few exceptions. Reported-by: Ferenc Wagner Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index dd8bd072c79a..4b96a961b7e0 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -8116,32 +8116,32 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp) return -EINVAL; } -module_param(experimental, int, 0); +module_param(experimental, int, 0444); MODULE_PARM_DESC(experimental, "Enables experimental features when non-zero"); module_param_named(debug, dbg_level, uint, 0); MODULE_PARM_DESC(debug, "Sets debug level bit-mask"); -module_param(force_load, bool, 0); +module_param(force_load, bool, 0444); MODULE_PARM_DESC(force_load, "Attempts to load the driver even on a " "mis-identified ThinkPad when true"); -module_param_named(fan_control, fan_control_allowed, bool, 0); +module_param_named(fan_control, fan_control_allowed, bool, 0444); MODULE_PARM_DESC(fan_control, "Enables setting fan parameters features when true"); -module_param_named(brightness_mode, brightness_mode, uint, 0); +module_param_named(brightness_mode, brightness_mode, uint, 0444); MODULE_PARM_DESC(brightness_mode, "Selects brightness control strategy: " "0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM"); -module_param(brightness_enable, uint, 0); +module_param(brightness_enable, uint, 0444); MODULE_PARM_DESC(brightness_enable, "Enables backlight control when 1, disables when 0"); -module_param(hotkey_report_mode, uint, 0); +module_param(hotkey_report_mode, uint, 0444); MODULE_PARM_DESC(hotkey_report_mode, "used for backwards compatibility with userspace, " "see documentation"); @@ -8164,25 +8164,25 @@ TPACPI_PARAM(volume); TPACPI_PARAM(fan); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES -module_param(dbg_wlswemul, uint, 0); +module_param(dbg_wlswemul, uint, 0444); MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation"); module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0); MODULE_PARM_DESC(wlsw_state, "Initial state of the emulated WLSW switch"); -module_param(dbg_bluetoothemul, uint, 0); +module_param(dbg_bluetoothemul, uint, 0444); MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation"); module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0); MODULE_PARM_DESC(bluetooth_state, "Initial state of the emulated bluetooth switch"); -module_param(dbg_wwanemul, uint, 0); +module_param(dbg_wwanemul, uint, 0444); MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation"); module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0); MODULE_PARM_DESC(wwan_state, "Initial state of the emulated WWAN switch"); -module_param(dbg_uwbemul, uint, 0); +module_param(dbg_uwbemul, uint, 0444); MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation"); module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0); MODULE_PARM_DESC(uwb_state, From 9ebd9e833648745fa5ac6998b9e0153ccd3ba839 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:28 +0000 Subject: [PATCH 08/17] thinkpad-acpi: log temperatures on termal alarm (v2) Log temperatures on any of the EC thermal alarms. It could be useful to help tracking down what is happening... Signed-off-by: Henrique de Moraes Holschuh Acked-by: Pavel Machek Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 83 +++++++++++++++++++--------- 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 4b96a961b7e0..b3e256b2cf16 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3546,49 +3546,57 @@ static bool hotkey_notify_usrevent(const u32 hkey, } } +static void thermal_dump_all_sensors(void); + static bool hotkey_notify_thermal(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) { + bool known = true; + /* 0x6000-0x6FFF: thermal alarms */ *send_acpi_ev = true; *ignore_acpi_ev = false; switch (hkey) { - case TP_HKEY_EV_ALARM_BAT_HOT: - printk(TPACPI_CRIT - "THERMAL ALARM: battery is too hot!\n"); - /* recommended action: warn user through gui */ - return true; - case TP_HKEY_EV_ALARM_BAT_XHOT: - printk(TPACPI_ALERT - "THERMAL EMERGENCY: battery is extremely hot!\n"); - /* recommended action: immediate sleep/hibernate */ - return true; - case TP_HKEY_EV_ALARM_SENSOR_HOT: - printk(TPACPI_CRIT - "THERMAL ALARM: " - "a sensor reports something is too hot!\n"); - /* recommended action: warn user through gui, that */ - /* some internal component is too hot */ - return true; - case TP_HKEY_EV_ALARM_SENSOR_XHOT: - printk(TPACPI_ALERT - "THERMAL EMERGENCY: " - "a sensor reports something is extremely hot!\n"); - /* recommended action: immediate sleep/hibernate */ - return true; case TP_HKEY_EV_THM_TABLE_CHANGED: printk(TPACPI_INFO "EC reports that Thermal Table has changed\n"); /* recommended action: do nothing, we don't have * Lenovo ATM information */ return true; + case TP_HKEY_EV_ALARM_BAT_HOT: + printk(TPACPI_CRIT + "THERMAL ALARM: battery is too hot!\n"); + /* recommended action: warn user through gui */ + break; + case TP_HKEY_EV_ALARM_BAT_XHOT: + printk(TPACPI_ALERT + "THERMAL EMERGENCY: battery is extremely hot!\n"); + /* recommended action: immediate sleep/hibernate */ + break; + case TP_HKEY_EV_ALARM_SENSOR_HOT: + printk(TPACPI_CRIT + "THERMAL ALARM: " + "a sensor reports something is too hot!\n"); + /* recommended action: warn user through gui, that */ + /* some internal component is too hot */ + break; + case TP_HKEY_EV_ALARM_SENSOR_XHOT: + printk(TPACPI_ALERT + "THERMAL EMERGENCY: " + "a sensor reports something is extremely hot!\n"); + /* recommended action: immediate sleep/hibernate */ + break; default: printk(TPACPI_ALERT "THERMAL ALERT: unknown thermal alarm received\n"); - return false; + known = false; } + + thermal_dump_all_sensors(); + + return known; } static void hotkey_notify(struct ibm_struct *ibm, u32 event) @@ -5462,8 +5470,11 @@ enum { /* TPACPI_THERMAL_TPEC_* */ TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */ TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */ TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */ + + TPACPI_THERMAL_SENSOR_NA = -128000, /* Sensor not available */ }; + #define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ struct ibm_thermal_sensors_struct { s32 temp[TPACPI_MAX_THERMAL_SENSORS]; @@ -5553,6 +5564,28 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) return n; } +static void thermal_dump_all_sensors(void) +{ + int n, i; + struct ibm_thermal_sensors_struct t; + + n = thermal_get_sensors(&t); + if (n <= 0) + return; + + printk(TPACPI_NOTICE + "temperatures (Celsius):"); + + for (i = 0; i < n; i++) { + if (t.temp[i] != TPACPI_THERMAL_SENSOR_NA) + printk(KERN_CONT " %d", (int)(t.temp[i] / 1000)); + else + printk(KERN_CONT " N/A"); + } + + printk(KERN_CONT "\n"); +} + /* sysfs temp##_input -------------------------------------------------- */ static ssize_t thermal_temp_input_show(struct device *dev, @@ -5568,7 +5601,7 @@ static ssize_t thermal_temp_input_show(struct device *dev, res = thermal_get_sensor(idx, &value); if (res) return res; - if (value == TP_EC_THERMAL_TMP_NA * 1000) + if (value == TPACPI_THERMAL_SENSOR_NA) return -ENXIO; return snprintf(buf, PAGE_SIZE, "%d\n", value); From 792979c8032b8f5adb77ea986db7082fff04c8e7 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Wed, 9 Dec 2009 01:36:29 +0000 Subject: [PATCH 09/17] thinkpad-acpi: use input_set_capability Use input_set_capability() instead of set_bit. Signed-off-by: Henrique de Moraes Holschuh Cc: Dmitry Torokhov Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index b3e256b2cf16..53d6c3318069 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3348,16 +3348,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) TPACPI_HOTKEY_MAP_SIZE); } - set_bit(EV_KEY, tpacpi_inputdev->evbit); - set_bit(EV_MSC, tpacpi_inputdev->evbit); - set_bit(MSC_SCAN, tpacpi_inputdev->mscbit); + input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN); tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE; tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN; tpacpi_inputdev->keycode = hotkey_keycode_map; for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) { if (hotkey_keycode_map[i] != KEY_RESERVED) { - set_bit(hotkey_keycode_map[i], - tpacpi_inputdev->keybit); + input_set_capability(tpacpi_inputdev, EV_KEY, + hotkey_keycode_map[i]); } else { if (i < sizeof(hotkey_reserved_mask)*8) hotkey_reserved_mask |= 1 << i; @@ -3365,12 +3363,10 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) } if (tp_features.hotkey_wlsw) { - set_bit(EV_SW, tpacpi_inputdev->evbit); - set_bit(SW_RFKILL_ALL, tpacpi_inputdev->swbit); + input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL); } if (tp_features.hotkey_tablet) { - set_bit(EV_SW, tpacpi_inputdev->evbit); - set_bit(SW_TABLET_MODE, tpacpi_inputdev->swbit); + input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE); } /* Do not issue duplicate brightness change events to From d89a727aff649f6768f7a34ee57f031ebf8bab4c Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 15 Dec 2009 21:51:06 -0200 Subject: [PATCH 10/17] thinkpad-acpi: sync input device EV_SW initial state 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 Signed-off-by: Henrique de Moraes Holschuh Cc: Alan Jenkins Cc: Johannes Berg Cc: Dmitry Torokhov Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 53d6c3318069..3daf349ddc98 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; From 5451a923bbdcff6ae665947e120af7238b21a9d2 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 15 Dec 2009 21:51:07 -0200 Subject: [PATCH 11/17] thinkpad-acpi: log initial state of rfkill switches 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 Cc: Josip Rodin Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3daf349ddc98..05714abf5a87 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; } From 329e4e18dfdc552f36b0642a3de5ebfa96063666 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 15 Dec 2009 21:51:08 -0200 Subject: [PATCH 12/17] thinkpad-acpi: volume subdriver rewrite 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 Cc: Lorne Applebaum Cc: Matthew Garrett Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 22 +- drivers/platform/x86/thinkpad_acpi.c | 342 ++++++++++++++++++++---- 2 files changed, 300 insertions(+), 64 deletions(-) diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index f5056c7fb5be..96687d0106a9 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 +-------------- -This feature allows volume control on ThinkPad models which don't have -a hardware volume knob. The available commands are: +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: echo up >/proc/acpi/ibm/volume echo down >/proc/acpi/ibm/volume echo mute >/proc/acpi/ibm/volume echo 'level ' >/proc/acpi/ibm/volume -The number range is 0 to 15 although not all of them may be +The 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 05714abf5a87..a2f5312c6a4e 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 " - " ( is 0-15)\n"); + " ( 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; + + 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 (!acpi_ec_read(volume_offset, &level)) - return -EIO; - new_mute = mute = level & 0x40; - new_level = level = level & 0xf; - 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 */ - - cmos_cmd = (new_mute) ? - TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP; - - if (issue_thinkpad_cmos_command(cmos_cmd) || - !acpi_ec_write(volume_offset, level + new_mute)) - return -EIO; - } } - return 0; + 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; } 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 " \ From a112ceee673629afc204bf6b4a4828a6143a083f Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 15 Dec 2009 21:51:09 -0200 Subject: [PATCH 13/17] thinkpad-acpi: support MUTE-only ThinkPads 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 Cc: Lorne Applebaum Cc: Matthew Garrett Signed-off-by: Len Brown --- 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 96687d0106a9..bd87682103c1 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 ' >/proc/acpi/ibm/volume The 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 a2f5312c6a4e..4d909d5a0340 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 " + + 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 " " ( 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 " \ From c7ac6291ea7ebc568a1fce16fed87d102898f264 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 15 Dec 2009 21:51:10 -0200 Subject: [PATCH 14/17] thinkpad-acpi: disable volume control 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 Cc: Lorne Applebaum Cc: Matthew Garrett Signed-off-by: Len Brown --- 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 bd87682103c1..6a5814330e51 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 4d909d5a0340..2d74926913d2 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 " - " ( 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 " + " ( 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 " \ From 0d204c34e85d1d63e5fdd3e3192747daf0ee7ec1 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 15 Dec 2009 21:51:11 -0200 Subject: [PATCH 15/17] thinkpad-acpi: basic ALSA mixer support (v2) 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 and ideas from Matthew Garrett . Signed-off-by: Henrique de Moraes Holschuh Cc: Lorne Applebaum Cc: Matthew Garrett Signed-off-by: Len Brown --- 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 6a5814330e51..b4ed30cb1375 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 2d74926913d2..e0fbe73b8dff 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -76,6 +76,10 @@ #include #include +#include +#include +#include + #include #include @@ -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 " \ From 887965e6576a78f71b9b98dec43fd1c73becd2e8 Mon Sep 17 00:00:00 2001 From: Alexey Dobriyan Date: Tue, 15 Dec 2009 21:51:12 -0200 Subject: [PATCH 16/17] thinkpad-acpi: convert to seq_file Signed-off-by: Alexey Dobriyan Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 289 +++++++++++++-------------- 1 file changed, 134 insertions(+), 155 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index e0fbe73b8dff..c21c35e2dcc6 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -61,6 +61,7 @@ #include #include +#include #include #include #include @@ -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; - - 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; + return ibm->read(m); } -static int dispatch_procfs_write(struct file *file, - const char __user *userbuf, - unsigned long count, void *data) +static int dispatch_proc_open(struct inode *inode, struct file *file) { - struct ibm_struct *ibm = data; + return single_open(file, dispatch_proc_show, PDE(inode)->data); +} + +static ssize_t dispatch_proc_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *pos) +{ + 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, \n"); + seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask); + seq_printf(m, "commands:\tenable, disable, reset, \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 ( is 0-21)\n"); + seq_printf(m, "status:\t\tsupported\n"); + seq_printf(m, "commands:\t ( 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" " on, off, blink ( 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 ( is 0-17)\n"); + seq_printf(m, "status:\t\tsupported\n"); + seq_printf(m, "commands:\t ( 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 0x" + seq_printf(m, "commands:\t0x 0x" " ( is 00-ff, is 00-ff)\n"); - len += sprintf(p + len, "commands:\t0x " + seq_printf(m, "commands:\t0x " " ( is 00-ff, 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 " + seq_printf(m, "level:\t\t%d\n", level); + seq_printf(m, "commands:\tup, down\n"); + seq_printf(m, "commands:\tlevel " " ( 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 " " ( 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 "); + seq_printf(m, "commands:\tlevel "); switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN: - len += sprintf(p + len, " ( is 0-7)\n"); + seq_printf(m, " ( is 0-7)\n"); break; default: - len += sprintf(p + len, " ( is 0-7, " + seq_printf(m, " ( 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 ( " "is 0 (off), 1-120 (seconds))\n"); if (fan_control_commands & TPACPI_FAN_CMD_SPEED) - len += sprintf(p + len, "commands:\tspeed " + seq_printf(m, "commands:\tspeed " " ( 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; } From 5d2eb14d36723eba0b31ae208bc346835751e944 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 15 Dec 2009 21:51:13 -0200 Subject: [PATCH 17/17] thinkpad-acpi: bump version to 0.24 Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- 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 b4ed30cb1375..169091f75e6d 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 Henrique de Moraes Holschuh diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index c21c35e2dcc6..af2352517c99 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 /*