diff options
-rw-r--r-- | Documentation/laptops/thinkpad-acpi.txt | 7 | ||||
-rw-r--r-- | 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 | |||
1096 | -------------- | 1096 | -------------- |
1097 | 1097 | ||
1098 | procfs: /proc/acpi/ibm/volume | 1098 | procfs: /proc/acpi/ibm/volume |
1099 | ALSA: "ThinkPad Console Audio Control", default ID: "ThinkPadEC" | ||
1099 | 1100 | ||
1100 | NOTE: by default, the volume control interface operates in read-only | 1101 | NOTE: by default, the volume control interface operates in read-only |
1101 | mode, as it is supposed to be used for on-screen-display purposes. | 1102 | 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 | |||
1144 | work well on your ThinkPad model, please report this to | 1145 | work well on your ThinkPad model, please report this to |
1145 | ibm-acpi-devel@lists.sourceforge.net. | 1146 | ibm-acpi-devel@lists.sourceforge.net. |
1146 | 1147 | ||
1147 | The ALSA mixer interface to this feature is still missing, but patches | 1148 | The driver supports the standard ALSA module parameters. If the ALSA |
1148 | to add it exist. That problem should be addressed in the not so | 1149 | mixer is disabled, the driver will disable all volume functionality. |
1149 | distant future. | ||
1150 | 1150 | ||
1151 | 1151 | ||
1152 | Fan control and monitoring: fan speed, fan enable/disable | 1152 | Fan control and monitoring: fan speed, fan enable/disable |
@@ -1478,3 +1478,4 @@ Sysfs interface changelog: | |||
1478 | 1478 | ||
1479 | 0x020700: Support for mute-only mixers. | 1479 | 0x020700: Support for mute-only mixers. |
1480 | Volume control in read-only mode by default. | 1480 | Volume control in read-only mode by default. |
1481 | 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 @@ | |||
76 | #include <linux/jiffies.h> | 76 | #include <linux/jiffies.h> |
77 | #include <linux/workqueue.h> | 77 | #include <linux/workqueue.h> |
78 | 78 | ||
79 | #include <sound/core.h> | ||
80 | #include <sound/control.h> | ||
81 | #include <sound/initval.h> | ||
82 | |||
79 | #include <acpi/acpi_drivers.h> | 83 | #include <acpi/acpi_drivers.h> |
80 | 84 | ||
81 | #include <linux/pci_ids.h> | 85 | #include <linux/pci_ids.h> |
@@ -6402,6 +6406,22 @@ static struct ibm_struct brightness_driver_data = { | |||
6402 | * and we leave them unchanged. | 6406 | * and we leave them unchanged. |
6403 | */ | 6407 | */ |
6404 | 6408 | ||
6409 | #define TPACPI_ALSA_DRVNAME "ThinkPad EC" | ||
6410 | #define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control" | ||
6411 | #define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME | ||
6412 | |||
6413 | static int alsa_index = SNDRV_DEFAULT_IDX1; | ||
6414 | static char *alsa_id = "ThinkPadEC"; | ||
6415 | static int alsa_enable = SNDRV_DEFAULT_ENABLE1; | ||
6416 | |||
6417 | struct tpacpi_alsa_data { | ||
6418 | struct snd_card *card; | ||
6419 | struct snd_ctl_elem_id *ctl_mute_id; | ||
6420 | struct snd_ctl_elem_id *ctl_vol_id; | ||
6421 | }; | ||
6422 | |||
6423 | static struct snd_card *alsa_card; | ||
6424 | |||
6405 | enum { | 6425 | enum { |
6406 | TP_EC_AUDIO = 0x30, | 6426 | TP_EC_AUDIO = 0x30, |
6407 | 6427 | ||
@@ -6584,11 +6604,104 @@ static int volume_set_volume(const u8 vol) | |||
6584 | return volume_set_volume_ec(vol); | 6604 | return volume_set_volume_ec(vol); |
6585 | } | 6605 | } |
6586 | 6606 | ||
6607 | static void volume_alsa_notify_change(void) | ||
6608 | { | ||
6609 | struct tpacpi_alsa_data *d; | ||
6610 | |||
6611 | if (alsa_card && alsa_card->private_data) { | ||
6612 | d = alsa_card->private_data; | ||
6613 | if (d->ctl_mute_id) | ||
6614 | snd_ctl_notify(alsa_card, | ||
6615 | SNDRV_CTL_EVENT_MASK_VALUE, | ||
6616 | d->ctl_mute_id); | ||
6617 | if (d->ctl_vol_id) | ||
6618 | snd_ctl_notify(alsa_card, | ||
6619 | SNDRV_CTL_EVENT_MASK_VALUE, | ||
6620 | d->ctl_vol_id); | ||
6621 | } | ||
6622 | } | ||
6623 | |||
6624 | static int volume_alsa_vol_info(struct snd_kcontrol *kcontrol, | ||
6625 | struct snd_ctl_elem_info *uinfo) | ||
6626 | { | ||
6627 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
6628 | uinfo->count = 1; | ||
6629 | uinfo->value.integer.min = 0; | ||
6630 | uinfo->value.integer.max = TP_EC_VOLUME_MAX; | ||
6631 | return 0; | ||
6632 | } | ||
6633 | |||
6634 | static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol, | ||
6635 | struct snd_ctl_elem_value *ucontrol) | ||
6636 | { | ||
6637 | u8 s; | ||
6638 | int rc; | ||
6639 | |||
6640 | rc = volume_get_status(&s); | ||
6641 | if (rc < 0) | ||
6642 | return rc; | ||
6643 | |||
6644 | ucontrol->value.integer.value[0] = s & TP_EC_AUDIO_LVL_MSK; | ||
6645 | return 0; | ||
6646 | } | ||
6647 | |||
6648 | static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol, | ||
6649 | struct snd_ctl_elem_value *ucontrol) | ||
6650 | { | ||
6651 | return volume_set_volume(ucontrol->value.integer.value[0]); | ||
6652 | } | ||
6653 | |||
6654 | #define volume_alsa_mute_info snd_ctl_boolean_mono_info | ||
6655 | |||
6656 | static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol, | ||
6657 | struct snd_ctl_elem_value *ucontrol) | ||
6658 | { | ||
6659 | u8 s; | ||
6660 | int rc; | ||
6661 | |||
6662 | rc = volume_get_status(&s); | ||
6663 | if (rc < 0) | ||
6664 | return rc; | ||
6665 | |||
6666 | ucontrol->value.integer.value[0] = | ||
6667 | (s & TP_EC_AUDIO_MUTESW_MSK) ? 0 : 1; | ||
6668 | return 0; | ||
6669 | } | ||
6670 | |||
6671 | static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol, | ||
6672 | struct snd_ctl_elem_value *ucontrol) | ||
6673 | { | ||
6674 | return volume_set_mute(!ucontrol->value.integer.value[0]); | ||
6675 | } | ||
6676 | |||
6677 | static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = { | ||
6678 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
6679 | .name = "Console Playback Volume", | ||
6680 | .index = 0, | ||
6681 | .access = SNDRV_CTL_ELEM_ACCESS_READ, | ||
6682 | .info = volume_alsa_vol_info, | ||
6683 | .get = volume_alsa_vol_get, | ||
6684 | }; | ||
6685 | |||
6686 | static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = { | ||
6687 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
6688 | .name = "Console Playback Switch", | ||
6689 | .index = 0, | ||
6690 | .access = SNDRV_CTL_ELEM_ACCESS_READ, | ||
6691 | .info = volume_alsa_mute_info, | ||
6692 | .get = volume_alsa_mute_get, | ||
6693 | }; | ||
6694 | |||
6587 | static void volume_suspend(pm_message_t state) | 6695 | static void volume_suspend(pm_message_t state) |
6588 | { | 6696 | { |
6589 | tpacpi_volume_checkpoint_nvram(); | 6697 | tpacpi_volume_checkpoint_nvram(); |
6590 | } | 6698 | } |
6591 | 6699 | ||
6700 | static void volume_resume(void) | ||
6701 | { | ||
6702 | volume_alsa_notify_change(); | ||
6703 | } | ||
6704 | |||
6592 | static void volume_shutdown(void) | 6705 | static void volume_shutdown(void) |
6593 | { | 6706 | { |
6594 | tpacpi_volume_checkpoint_nvram(); | 6707 | tpacpi_volume_checkpoint_nvram(); |
@@ -6596,9 +6709,87 @@ static void volume_shutdown(void) | |||
6596 | 6709 | ||
6597 | static void volume_exit(void) | 6710 | static void volume_exit(void) |
6598 | { | 6711 | { |
6712 | if (alsa_card) { | ||
6713 | snd_card_free(alsa_card); | ||
6714 | alsa_card = NULL; | ||
6715 | } | ||
6716 | |||
6599 | tpacpi_volume_checkpoint_nvram(); | 6717 | tpacpi_volume_checkpoint_nvram(); |
6600 | } | 6718 | } |
6601 | 6719 | ||
6720 | static int __init volume_create_alsa_mixer(void) | ||
6721 | { | ||
6722 | struct snd_card *card; | ||
6723 | struct tpacpi_alsa_data *data; | ||
6724 | struct snd_kcontrol *ctl_vol; | ||
6725 | struct snd_kcontrol *ctl_mute; | ||
6726 | int rc; | ||
6727 | |||
6728 | rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE, | ||
6729 | sizeof(struct tpacpi_alsa_data), &card); | ||
6730 | if (rc < 0) | ||
6731 | return rc; | ||
6732 | if (!card) | ||
6733 | return -ENOMEM; | ||
6734 | |||
6735 | BUG_ON(!card->private_data); | ||
6736 | data = card->private_data; | ||
6737 | data->card = card; | ||
6738 | |||
6739 | strlcpy(card->driver, TPACPI_ALSA_DRVNAME, | ||
6740 | sizeof(card->driver)); | ||
6741 | strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME, | ||
6742 | sizeof(card->shortname)); | ||
6743 | snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s", | ||
6744 | (thinkpad_id.ec_version_str) ? | ||
6745 | thinkpad_id.ec_version_str : "(unknown)"); | ||
6746 | snprintf(card->longname, sizeof(card->longname), | ||
6747 | "%s at EC reg 0x%02x, fw %s", card->shortname, TP_EC_AUDIO, | ||
6748 | (thinkpad_id.ec_version_str) ? | ||
6749 | thinkpad_id.ec_version_str : "unknown"); | ||
6750 | |||
6751 | if (volume_control_allowed) { | ||
6752 | volume_alsa_control_vol.put = volume_alsa_vol_put; | ||
6753 | volume_alsa_control_vol.access = | ||
6754 | SNDRV_CTL_ELEM_ACCESS_READWRITE; | ||
6755 | |||
6756 | volume_alsa_control_mute.put = volume_alsa_mute_put; | ||
6757 | volume_alsa_control_mute.access = | ||
6758 | SNDRV_CTL_ELEM_ACCESS_READWRITE; | ||
6759 | } | ||
6760 | |||
6761 | if (!tp_features.mixer_no_level_control) { | ||
6762 | ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL); | ||
6763 | rc = snd_ctl_add(card, ctl_vol); | ||
6764 | if (rc < 0) { | ||
6765 | printk(TPACPI_ERR | ||
6766 | "Failed to create ALSA volume control\n"); | ||
6767 | goto err_out; | ||
6768 | } | ||
6769 | data->ctl_vol_id = &ctl_vol->id; | ||
6770 | } | ||
6771 | |||
6772 | ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL); | ||
6773 | rc = snd_ctl_add(card, ctl_mute); | ||
6774 | if (rc < 0) { | ||
6775 | printk(TPACPI_ERR "Failed to create ALSA mute control\n"); | ||
6776 | goto err_out; | ||
6777 | } | ||
6778 | data->ctl_mute_id = &ctl_mute->id; | ||
6779 | |||
6780 | snd_card_set_dev(card, &tpacpi_pdev->dev); | ||
6781 | rc = snd_card_register(card); | ||
6782 | |||
6783 | err_out: | ||
6784 | if (rc < 0) { | ||
6785 | snd_card_free(card); | ||
6786 | card = NULL; | ||
6787 | } | ||
6788 | |||
6789 | alsa_card = card; | ||
6790 | return rc; | ||
6791 | } | ||
6792 | |||
6602 | #define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */ | 6793 | #define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */ |
6603 | #define TPACPI_VOL_Q_LEVEL 0x0002 /* Volume control available */ | 6794 | #define TPACPI_VOL_Q_LEVEL 0x0002 /* Volume control available */ |
6604 | 6795 | ||
@@ -6628,6 +6819,7 @@ static const struct tpacpi_quirk volume_quirk_table[] __initconst = { | |||
6628 | static int __init volume_init(struct ibm_init_struct *iibm) | 6819 | static int __init volume_init(struct ibm_init_struct *iibm) |
6629 | { | 6820 | { |
6630 | unsigned long quirks; | 6821 | unsigned long quirks; |
6822 | int rc; | ||
6631 | 6823 | ||
6632 | vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n"); | 6824 | vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n"); |
6633 | 6825 | ||
@@ -6651,6 +6843,17 @@ static int __init volume_init(struct ibm_init_struct *iibm) | |||
6651 | if (volume_capabilities >= TPACPI_VOL_CAP_MAX) | 6843 | if (volume_capabilities >= TPACPI_VOL_CAP_MAX) |
6652 | return -EINVAL; | 6844 | return -EINVAL; |
6653 | 6845 | ||
6846 | /* | ||
6847 | * The ALSA mixer is our primary interface. | ||
6848 | * When disabled, don't install the subdriver at all | ||
6849 | */ | ||
6850 | if (!alsa_enable) { | ||
6851 | vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, | ||
6852 | "ALSA mixer disabled by parameter, " | ||
6853 | "not loading volume subdriver...\n"); | ||
6854 | return 1; | ||
6855 | } | ||
6856 | |||
6654 | quirks = tpacpi_check_quirks(volume_quirk_table, | 6857 | quirks = tpacpi_check_quirks(volume_quirk_table, |
6655 | ARRAY_SIZE(volume_quirk_table)); | 6858 | ARRAY_SIZE(volume_quirk_table)); |
6656 | 6859 | ||
@@ -6695,12 +6898,26 @@ static int __init volume_init(struct ibm_init_struct *iibm) | |||
6695 | "mute is supported, volume control is %s\n", | 6898 | "mute is supported, volume control is %s\n", |
6696 | str_supported(!tp_features.mixer_no_level_control)); | 6899 | str_supported(!tp_features.mixer_no_level_control)); |
6697 | 6900 | ||
6901 | rc = volume_create_alsa_mixer(); | ||
6902 | if (rc) { | ||
6903 | printk(TPACPI_ERR | ||
6904 | "Could not create the ALSA mixer interface\n"); | ||
6905 | return rc; | ||
6906 | } | ||
6907 | |||
6698 | printk(TPACPI_INFO | 6908 | printk(TPACPI_INFO |
6699 | "Console audio control enabled, mode: %s\n", | 6909 | "Console audio control enabled, mode: %s\n", |
6700 | (volume_control_allowed) ? | 6910 | (volume_control_allowed) ? |
6701 | "override (read/write)" : | 6911 | "override (read/write)" : |
6702 | "monitor (read only)"); | 6912 | "monitor (read only)"); |
6703 | 6913 | ||
6914 | vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, | ||
6915 | "registering volume hotkeys as change notification\n"); | ||
6916 | tpacpi_hotkey_driver_mask_set(hotkey_driver_mask | ||
6917 | | TP_ACPI_HKEY_VOLUP_MASK | ||
6918 | | TP_ACPI_HKEY_VOLDWN_MASK | ||
6919 | | TP_ACPI_HKEY_MUTE_MASK); | ||
6920 | |||
6704 | return 0; | 6921 | return 0; |
6705 | } | 6922 | } |
6706 | 6923 | ||
@@ -6807,6 +7024,7 @@ static int volume_write(char *buf) | |||
6807 | new_mute ? "" : "un", new_level); | 7024 | new_mute ? "" : "un", new_level); |
6808 | rc = volume_set_status(new_mute | new_level); | 7025 | rc = volume_set_status(new_mute | new_level); |
6809 | } | 7026 | } |
7027 | volume_alsa_notify_change(); | ||
6810 | 7028 | ||
6811 | return (rc == -EINTR) ? -ERESTARTSYS : rc; | 7029 | return (rc == -EINTR) ? -ERESTARTSYS : rc; |
6812 | } | 7030 | } |
@@ -6817,6 +7035,7 @@ static struct ibm_struct volume_driver_data = { | |||
6817 | .write = volume_write, | 7035 | .write = volume_write, |
6818 | .exit = volume_exit, | 7036 | .exit = volume_exit, |
6819 | .suspend = volume_suspend, | 7037 | .suspend = volume_suspend, |
7038 | .resume = volume_resume, | ||
6820 | .shutdown = volume_shutdown, | 7039 | .shutdown = volume_shutdown, |
6821 | }; | 7040 | }; |
6822 | 7041 | ||
@@ -8115,10 +8334,16 @@ static void tpacpi_driver_event(const unsigned int hkey_event) | |||
8115 | tpacpi_brightness_notify_change(); | 8334 | tpacpi_brightness_notify_change(); |
8116 | } | 8335 | } |
8117 | } | 8336 | } |
8337 | if (alsa_card) { | ||
8338 | switch (hkey_event) { | ||
8339 | case TP_HKEY_EV_VOL_UP: | ||
8340 | case TP_HKEY_EV_VOL_DOWN: | ||
8341 | case TP_HKEY_EV_VOL_MUTE: | ||
8342 | volume_alsa_notify_change(); | ||
8343 | } | ||
8344 | } | ||
8118 | } | 8345 | } |
8119 | 8346 | ||
8120 | |||
8121 | |||
8122 | static void hotkey_driver_event(const unsigned int scancode) | 8347 | static void hotkey_driver_event(const unsigned int scancode) |
8123 | { | 8348 | { |
8124 | tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode); | 8349 | tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode); |
@@ -8552,6 +8777,14 @@ MODULE_PARM_DESC(volume_control, | |||
8552 | "Enables software override for the console audio " | 8777 | "Enables software override for the console audio " |
8553 | "control when true"); | 8778 | "control when true"); |
8554 | 8779 | ||
8780 | /* ALSA module API parameters */ | ||
8781 | module_param_named(index, alsa_index, int, 0444); | ||
8782 | MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer"); | ||
8783 | module_param_named(id, alsa_id, charp, 0444); | ||
8784 | MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer"); | ||
8785 | module_param_named(enable, alsa_enable, bool, 0444); | ||
8786 | MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer"); | ||
8787 | |||
8555 | #define TPACPI_PARAM(feature) \ | 8788 | #define TPACPI_PARAM(feature) \ |
8556 | module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ | 8789 | module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ |
8557 | MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \ | 8790 | MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \ |