diff options
-rw-r--r-- | Documentation/laptops/thinkpad-acpi.txt | 22 | ||||
-rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 340 |
2 files changed, 299 insertions, 63 deletions
diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index f5056c7fb5be..96687d0106a9 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt | |||
@@ -1092,22 +1092,33 @@ WARNING: | |||
1092 | its level up and down at every change. | 1092 | its level up and down at every change. |
1093 | 1093 | ||
1094 | 1094 | ||
1095 | Volume control -- /proc/acpi/ibm/volume | 1095 | Volume control |
1096 | --------------------------------------- | 1096 | -------------- |
1097 | |||
1098 | procfs: /proc/acpi/ibm/volume | ||
1097 | 1099 | ||
1098 | This feature allows volume control on ThinkPad models which don't have | 1100 | This feature allows volume control on ThinkPad models with a digital |
1099 | a hardware volume knob. The available commands are: | 1101 | volume knob, as well as mute/unmute control. The available commands are: |
1100 | 1102 | ||
1101 | echo up >/proc/acpi/ibm/volume | 1103 | echo up >/proc/acpi/ibm/volume |
1102 | echo down >/proc/acpi/ibm/volume | 1104 | echo down >/proc/acpi/ibm/volume |
1103 | echo mute >/proc/acpi/ibm/volume | 1105 | echo mute >/proc/acpi/ibm/volume |
1104 | echo 'level <level>' >/proc/acpi/ibm/volume | 1106 | echo 'level <level>' >/proc/acpi/ibm/volume |
1105 | 1107 | ||
1106 | The <level> number range is 0 to 15 although not all of them may be | 1108 | The <level> number range is 0 to 14 although not all of them may be |
1107 | distinct. The unmute the volume after the mute command, use either the | 1109 | distinct. The unmute the volume after the mute command, use either the |
1108 | up or down command (the level command will not unmute the volume). | 1110 | up or down command (the level command will not unmute the volume). |
1109 | The current volume level and mute state is shown in the file. | 1111 | The current volume level and mute state is shown in the file. |
1110 | 1112 | ||
1113 | There are two strategies for volume control. To select which one | ||
1114 | should be used, use the volume_mode module parameter: volume_mode=1 | ||
1115 | selects EC mode, and volume_mode=3 selects EC mode with NVRAM backing | ||
1116 | (so that volume/mute changes are remembered across shutdown/reboot). | ||
1117 | |||
1118 | The driver will operate in volume_mode=3 by default. If that does not | ||
1119 | work well on your ThinkPad model, please report this to | ||
1120 | ibm-acpi-devel@lists.sourceforge.net. | ||
1121 | |||
1111 | The ALSA mixer interface to this feature is still missing, but patches | 1122 | The ALSA mixer interface to this feature is still missing, but patches |
1112 | to add it exist. That problem should be addressed in the not so | 1123 | to add it exist. That problem should be addressed in the not so |
1113 | distant future. | 1124 | distant future. |
@@ -1376,6 +1387,7 @@ to enable more than one output class, just add their values. | |||
1376 | 0x0008 HKEY event interface, hotkeys | 1387 | 0x0008 HKEY event interface, hotkeys |
1377 | 0x0010 Fan control | 1388 | 0x0010 Fan control |
1378 | 0x0020 Backlight brightness | 1389 | 0x0020 Backlight brightness |
1390 | 0x0040 Audio mixer/volume control | ||
1379 | 1391 | ||
1380 | There is also a kernel build option to enable more debugging | 1392 | There is also a kernel build option to enable more debugging |
1381 | information, which may be necessary to debug driver problems. | 1393 | 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 { | |||
231 | #define TPACPI_DBG_HKEY 0x0008 | 231 | #define TPACPI_DBG_HKEY 0x0008 |
232 | #define TPACPI_DBG_FAN 0x0010 | 232 | #define TPACPI_DBG_FAN 0x0010 |
233 | #define TPACPI_DBG_BRGHT 0x0020 | 233 | #define TPACPI_DBG_BRGHT 0x0020 |
234 | #define TPACPI_DBG_MIXER 0x0040 | ||
234 | 235 | ||
235 | #define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off") | 236 | #define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off") |
236 | #define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") | 237 | #define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") |
@@ -6375,21 +6376,260 @@ static struct ibm_struct brightness_driver_data = { | |||
6375 | * Volume subdriver | 6376 | * Volume subdriver |
6376 | */ | 6377 | */ |
6377 | 6378 | ||
6378 | static int volume_offset = 0x30; | 6379 | /* |
6380 | * IBM ThinkPads have a simple volume controller with MUTE gating. | ||
6381 | * Very early Lenovo ThinkPads follow the IBM ThinkPad spec. | ||
6382 | * | ||
6383 | * Since the *61 series (and probably also the later *60 series), Lenovo | ||
6384 | * ThinkPads only implement the MUTE gate. | ||
6385 | * | ||
6386 | * EC register 0x30 | ||
6387 | * Bit 6: MUTE (1 mutes sound) | ||
6388 | * Bit 3-0: Volume | ||
6389 | * Other bits should be zero as far as we know. | ||
6390 | * | ||
6391 | * This is also stored in CMOS NVRAM, byte 0x60, bit 6 (MUTE), and | ||
6392 | * bits 3-0 (volume). Other bits in NVRAM may have other functions, | ||
6393 | * such as bit 7 which is used to detect repeated presses of MUTE, | ||
6394 | * and we leave them unchanged. | ||
6395 | */ | ||
6396 | |||
6397 | enum { | ||
6398 | TP_EC_AUDIO = 0x30, | ||
6399 | |||
6400 | /* TP_EC_AUDIO bits */ | ||
6401 | TP_EC_AUDIO_MUTESW = 6, | ||
6402 | |||
6403 | /* TP_EC_AUDIO bitmasks */ | ||
6404 | TP_EC_AUDIO_LVL_MSK = 0x0F, | ||
6405 | TP_EC_AUDIO_MUTESW_MSK = (1 << TP_EC_AUDIO_MUTESW), | ||
6406 | |||
6407 | /* Maximum volume */ | ||
6408 | TP_EC_VOLUME_MAX = 14, | ||
6409 | }; | ||
6410 | |||
6411 | enum tpacpi_volume_access_mode { | ||
6412 | TPACPI_VOL_MODE_AUTO = 0, /* Not implemented yet */ | ||
6413 | TPACPI_VOL_MODE_EC, /* Pure EC control */ | ||
6414 | TPACPI_VOL_MODE_UCMS_STEP, /* UCMS step-based control: N/A */ | ||
6415 | TPACPI_VOL_MODE_ECNVRAM, /* EC control w/ NVRAM store */ | ||
6416 | TPACPI_VOL_MODE_MAX | ||
6417 | }; | ||
6418 | |||
6419 | static enum tpacpi_volume_access_mode volume_mode = | ||
6420 | TPACPI_VOL_MODE_MAX; | ||
6421 | |||
6422 | |||
6423 | /* | ||
6424 | * Used to syncronize writers to TP_EC_AUDIO and | ||
6425 | * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write | ||
6426 | */ | ||
6427 | static struct mutex volume_mutex; | ||
6428 | |||
6429 | static void tpacpi_volume_checkpoint_nvram(void) | ||
6430 | { | ||
6431 | u8 lec = 0; | ||
6432 | u8 b_nvram; | ||
6433 | const u8 ec_mask = TP_EC_AUDIO_LVL_MSK | TP_EC_AUDIO_MUTESW_MSK; | ||
6434 | |||
6435 | if (volume_mode != TPACPI_VOL_MODE_ECNVRAM) | ||
6436 | return; | ||
6437 | |||
6438 | vdbg_printk(TPACPI_DBG_MIXER, | ||
6439 | "trying to checkpoint mixer state to NVRAM...\n"); | ||
6440 | |||
6441 | if (mutex_lock_killable(&volume_mutex) < 0) | ||
6442 | return; | ||
6443 | |||
6444 | if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec))) | ||
6445 | goto unlock; | ||
6446 | lec &= ec_mask; | ||
6447 | b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER); | ||
6448 | |||
6449 | if (lec != (b_nvram & ec_mask)) { | ||
6450 | /* NVRAM needs update */ | ||
6451 | b_nvram &= ~ec_mask; | ||
6452 | b_nvram |= lec; | ||
6453 | nvram_write_byte(b_nvram, TP_NVRAM_ADDR_MIXER); | ||
6454 | dbg_printk(TPACPI_DBG_MIXER, | ||
6455 | "updated NVRAM mixer status to 0x%02x (0x%02x)\n", | ||
6456 | (unsigned int) lec, (unsigned int) b_nvram); | ||
6457 | } else { | ||
6458 | vdbg_printk(TPACPI_DBG_MIXER, | ||
6459 | "NVRAM mixer status already is 0x%02x (0x%02x)\n", | ||
6460 | (unsigned int) lec, (unsigned int) b_nvram); | ||
6461 | } | ||
6462 | |||
6463 | unlock: | ||
6464 | mutex_unlock(&volume_mutex); | ||
6465 | } | ||
6466 | |||
6467 | static int volume_get_status_ec(u8 *status) | ||
6468 | { | ||
6469 | u8 s; | ||
6470 | |||
6471 | if (!acpi_ec_read(TP_EC_AUDIO, &s)) | ||
6472 | return -EIO; | ||
6473 | |||
6474 | *status = s; | ||
6475 | |||
6476 | dbg_printk(TPACPI_DBG_MIXER, "status 0x%02x\n", s); | ||
6477 | |||
6478 | return 0; | ||
6479 | } | ||
6480 | |||
6481 | static int volume_get_status(u8 *status) | ||
6482 | { | ||
6483 | return volume_get_status_ec(status); | ||
6484 | } | ||
6485 | |||
6486 | static int volume_set_status_ec(const u8 status) | ||
6487 | { | ||
6488 | if (!acpi_ec_write(TP_EC_AUDIO, status)) | ||
6489 | return -EIO; | ||
6490 | |||
6491 | dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status); | ||
6492 | |||
6493 | return 0; | ||
6494 | } | ||
6495 | |||
6496 | static int volume_set_status(const u8 status) | ||
6497 | { | ||
6498 | return volume_set_status_ec(status); | ||
6499 | } | ||
6500 | |||
6501 | static int volume_set_mute_ec(const bool mute) | ||
6502 | { | ||
6503 | int rc; | ||
6504 | u8 s, n; | ||
6505 | |||
6506 | if (mutex_lock_killable(&volume_mutex) < 0) | ||
6507 | return -EINTR; | ||
6508 | |||
6509 | rc = volume_get_status_ec(&s); | ||
6510 | if (rc) | ||
6511 | goto unlock; | ||
6512 | |||
6513 | n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK : | ||
6514 | s & ~TP_EC_AUDIO_MUTESW_MSK; | ||
6515 | |||
6516 | if (n != s) | ||
6517 | rc = volume_set_status_ec(n); | ||
6518 | |||
6519 | unlock: | ||
6520 | mutex_unlock(&volume_mutex); | ||
6521 | return rc; | ||
6522 | } | ||
6523 | |||
6524 | static int volume_set_mute(const bool mute) | ||
6525 | { | ||
6526 | dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n", | ||
6527 | (mute) ? "" : "un"); | ||
6528 | return volume_set_mute_ec(mute); | ||
6529 | } | ||
6530 | |||
6531 | static int volume_set_volume_ec(const u8 vol) | ||
6532 | { | ||
6533 | int rc; | ||
6534 | u8 s, n; | ||
6535 | |||
6536 | if (vol > TP_EC_VOLUME_MAX) | ||
6537 | return -EINVAL; | ||
6538 | |||
6539 | if (mutex_lock_killable(&volume_mutex) < 0) | ||
6540 | return -EINTR; | ||
6541 | |||
6542 | rc = volume_get_status_ec(&s); | ||
6543 | if (rc) | ||
6544 | goto unlock; | ||
6545 | |||
6546 | n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol; | ||
6547 | |||
6548 | if (n != s) | ||
6549 | rc = volume_set_status_ec(n); | ||
6550 | |||
6551 | unlock: | ||
6552 | mutex_unlock(&volume_mutex); | ||
6553 | return rc; | ||
6554 | } | ||
6555 | |||
6556 | static int volume_set_volume(const u8 vol) | ||
6557 | { | ||
6558 | dbg_printk(TPACPI_DBG_MIXER, | ||
6559 | "trying to set volume level to %hu\n", vol); | ||
6560 | return volume_set_volume_ec(vol); | ||
6561 | } | ||
6562 | |||
6563 | static void volume_suspend(pm_message_t state) | ||
6564 | { | ||
6565 | tpacpi_volume_checkpoint_nvram(); | ||
6566 | } | ||
6567 | |||
6568 | static void volume_shutdown(void) | ||
6569 | { | ||
6570 | tpacpi_volume_checkpoint_nvram(); | ||
6571 | } | ||
6572 | |||
6573 | static void volume_exit(void) | ||
6574 | { | ||
6575 | tpacpi_volume_checkpoint_nvram(); | ||
6576 | } | ||
6577 | |||
6578 | static int __init volume_init(struct ibm_init_struct *iibm) | ||
6579 | { | ||
6580 | vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n"); | ||
6581 | |||
6582 | mutex_init(&volume_mutex); | ||
6583 | |||
6584 | /* | ||
6585 | * Check for module parameter bogosity, note that we | ||
6586 | * init volume_mode to TPACPI_VOL_MODE_MAX in order to be | ||
6587 | * able to detect "unspecified" | ||
6588 | */ | ||
6589 | if (volume_mode > TPACPI_VOL_MODE_MAX) | ||
6590 | return -EINVAL; | ||
6591 | |||
6592 | if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) { | ||
6593 | printk(TPACPI_ERR | ||
6594 | "UCMS step volume mode not implemented, " | ||
6595 | "please contact %s\n", TPACPI_MAIL); | ||
6596 | return 1; | ||
6597 | } | ||
6598 | |||
6599 | if (volume_mode == TPACPI_VOL_MODE_AUTO || | ||
6600 | volume_mode == TPACPI_VOL_MODE_MAX) { | ||
6601 | volume_mode = TPACPI_VOL_MODE_ECNVRAM; | ||
6602 | |||
6603 | dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, | ||
6604 | "driver auto-selected volume_mode=%d\n", | ||
6605 | volume_mode); | ||
6606 | } else { | ||
6607 | dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, | ||
6608 | "using user-supplied volume_mode=%d\n", | ||
6609 | volume_mode); | ||
6610 | } | ||
6611 | |||
6612 | vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, | ||
6613 | "volume is supported\n"); | ||
6614 | |||
6615 | return 0; | ||
6616 | } | ||
6379 | 6617 | ||
6380 | static int volume_read(char *p) | 6618 | static int volume_read(char *p) |
6381 | { | 6619 | { |
6382 | int len = 0; | 6620 | int len = 0; |
6383 | u8 level; | 6621 | u8 status; |
6384 | 6622 | ||
6385 | if (!acpi_ec_read(volume_offset, &level)) { | 6623 | if (volume_get_status(&status) < 0) { |
6386 | len += sprintf(p + len, "level:\t\tunreadable\n"); | 6624 | len += sprintf(p + len, "level:\t\tunreadable\n"); |
6387 | } else { | 6625 | } else { |
6388 | len += sprintf(p + len, "level:\t\t%d\n", level & 0xf); | 6626 | len += sprintf(p + len, "level:\t\t%d\n", |
6389 | len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6)); | 6627 | status & TP_EC_AUDIO_LVL_MSK); |
6628 | len += sprintf(p + len, "mute:\t\t%s\n", | ||
6629 | onoff(status, TP_EC_AUDIO_MUTESW)); | ||
6390 | len += sprintf(p + len, "commands:\tup, down, mute\n"); | 6630 | len += sprintf(p + len, "commands:\tup, down, mute\n"); |
6391 | len += sprintf(p + len, "commands:\tlevel <level>" | 6631 | len += sprintf(p + len, "commands:\tlevel <level>" |
6392 | " (<level> is 0-15)\n"); | 6632 | " (<level> is 0-%d)\n", TP_EC_VOLUME_MAX); |
6393 | } | 6633 | } |
6394 | 6634 | ||
6395 | return len; | 6635 | return len; |
@@ -6397,77 +6637,55 @@ static int volume_read(char *p) | |||
6397 | 6637 | ||
6398 | static int volume_write(char *buf) | 6638 | static int volume_write(char *buf) |
6399 | { | 6639 | { |
6400 | int cmos_cmd, inc, i; | 6640 | u8 s; |
6401 | u8 level, mute; | 6641 | u8 new_level, new_mute; |
6402 | int new_level, new_mute; | 6642 | int l; |
6403 | char *cmd; | 6643 | char *cmd; |
6644 | int rc; | ||
6404 | 6645 | ||
6405 | while ((cmd = next_cmd(&buf))) { | 6646 | rc = volume_get_status(&s); |
6406 | if (!acpi_ec_read(volume_offset, &level)) | 6647 | if (rc < 0) |
6407 | return -EIO; | 6648 | return rc; |
6408 | new_mute = mute = level & 0x40; | ||
6409 | new_level = level = level & 0xf; | ||
6410 | 6649 | ||
6650 | new_level = s & TP_EC_AUDIO_LVL_MSK; | ||
6651 | new_mute = s & TP_EC_AUDIO_MUTESW_MSK; | ||
6652 | |||
6653 | while ((cmd = next_cmd(&buf))) { | ||
6411 | if (strlencmp(cmd, "up") == 0) { | 6654 | if (strlencmp(cmd, "up") == 0) { |
6412 | if (mute) | 6655 | if (new_mute) |
6413 | new_mute = 0; | 6656 | new_mute = 0; |
6414 | else | 6657 | else if (new_level < TP_EC_VOLUME_MAX) |
6415 | new_level = level == 15 ? 15 : level + 1; | 6658 | new_level++; |
6416 | } else if (strlencmp(cmd, "down") == 0) { | 6659 | } else if (strlencmp(cmd, "down") == 0) { |
6417 | if (mute) | 6660 | if (new_mute) |
6418 | new_mute = 0; | 6661 | new_mute = 0; |
6419 | else | 6662 | else if (new_level > 0) |
6420 | new_level = level == 0 ? 0 : level - 1; | 6663 | new_level--; |
6421 | } else if (sscanf(cmd, "level %d", &new_level) == 1 && | 6664 | } else if (sscanf(cmd, "level %u", &l) == 1 && |
6422 | new_level >= 0 && new_level <= 15) { | 6665 | l >= 0 && l <= TP_EC_VOLUME_MAX) { |
6423 | /* new_level set */ | 6666 | new_level = l; |
6424 | } else if (strlencmp(cmd, "mute") == 0) { | 6667 | } else if (strlencmp(cmd, "mute") == 0) { |
6425 | new_mute = 0x40; | 6668 | new_mute = TP_EC_AUDIO_MUTESW_MSK; |
6426 | } else | 6669 | } else |
6427 | return -EINVAL; | 6670 | return -EINVAL; |
6671 | } | ||
6428 | 6672 | ||
6429 | if (new_level != level) { | 6673 | tpacpi_disclose_usertask("procfs volume", |
6430 | /* mute doesn't change */ | 6674 | "%smute and set level to %d\n", |
6431 | 6675 | new_mute ? "" : "un", new_level); | |
6432 | cmos_cmd = (new_level > level) ? | ||
6433 | TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN; | ||
6434 | inc = new_level > level ? 1 : -1; | ||
6435 | |||
6436 | if (mute && (issue_thinkpad_cmos_command(cmos_cmd) || | ||
6437 | !acpi_ec_write(volume_offset, level))) | ||
6438 | return -EIO; | ||
6439 | |||
6440 | for (i = level; i != new_level; i += inc) | ||
6441 | if (issue_thinkpad_cmos_command(cmos_cmd) || | ||
6442 | !acpi_ec_write(volume_offset, i + inc)) | ||
6443 | return -EIO; | ||
6444 | |||
6445 | if (mute && | ||
6446 | (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) || | ||
6447 | !acpi_ec_write(volume_offset, new_level + mute))) { | ||
6448 | return -EIO; | ||
6449 | } | ||
6450 | } | ||
6451 | |||
6452 | if (new_mute != mute) { | ||
6453 | /* level doesn't change */ | ||
6454 | 6676 | ||
6455 | cmos_cmd = (new_mute) ? | 6677 | rc = volume_set_status(new_mute | new_level); |
6456 | TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP; | ||
6457 | 6678 | ||
6458 | if (issue_thinkpad_cmos_command(cmos_cmd) || | 6679 | return (rc == -EINTR) ? -ERESTARTSYS : rc; |
6459 | !acpi_ec_write(volume_offset, level + new_mute)) | ||
6460 | return -EIO; | ||
6461 | } | ||
6462 | } | ||
6463 | |||
6464 | return 0; | ||
6465 | } | 6680 | } |
6466 | 6681 | ||
6467 | static struct ibm_struct volume_driver_data = { | 6682 | static struct ibm_struct volume_driver_data = { |
6468 | .name = "volume", | 6683 | .name = "volume", |
6469 | .read = volume_read, | 6684 | .read = volume_read, |
6470 | .write = volume_write, | 6685 | .write = volume_write, |
6686 | .exit = volume_exit, | ||
6687 | .suspend = volume_suspend, | ||
6688 | .shutdown = volume_shutdown, | ||
6471 | }; | 6689 | }; |
6472 | 6690 | ||
6473 | /************************************************************************* | 6691 | /************************************************************************* |
@@ -8121,6 +8339,7 @@ static struct ibm_init_struct ibms_init[] __initdata = { | |||
8121 | .data = &brightness_driver_data, | 8339 | .data = &brightness_driver_data, |
8122 | }, | 8340 | }, |
8123 | { | 8341 | { |
8342 | .init = volume_init, | ||
8124 | .data = &volume_driver_data, | 8343 | .data = &volume_driver_data, |
8125 | }, | 8344 | }, |
8126 | { | 8345 | { |
@@ -8186,6 +8405,11 @@ MODULE_PARM_DESC(hotkey_report_mode, | |||
8186 | "used for backwards compatibility with userspace, " | 8405 | "used for backwards compatibility with userspace, " |
8187 | "see documentation"); | 8406 | "see documentation"); |
8188 | 8407 | ||
8408 | module_param_named(volume_mode, volume_mode, uint, 0444); | ||
8409 | MODULE_PARM_DESC(volume_mode, | ||
8410 | "Selects volume control strategy: " | ||
8411 | "0=auto, 1=EC, 2=N/A, 3=EC+NVRAM"); | ||
8412 | |||
8189 | #define TPACPI_PARAM(feature) \ | 8413 | #define TPACPI_PARAM(feature) \ |
8190 | module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ | 8414 | module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ |
8191 | MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \ | 8415 | MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \ |