diff options
-rw-r--r-- | Documentation/laptops/thinkpad-acpi.txt | 58 | ||||
-rw-r--r-- | drivers/acpi/video.c | 8 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_si_intf.c | 4 | ||||
-rw-r--r-- | drivers/platform/x86/Kconfig | 28 | ||||
-rw-r--r-- | drivers/platform/x86/dell-wmi.c | 18 | ||||
-rw-r--r-- | drivers/platform/x86/hp-wmi.c | 9 | ||||
-rw-r--r-- | drivers/platform/x86/msi-wmi.c | 9 | ||||
-rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 59 | ||||
-rw-r--r-- | drivers/platform/x86/wmi.c | 36 |
9 files changed, 190 insertions, 39 deletions
diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index 169091f75e6d..75afa1229fd7 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -1092,8 +1092,8 @@ WARNING: its level up and down at every change. -Volume control --------------- +Volume control (Console Audio control) +-------------------------------------- procfs: /proc/acpi/ibm/volume ALSA: "ThinkPad Console Audio Control", default ID: "ThinkPadEC" @@ -1110,9 +1110,53 @@ 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: + +About the ThinkPad Console Audio control: + +ThinkPads have a built-in amplifier and muting circuit that drives the +console headphone and speakers. This circuit is after the main AC97 +or HDA mixer in the audio path, and under exclusive control of the +firmware. + +ThinkPads have three special hotkeys to interact with the console +audio control: volume up, volume down and mute. + +It is worth noting that the normal way the mute function works (on +ThinkPads that do not have a "mute LED") is: + +1. Press mute to mute. It will *always* mute, you can press it as + many times as you want, and the sound will remain mute. + +2. Press either volume key to unmute the ThinkPad (it will _not_ + change the volume, it will just unmute). + +This is a very superior design when compared to the cheap software-only +mute-toggle solution found on normal consumer laptops: you can be +absolutely sure the ThinkPad will not make noise if you press the mute +button, no matter the previous state. + +The IBM ThinkPads, and the earlier Lenovo ThinkPads have variable-gain +amplifiers driving the speakers and headphone output, and the firmware +also handles volume control for the headphone and speakers on these +ThinkPads without any help from the operating system (this volume +control stage exists after the main AC97 or HDA mixer in the audio +path). + +The newer Lenovo models only have firmware mute control, and depend on +the main HDA mixer to do volume control (which is done by the operating +system). In this case, the volume keys are filtered out for unmute +key press (there are some firmware bugs in this area) and delivered as +normal key presses to the operating system (thinkpad-acpi is not +involved). + + +The ThinkPad-ACPI volume control: + +The preferred way to interact with the Console Audio control is the +ALSA interface. + +The legacy procfs interface allows one to read the current state, +and if volume control is enabled, accepts the following commands: echo up >/proc/acpi/ibm/volume echo down >/proc/acpi/ibm/volume @@ -1121,12 +1165,10 @@ mute/unmute control. The available commands are: 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 +distinct. To unmute the volume after the mute command, use either the 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, diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index 05dff631591c..72e76b4b6538 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -999,8 +999,10 @@ static void acpi_video_device_find_cap(struct acpi_video_device *device) sprintf(name, "acpi_video%d", count++); device->backlight = backlight_device_register(name, NULL, device, &acpi_backlight_ops); - device->backlight->props.max_brightness = device->brightness->count-3; kfree(name); + if (IS_ERR(device->backlight)) + return; + device->backlight->props.max_brightness = device->brightness->count-3; result = sysfs_create_link(&device->backlight->dev.kobj, &device->dev->dev.kobj, "device"); @@ -1979,6 +1981,10 @@ acpi_video_switch_brightness(struct acpi_video_device *device, int event) unsigned long long level_current, level_next; int result = -EINVAL; + /* no warning message if acpi_backlight=vendor is used */ + if (!acpi_video_backlight_support()) + return 0; + if (!device->brightness) goto out; diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index 679cd08b80b4..176f1751237f 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -3204,7 +3204,7 @@ static __devinit int init_ipmi_si(void) #ifdef CONFIG_ACPI spmi_find_bmc(); #endif -#ifdef CONFIG_PNP +#ifdef CONFIG_ACPI pnp_register_driver(&ipmi_pnp_driver); #endif @@ -3330,7 +3330,7 @@ static __exit void cleanup_ipmi_si(void) #ifdef CONFIG_PCI pci_unregister_driver(&ipmi_pci_driver); #endif -#ifdef CONFIG_PNP +#ifdef CONFIG_ACPI pnp_unregister_driver(&ipmi_pnp_driver); #endif diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ec4faffe6b05..db32c25e3605 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -231,8 +231,36 @@ config THINKPAD_ACPI This driver was formerly known as ibm-acpi. + Extra functionality will be available if the rfkill (CONFIG_RFKILL) + and/or ALSA (CONFIG_SND) subsystems are available in the kernel. + Note that if you want ThinkPad-ACPI to be built-in instead of + modular, ALSA and rfkill will also have to be built-in. + If you have an IBM or Lenovo ThinkPad laptop, say Y or M here. +config THINKPAD_ACPI_ALSA_SUPPORT + bool "Console audio control ALSA interface" + depends on THINKPAD_ACPI + depends on SND + depends on SND = y || THINKPAD_ACPI = SND + default y + ---help--- + Enables monitoring of the built-in console audio output control + (headphone and speakers), which is operated by the mute and (in + some ThinkPad models) volume hotkeys. + + If this option is enabled, ThinkPad-ACPI will export an ALSA card + with a single read-only mixer control, which should be used for + on-screen-display feedback purposes by the Desktop Environment. + + Optionally, the driver will also allow software control (the + ALSA mixer will be made read-write). Please refer to the driver + documentation for details. + + All IBM models have both volume and mute control. Newer Lenovo + models only have mute control (the volume hotkeys are just normal + keys and volume control is done through the main HDA mixer). + config THINKPAD_ACPI_DEBUGFACILITIES bool "Maintainer debug facilities" depends on THINKPAD_ACPI diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 916ccb2b316c..1b1dddbd5744 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -202,8 +202,13 @@ static void dell_wmi_notify(u32 value, void *context) struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; static struct key_entry *key; union acpi_object *obj; + acpi_status status; - wmi_get_event_data(value, &response); + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + printk(KERN_INFO "dell-wmi: bad event status 0x%x\n", status); + return; + } obj = (union acpi_object *)response.pointer; @@ -323,8 +328,9 @@ static int __init dell_wmi_input_setup(void) static int __init dell_wmi_init(void) { int err; + acpi_status status; - if (wmi_has_guid(DELL_EVENT_GUID)) { + if (!wmi_has_guid(DELL_EVENT_GUID)) { printk(KERN_WARNING "dell-wmi: No known WMI GUID found\n"); return -ENODEV; } @@ -336,14 +342,14 @@ static int __init dell_wmi_init(void) if (err) return err; - err = wmi_install_notify_handler(DELL_EVENT_GUID, + status = wmi_install_notify_handler(DELL_EVENT_GUID, dell_wmi_notify, NULL); - if (err) { + if (ACPI_FAILURE(status)) { input_unregister_device(dell_wmi_input_dev); printk(KERN_ERR "dell-wmi: Unable to register notify handler - %d\n", - err); - return err; + status); + return -ENODEV; } return 0; diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 8781d8fa7a57..5b648f0c6075 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -338,8 +338,13 @@ static void hp_wmi_notify(u32 value, void *context) static struct key_entry *key; union acpi_object *obj; int eventcode; + acpi_status status; - wmi_get_event_data(value, &response); + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + printk(KERN_INFO "hp-wmi: bad event status 0x%x\n", status); + return; + } obj = (union acpi_object *)response.pointer; @@ -581,7 +586,7 @@ static int __init hp_wmi_init(void) if (wmi_has_guid(HPWMI_EVENT_GUID)) { err = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL); - if (!err) + if (ACPI_SUCCESS(err)) hp_wmi_input_setup(); } diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 7f77f908bb01..f5f70d4c6913 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -149,8 +149,13 @@ static void msi_wmi_notify(u32 value, void *context) static struct key_entry *key; union acpi_object *obj; ktime_t cur; + acpi_status status; - wmi_get_event_data(value, &response); + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + printk(KERN_INFO DRV_PFX "bad event status 0x%x\n", status); + return; + } obj = (union acpi_object *)response.pointer; @@ -236,7 +241,7 @@ static int __init msi_wmi_init(void) } err = wmi_install_notify_handler(MSIWMI_EVENT_GUID, msi_wmi_notify, NULL); - if (err) + if (ACPI_FAILURE(err)) return -EINVAL; err = msi_wmi_input_setup(); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 448c8aeb166b..e67e4feb35cb 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -6384,11 +6384,13 @@ static struct ibm_struct brightness_driver_data = { * and we leave them unchanged. */ +#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT + #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 int alsa_index = ~((1 << (SNDRV_CARDS - 3)) - 1); /* last three slots */ static char *alsa_id = "ThinkPadEC"; static int alsa_enable = SNDRV_DEFAULT_ENABLE1; @@ -6705,10 +6707,11 @@ static int __init volume_create_alsa_mixer(void) 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; + if (rc < 0 || !card) { + printk(TPACPI_ERR + "Failed to create ALSA card structures: %d\n", rc); + return 1; + } BUG_ON(!card->private_data); data = card->private_data; @@ -6741,8 +6744,9 @@ static int __init volume_create_alsa_mixer(void) rc = snd_ctl_add(card, ctl_vol); if (rc < 0) { printk(TPACPI_ERR - "Failed to create ALSA volume control\n"); - goto err_out; + "Failed to create ALSA volume control: %d\n", + rc); + goto err_exit; } data->ctl_vol_id = &ctl_vol->id; } @@ -6750,22 +6754,25 @@ static int __init volume_create_alsa_mixer(void) 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; + printk(TPACPI_ERR "Failed to create ALSA mute control: %d\n", + rc); + goto err_exit; } 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; + printk(TPACPI_ERR "Failed to register ALSA card: %d\n", rc); + goto err_exit; } alsa_card = card; - return rc; + return 0; + +err_exit: + snd_card_free(card); + return 1; } #define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */ @@ -7016,6 +7023,28 @@ static struct ibm_struct volume_driver_data = { .shutdown = volume_shutdown, }; +#else /* !CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */ + +#define alsa_card NULL + +static void inline volume_alsa_notify_change(void) +{ +} + +static int __init volume_init(struct ibm_init_struct *iibm) +{ + printk(TPACPI_INFO + "volume: disabled as there is no ALSA support in this kernel\n"); + + return 1; +} + +static struct ibm_struct volume_driver_data = { + .name = "volume", +}; + +#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */ + /************************************************************************* * Fan subdriver */ @@ -8738,6 +8767,7 @@ MODULE_PARM_DESC(hotkey_report_mode, "used for backwards compatibility with userspace, " "see documentation"); +#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT module_param_named(volume_mode, volume_mode, uint, 0444); MODULE_PARM_DESC(volume_mode, "Selects volume control strategy: " @@ -8760,6 +8790,7 @@ 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"); +#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */ #define TPACPI_PARAM(feature) \ module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 9f93d6c0f510..b104302fea0a 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -492,8 +492,7 @@ wmi_notify_handler handler, void *data) if (!guid || !handler) return AE_BAD_PARAMETER; - find_guid(guid, &block); - if (!block) + if (!find_guid(guid, &block)) return AE_NOT_EXIST; if (block->handler) @@ -521,8 +520,7 @@ acpi_status wmi_remove_notify_handler(const char *guid) if (!guid) return AE_BAD_PARAMETER; - find_guid(guid, &block); - if (!block) + if (!find_guid(guid, &block)) return AE_NOT_EXIST; if (!block->handler) @@ -716,6 +714,22 @@ static int wmi_class_init(void) return ret; } +static bool guid_already_parsed(const char *guid_string) +{ + struct guid_block *gblock; + struct wmi_block *wblock; + struct list_head *p; + + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + gblock = &wblock->gblock; + + if (strncmp(gblock->guid, guid_string, 16) == 0) + return true; + } + return false; +} + /* * Parse the _WDG method for the GUID data blocks */ @@ -725,6 +739,7 @@ static __init acpi_status parse_wdg(acpi_handle handle) union acpi_object *obj; struct guid_block *gblock; struct wmi_block *wblock; + char guid_string[37]; acpi_status status; u32 i, total; @@ -747,6 +762,19 @@ static __init acpi_status parse_wdg(acpi_handle handle) memcpy(gblock, obj->buffer.pointer, obj->buffer.length); for (i = 0; i < total; i++) { + /* + Some WMI devices, like those for nVidia hooks, have a + duplicate GUID. It's not clear what we should do in this + case yet, so for now, we'll just ignore the duplicate. + Anyone who wants to add support for that device can come + up with a better workaround for the mess then. + */ + if (guid_already_parsed(gblock[i].guid) == true) { + wmi_gtoa(gblock[i].guid, guid_string); + printk(KERN_INFO PREFIX "Skipping duplicate GUID %s\n", + guid_string); + continue; + } wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); if (!wblock) return AE_NO_MEMORY; |