diff options
| author | Henrique de Moraes Holschuh <hmh@hmh.eng.br> | 2009-12-15 18:51:11 -0500 |
|---|---|---|
| committer | Len Brown <len.brown@intel.com> | 2009-12-15 23:57:44 -0500 |
| commit | 0d204c34e85d1d63e5fdd3e3192747daf0ee7ec1 (patch) | |
| tree | cdf93247a1e8cb673f0a3fcae5c193e1d55d3fc3 | |
| parent | c7ac6291ea7ebc568a1fce16fed87d102898f264 (diff) | |
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 <lorne.applebaum@gmail.com>
and ideas from Matthew Garrett <mjg@redhat.com>.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Lorne Applebaum <lorne.applebaum@gmail.com>
Cc: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Len Brown <len.brown@intel.com>
| -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 " \ |
