--- zzzz-none-000/linux-3.10.107/drivers/platform/x86/thinkpad_acpi.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/platform/x86/thinkpad_acpi.c 2021-02-04 17:41:59.000000000 +0000 @@ -23,7 +23,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define TPACPI_VERSION "0.24" +#define TPACPI_VERSION "0.25" #define TPACPI_SYSFS_VERSION 0x020700 /* @@ -61,7 +61,6 @@ #include #include #include - #include #include #include @@ -74,20 +73,17 @@ #include #include #include -#include - #include #include #include - +#include +#include +#include #include #include #include - -#include - -#include - +#include +#include /* ThinkPad CMOS commands */ #define TP_CMOS_VOLUME_DOWN 0 @@ -201,6 +197,7 @@ /* Key-related user-interface events */ TP_HKEY_EV_KEY_NUMLOCK = 0x6000, /* NumLock key pressed */ TP_HKEY_EV_KEY_FN = 0x6005, /* Fn key pressed? E420 */ + TP_HKEY_EV_KEY_FN_ESC = 0x6060, /* Fn+Esc key pressed X240 */ /* Thermal events */ TP_HKEY_EV_ALARM_BAT_HOT = 0x6011, /* battery too hot */ @@ -323,6 +320,7 @@ u32 sensors_pdrv_attrs_registered:1; u32 sensors_pdev_attrs_registered:1; u32 hotkey_poll_active:1; + u32 has_adaptive_kbd:1; } tp_features; static struct { @@ -369,7 +367,7 @@ struct led_classdev led_classdev; struct work_struct work; enum led_status_t new_state; - unsigned int led; + int led; }; /* brightness level capabilities */ @@ -404,7 +402,7 @@ #else static inline const char *str_supported(int is_supported) { return ""; } #define vdbg_printk(a_dbg_level, format, arg...) \ - no_printk(format, ##arg) + do { if (0) no_printk(format, ##arg); } while (0) #endif static void tpacpi_log_usertask(const char * const what) @@ -700,6 +698,14 @@ static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle, u32 level, void *context, void **return_value) { + struct acpi_device *dev; + if (!strcmp(context, "video")) { + if (acpi_bus_get_device(handle, &dev)) + return AE_OK; + if (strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) + return AE_OK; + } + *(acpi_handle *)return_value = handle; return AE_CTRL_TERMINATE; @@ -712,10 +718,10 @@ acpi_status status; acpi_handle device_found; - BUG_ON(!name || !hid || !handle); + BUG_ON(!name || !handle); vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s, using HID %s\n", - name, hid); + name, hid ? hid : "NULL"); memset(&device_found, 0, sizeof(device_found)); status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback, @@ -969,7 +975,6 @@ static struct platform_driver tpacpi_pdriver = { .driver = { .name = TPACPI_DRVR_NAME, - .owner = THIS_MODULE, .pm = &tpacpi_pm, }, .shutdown = tpacpi_shutdown_handler, @@ -978,7 +983,6 @@ static struct platform_driver tpacpi_hwmon_pdriver = { .driver = { .name = TPACPI_HWMON_DRVR_NAME, - .owner = THIS_MODULE, }, }; @@ -1909,6 +1913,27 @@ TP_ACPI_HOTKEYSCAN_UNK7, TP_ACPI_HOTKEYSCAN_UNK8, + TP_ACPI_HOTKEYSCAN_MUTE2, + TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO, + TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL, + TP_ACPI_HOTKEYSCAN_CLOUD, + TP_ACPI_HOTKEYSCAN_UNK9, + TP_ACPI_HOTKEYSCAN_VOICE, + TP_ACPI_HOTKEYSCAN_UNK10, + TP_ACPI_HOTKEYSCAN_GESTURES, + TP_ACPI_HOTKEYSCAN_UNK11, + TP_ACPI_HOTKEYSCAN_UNK12, + TP_ACPI_HOTKEYSCAN_UNK13, + TP_ACPI_HOTKEYSCAN_CONFIG, + TP_ACPI_HOTKEYSCAN_NEW_TAB, + TP_ACPI_HOTKEYSCAN_RELOAD, + TP_ACPI_HOTKEYSCAN_BACK, + TP_ACPI_HOTKEYSCAN_MIC_DOWN, + TP_ACPI_HOTKEYSCAN_MIC_UP, + TP_ACPI_HOTKEYSCAN_MIC_CANCELLATION, + TP_ACPI_HOTKEYSCAN_CAMERA_MODE, + TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY, + /* Hotkey keymap size */ TPACPI_HOTKEY_MAP_LEN }; @@ -2022,8 +2047,6 @@ static u32 hotkey_user_mask; /* events visible to userspace */ static u32 hotkey_acpi_mask; /* events enabled in firmware */ -static unsigned int hotkey_report_mode; - static u16 *hotkey_keycode_map; static struct attribute_set *hotkey_dev_attributes; @@ -2093,7 +2116,7 @@ return 0; } -void static hotkey_mask_warn_incomplete_mask(void) +static void hotkey_mask_warn_incomplete_mask(void) { /* log only what the user can fix... */ const u32 wantedmask = hotkey_driver_mask & @@ -2282,10 +2305,6 @@ static void tpacpi_hotkey_send_key(unsigned int scancode) { tpacpi_input_send_key_masked(scancode); - if (hotkey_report_mode < 2) { - acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device, - 0x80, TP_HKEY_EV_HOTKEY_BASE + scancode); - } } static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m) @@ -2324,53 +2343,55 @@ } } -static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, - struct tp_nvram_state *newn, - const u32 event_mask) -{ - #define TPACPI_COMPARE_KEY(__scancode, __member) \ - do { \ - if ((event_mask & (1 << __scancode)) && \ - oldn->__member != newn->__member) \ - tpacpi_hotkey_send_key(__scancode); \ - } while (0) +do { \ + if ((event_mask & (1 << __scancode)) && \ + oldn->__member != newn->__member) \ + tpacpi_hotkey_send_key(__scancode); \ +} while (0) #define TPACPI_MAY_SEND_KEY(__scancode) \ - do { \ - if (event_mask & (1 << __scancode)) \ - tpacpi_hotkey_send_key(__scancode); \ - } while (0) - - void issue_volchange(const unsigned int oldvol, - const unsigned int newvol) - { - unsigned int i = oldvol; +do { \ + if (event_mask & (1 << __scancode)) \ + tpacpi_hotkey_send_key(__scancode); \ +} while (0) - while (i > newvol) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); - i--; - } - while (i < newvol) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); - i++; - } +static void issue_volchange(const unsigned int oldvol, + const unsigned int newvol, + const u32 event_mask) +{ + unsigned int i = oldvol; + + while (i > newvol) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); + i--; } + while (i < newvol) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); + i++; + } +} - void issue_brightnesschange(const unsigned int oldbrt, - const unsigned int newbrt) - { - unsigned int i = oldbrt; - - while (i > newbrt) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); - i--; - } - while (i < newbrt) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); - i++; - } +static void issue_brightnesschange(const unsigned int oldbrt, + const unsigned int newbrt, + const u32 event_mask) +{ + unsigned int i = oldbrt; + + while (i > newbrt) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); + i--; + } + while (i < newbrt) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); + i++; } +} + +static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, + struct tp_nvram_state *newn, + const u32 event_mask) +{ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle); TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle); @@ -2405,7 +2426,8 @@ oldn->volume_level != newn->volume_level) { /* recently muted, or repeated mute keypress, or * multiple presses ending in mute */ - issue_volchange(oldn->volume_level, newn->volume_level); + issue_volchange(oldn->volume_level, newn->volume_level, + event_mask); TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE); } } else { @@ -2415,7 +2437,8 @@ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); } if (oldn->volume_level != newn->volume_level) { - issue_volchange(oldn->volume_level, newn->volume_level); + issue_volchange(oldn->volume_level, newn->volume_level, + event_mask); } else if (oldn->volume_toggle != newn->volume_toggle) { /* repeated vol up/down keypress at end of scale ? */ if (newn->volume_level == 0) @@ -2428,7 +2451,7 @@ /* handle brightness */ if (oldn->brightness_level != newn->brightness_level) { issue_brightnesschange(oldn->brightness_level, - newn->brightness_level); + newn->brightness_level, event_mask); } else if (oldn->brightness_toggle != newn->brightness_toggle) { /* repeated key presses that didn't change state */ if (newn->brightness_level == 0) @@ -2647,9 +2670,7 @@ return count; } -static struct device_attribute dev_attr_hotkey_enable = - __ATTR(hotkey_enable, S_IWUSR | S_IRUGO, - hotkey_enable_show, hotkey_enable_store); +static DEVICE_ATTR_RW(hotkey_enable); /* sysfs hotkey mask --------------------------------------------------- */ static ssize_t hotkey_mask_show(struct device *dev, @@ -2685,9 +2706,7 @@ return (res) ? res : count; } -static struct device_attribute dev_attr_hotkey_mask = - __ATTR(hotkey_mask, S_IWUSR | S_IRUGO, - hotkey_mask_show, hotkey_mask_store); +static DEVICE_ATTR_RW(hotkey_mask); /* sysfs hotkey bios_enabled ------------------------------------------- */ static ssize_t hotkey_bios_enabled_show(struct device *dev, @@ -2697,8 +2716,7 @@ return sprintf(buf, "0\n"); } -static struct device_attribute dev_attr_hotkey_bios_enabled = - __ATTR(hotkey_bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL); +static DEVICE_ATTR_RO(hotkey_bios_enabled); /* sysfs hotkey bios_mask ---------------------------------------------- */ static ssize_t hotkey_bios_mask_show(struct device *dev, @@ -2710,8 +2728,7 @@ return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask); } -static struct device_attribute dev_attr_hotkey_bios_mask = - __ATTR(hotkey_bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_bios_mask); /* sysfs hotkey all_mask ----------------------------------------------- */ static ssize_t hotkey_all_mask_show(struct device *dev, @@ -2722,8 +2739,7 @@ hotkey_all_mask | hotkey_source_mask); } -static struct device_attribute dev_attr_hotkey_all_mask = - __ATTR(hotkey_all_mask, S_IRUGO, hotkey_all_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_all_mask); /* sysfs hotkey recommended_mask --------------------------------------- */ static ssize_t hotkey_recommended_mask_show(struct device *dev, @@ -2735,9 +2751,7 @@ & ~hotkey_reserved_mask); } -static struct device_attribute dev_attr_hotkey_recommended_mask = - __ATTR(hotkey_recommended_mask, S_IRUGO, - hotkey_recommended_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_recommended_mask); #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -2792,9 +2806,7 @@ return (rc < 0) ? rc : count; } -static struct device_attribute dev_attr_hotkey_source_mask = - __ATTR(hotkey_source_mask, S_IWUSR | S_IRUGO, - hotkey_source_mask_show, hotkey_source_mask_store); +static DEVICE_ATTR_RW(hotkey_source_mask); /* sysfs hotkey hotkey_poll_freq --------------------------------------- */ static ssize_t hotkey_poll_freq_show(struct device *dev, @@ -2826,9 +2838,7 @@ return count; } -static struct device_attribute dev_attr_hotkey_poll_freq = - __ATTR(hotkey_poll_freq, S_IWUSR | S_IRUGO, - hotkey_poll_freq_show, hotkey_poll_freq_store); +static DEVICE_ATTR_RW(hotkey_poll_freq); #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ @@ -2849,8 +2859,7 @@ (res == TPACPI_RFK_RADIO_OFF) ? 0 : 1); } -static struct device_attribute dev_attr_hotkey_radio_sw = - __ATTR(hotkey_radio_sw, S_IRUGO, hotkey_radio_sw_show, NULL); +static DEVICE_ATTR_RO(hotkey_radio_sw); static void hotkey_radio_sw_notify_change(void) { @@ -2872,8 +2881,7 @@ return snprintf(buf, PAGE_SIZE, "%d\n", !!s); } -static struct device_attribute dev_attr_hotkey_tablet_mode = - __ATTR(hotkey_tablet_mode, S_IRUGO, hotkey_tablet_mode_show, NULL); +static DEVICE_ATTR_RO(hotkey_tablet_mode); static void hotkey_tablet_mode_notify_change(void) { @@ -2882,18 +2890,6 @@ "hotkey_tablet_mode"); } -/* sysfs hotkey report_mode -------------------------------------------- */ -static ssize_t hotkey_report_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%d\n", - (hotkey_report_mode != 0) ? hotkey_report_mode : 1); -} - -static struct device_attribute dev_attr_hotkey_report_mode = - __ATTR(hotkey_report_mode, S_IRUGO, hotkey_report_mode_show, NULL); - /* sysfs wakeup reason (pollable) -------------------------------------- */ static ssize_t hotkey_wakeup_reason_show(struct device *dev, struct device_attribute *attr, @@ -2902,8 +2898,7 @@ return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_wakeup_reason); } -static struct device_attribute dev_attr_hotkey_wakeup_reason = - __ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL); +static DEVICE_ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL); static void hotkey_wakeup_reason_notify_change(void) { @@ -2919,9 +2914,8 @@ return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_autosleep_ack); } -static struct device_attribute dev_attr_hotkey_wakeup_hotunplug_complete = - __ATTR(wakeup_hotunplug_complete, S_IRUGO, - hotkey_wakeup_hotunplug_complete_show, NULL); +static DEVICE_ATTR(wakeup_hotunplug_complete, S_IRUGO, + hotkey_wakeup_hotunplug_complete_show, NULL); static void hotkey_wakeup_hotunplug_complete_notify_change(void) { @@ -2929,15 +2923,65 @@ "wakeup_hotunplug_complete"); } +/* sysfs adaptive kbd mode --------------------------------------------- */ + +static int adaptive_keyboard_get_mode(void); +static int adaptive_keyboard_set_mode(int new_mode); + +enum ADAPTIVE_KEY_MODE { + HOME_MODE, + WEB_BROWSER_MODE, + WEB_CONFERENCE_MODE, + FUNCTION_MODE, + LAYFLAT_MODE +}; + +static ssize_t adaptive_kbd_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int current_mode; + + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) + return current_mode; + + return snprintf(buf, PAGE_SIZE, "%d\n", current_mode); +} + +static ssize_t adaptive_kbd_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res; + + if (parse_strtoul(buf, LAYFLAT_MODE, &t)) + return -EINVAL; + + res = adaptive_keyboard_set_mode(t); + return (res < 0) ? res : count; +} + +static DEVICE_ATTR_RW(adaptive_kbd_mode); + +static struct attribute *adaptive_kbd_attributes[] = { + &dev_attr_adaptive_kbd_mode.attr, + NULL +}; + +static const struct attribute_group adaptive_kbd_attr_group = { + .attrs = adaptive_kbd_attributes, +}; + /* --------------------------------------------------------------------- */ static struct attribute *hotkey_attributes[] __initdata = { &dev_attr_hotkey_enable.attr, &dev_attr_hotkey_bios_enabled.attr, &dev_attr_hotkey_bios_mask.attr, - &dev_attr_hotkey_report_mode.attr, - &dev_attr_hotkey_wakeup_reason.attr, - &dev_attr_hotkey_wakeup_hotunplug_complete.attr, + &dev_attr_wakeup_reason.attr, + &dev_attr_wakeup_hotunplug_complete.attr, &dev_attr_hotkey_mask.attr, &dev_attr_hotkey_all_mask.attr, &dev_attr_hotkey_recommended_mask.attr, @@ -3131,6 +3175,13 @@ /* (assignments unknown, please report if found) */ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + + /* No assignments, only used for Adaptive keyboards. */ + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, }, /* Generic keymap for Lenovo ThinkPads */ @@ -3183,8 +3234,39 @@ KEY_MICMUTE, /* 0x1a: Mic mute (since ?400 or so) */ /* (assignments unknown, please report if found) */ - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + + /* Extra keys in use since the X240 / T440 / T540 */ + KEY_CONFIG, KEY_SEARCH, KEY_SCALE, KEY_FILE, + + /* + * These are the adaptive keyboard keycodes for Carbon X1 2014. + * The first item in this list is the Mute button which is + * emitted with 0x103 through + * adaptive_keyboard_hotkey_notify_hotkey() when the sound + * symbol is held. + * We'll need to offset those by 0x20. + */ + KEY_RESERVED, /* Mute held, 0x103 */ + KEY_BRIGHTNESS_MIN, /* Backlight off */ + KEY_RESERVED, /* Clipping tool */ + KEY_RESERVED, /* Cloud */ + KEY_RESERVED, + KEY_VOICECOMMAND, /* Voice */ + KEY_RESERVED, + KEY_RESERVED, /* Gestures */ + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_CONFIG, /* Settings */ + KEY_RESERVED, /* New tab */ + KEY_REFRESH, /* Reload */ + KEY_BACK, /* Back */ + KEY_RESERVED, /* Microphone down */ + KEY_RESERVED, /* Microphone up */ + KEY_RESERVED, /* Microphone cancellation */ + KEY_RESERVED, /* Camera mode */ + KEY_RESERVED, /* Rotate display, 0x116 */ }, }; @@ -3238,6 +3320,20 @@ if (!tp_features.hotkey) return 1; + /* + * Check if we have an adaptive keyboard, like on the + * Lenovo Carbon X1 2014 (2nd Gen). + */ + if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { + if ((hkeyv >> 8) == 2) { + tp_features.has_adaptive_kbd = true; + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &adaptive_kbd_attr_group); + if (res) + goto err_exit; + } + } + quirks = tpacpi_check_quirks(tpacpi_hotkey_qtable, ARRAY_SIZE(tpacpi_hotkey_qtable)); @@ -3392,7 +3488,7 @@ /* Do not issue duplicate brightness change events to * userspace. tpacpi_detect_brightness_capabilities() must have * been called before this point */ - if (acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) { pr_info("This ThinkPad has standard ACPI backlight " "brightness control, supported by the ACPI " "video driver\n"); @@ -3439,11 +3535,6 @@ "initial masks: user=0x%08x, fw=0x%08x, poll=0x%08x\n", hotkey_user_mask, hotkey_acpi_mask, hotkey_source_mask); - dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, - "legacy ibm/hotkey event reporting over procfs %s\n", - (hotkey_report_mode < 2) ? - "enabled" : "disabled"); - tpacpi_inputdev->open = &hotkey_inputdev_open; tpacpi_inputdev->close = &hotkey_inputdev_close; @@ -3453,9 +3544,141 @@ err_exit: delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj); + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &adaptive_kbd_attr_group); + hotkey_dev_attributes = NULL; - return (res < 0)? res : 1; + return (res < 0) ? res : 1; +} + +/* Thinkpad X1 Carbon support 5 modes including Home mode, Web browser + * mode, Web conference mode, Function mode and Lay-flat mode. + * We support Home mode and Function mode currently. + * + * Will consider support rest of modes in future. + * + */ +static const int adaptive_keyboard_modes[] = { + HOME_MODE, +/* WEB_BROWSER_MODE = 2, + WEB_CONFERENCE_MODE = 3, */ + FUNCTION_MODE +}; + +#define DFR_CHANGE_ROW 0x101 +#define DFR_SHOW_QUICKVIEW_ROW 0x102 +#define FIRST_ADAPTIVE_KEY 0x103 +#define ADAPTIVE_KEY_OFFSET 0x020 + +/* press Fn key a while second, it will switch to Function Mode. Then + * release Fn key, previous mode be restored. + */ +static bool adaptive_keyboard_mode_is_saved; +static int adaptive_keyboard_prev_mode; + +static int adaptive_keyboard_get_mode(void) +{ + int mode = 0; + + if (!acpi_evalf(hkey_handle, &mode, "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode\n"); + return -EIO; + } + + return mode; +} + +static int adaptive_keyboard_set_mode(int new_mode) +{ + if (new_mode < 0 || + new_mode > LAYFLAT_MODE) + return -EINVAL; + + if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", new_mode)) { + pr_err("Cannot set adaptive keyboard mode\n"); + return -EIO; + } + + return 0; +} + +static int adaptive_keyboard_get_next_mode(int mode) +{ + size_t i; + size_t max_mode = ARRAY_SIZE(adaptive_keyboard_modes) - 1; + + for (i = 0; i <= max_mode; i++) { + if (adaptive_keyboard_modes[i] == mode) + break; + } + + if (i >= max_mode) + i = 0; + else + i++; + + return adaptive_keyboard_modes[i]; +} + +static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) +{ + int current_mode = 0; + int new_mode = 0; + int keycode; + + switch (scancode) { + case DFR_CHANGE_ROW: + if (adaptive_keyboard_mode_is_saved) { + new_mode = adaptive_keyboard_prev_mode; + adaptive_keyboard_mode_is_saved = false; + } else { + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) + return false; + new_mode = adaptive_keyboard_get_next_mode( + current_mode); + } + + if (adaptive_keyboard_set_mode(new_mode) < 0) + return false; + + return true; + + case DFR_SHOW_QUICKVIEW_ROW: + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) + return false; + + adaptive_keyboard_prev_mode = current_mode; + adaptive_keyboard_mode_is_saved = true; + + if (adaptive_keyboard_set_mode (FUNCTION_MODE) < 0) + return false; + return true; + + default: + if (scancode < FIRST_ADAPTIVE_KEY || + scancode >= FIRST_ADAPTIVE_KEY + TPACPI_HOTKEY_MAP_LEN - + ADAPTIVE_KEY_OFFSET) { + pr_info("Unhandled adaptive keyboard key: 0x%x\n", + scancode); + return false; + } + keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY + ADAPTIVE_KEY_OFFSET]; + if (keycode != KEY_RESERVED) { + mutex_lock(&tpacpi_inputdev_send_mutex); + + input_report_key(tpacpi_inputdev, keycode, 1); + input_sync(tpacpi_inputdev); + + input_report_key(tpacpi_inputdev, keycode, 0); + input_sync(tpacpi_inputdev); + + mutex_unlock(&tpacpi_inputdev_send_mutex); + } + return true; + } } static bool hotkey_notify_hotkey(const u32 hkey, @@ -3477,6 +3700,8 @@ *ignore_acpi_ev = true; } return true; + } else { + return adaptive_keyboard_hotkey_notify_hotkey(scancode); } return false; } @@ -3627,6 +3852,7 @@ case TP_HKEY_EV_KEY_NUMLOCK: case TP_HKEY_EV_KEY_FN: + case TP_HKEY_EV_KEY_FN_ESC: /* key press events, we just ignore them as long as the EC * is still reporting them in the normal keyboard stream */ *send_acpi_ev = false; @@ -3737,13 +3963,6 @@ "event happened to %s\n", TPACPI_MAIL); } - /* Legacy events */ - if (!ignore_acpi_ev && - (send_acpi_ev || hotkey_report_mode < 2)) { - acpi_bus_generate_proc_event(ibm->acpi->device, - event, hkey); - } - /* netlink events */ if (!ignore_acpi_ev && send_acpi_ev) { acpi_bus_generate_netlink_event( @@ -3759,6 +3978,14 @@ /* Do these on suspend, we get the events on early resume! */ hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE; hotkey_autosleep_ack = 0; + + /* save previous mode of adaptive keyboard of X1 Carbon */ + if (tp_features.has_adaptive_kbd) { + if (!acpi_evalf(hkey_handle, &adaptive_keyboard_prev_mode, + "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode.\n"); + } + } } static void hotkey_resume(void) @@ -3775,6 +4002,14 @@ hotkey_wakeup_reason_notify_change(); hotkey_wakeup_hotunplug_complete_notify_change(); hotkey_poll_setup_safe(false); + + /* restore previous mode of adapive keyboard of X1 Carbon */ + if (tp_features.has_adaptive_kbd) { + if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", + adaptive_keyboard_prev_mode)) { + pr_err("Cannot set adaptive keyboard mode.\n"); + } + } } /* procfs -------------------------------------------------------------- */ @@ -3972,9 +4207,7 @@ attr, buf, count); } -static struct device_attribute dev_attr_bluetooth_enable = - __ATTR(bluetooth_enable, S_IWUSR | S_IRUGO, - bluetooth_enable_show, bluetooth_enable_store); +static DEVICE_ATTR_RW(bluetooth_enable); /* --------------------------------------------------------------------- */ @@ -4162,14 +4395,13 @@ attr, buf, count); } -static struct device_attribute dev_attr_wan_enable = - __ATTR(wwan_enable, S_IWUSR | S_IRUGO, - wan_enable_show, wan_enable_store); +static DEVICE_ATTR(wwan_enable, S_IWUSR | S_IRUGO, + wan_enable_show, wan_enable_store); /* --------------------------------------------------------------------- */ static struct attribute *wan_attributes[] = { - &dev_attr_wan_enable.attr, + &dev_attr_wwan_enable.attr, NULL }; @@ -4469,7 +4701,7 @@ str_supported(video_supported != TPACPI_VIDEO_NONE), video_supported); - return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1; + return (video_supported != TPACPI_VIDEO_NONE) ? 0 : 1; } static void video_exit(void) @@ -4562,7 +4794,7 @@ return -ENOSYS; } - return (res)? 0 : -EIO; + return (res) ? 0 : -EIO; } static int video_autosw_get(void) @@ -4588,7 +4820,7 @@ static int video_autosw_set(int enable) { - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0)) + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable) ? 1 : 0)) return -EIO; return 0; } @@ -4623,20 +4855,20 @@ return -EIO; } - return (res)? 0 : -EIO; + return (res) ? 0 : -EIO; } static int video_expand_toggle(void) { switch (video_supported) { case TPACPI_VIDEO_570: - return acpi_evalf(ec_handle, NULL, "_Q17", "v")? + return acpi_evalf(ec_handle, NULL, "_Q17", "v") ? 0 : -EIO; case TPACPI_VIDEO_770: - return acpi_evalf(vid_handle, NULL, "VEXP", "v")? + return acpi_evalf(vid_handle, NULL, "VEXP", "v") ? 0 : -EIO; case TPACPI_VIDEO_NEW: - return acpi_evalf(NULL, NULL, "\\VEXP", "v")? + return acpi_evalf(NULL, NULL, "\\VEXP", "v") ? 0 : -EIO; default: return -ENOSYS; @@ -4780,14 +5012,14 @@ if (tp_features.light) { if (cmos_handle) { rc = acpi_evalf(cmos_handle, NULL, NULL, "vd", - (status)? + (status) ? TP_CMOS_THINKLIGHT_ON : TP_CMOS_THINKLIGHT_OFF); } else { rc = acpi_evalf(lght_handle, NULL, NULL, "vd", - (status)? 1 : 0); + (status) ? 1 : 0); } - return (rc)? 0 : -EIO; + return (rc) ? 0 : -EIO; } return -ENXIO; @@ -4816,7 +5048,7 @@ static enum led_brightness light_sysfs_get(struct led_classdev *led_cdev) { - return (light_get_status() == 1)? LED_FULL : LED_OFF; + return (light_get_status() == 1) ? LED_FULL : LED_OFF; } static struct tpacpi_led_classdev tpacpi_led_thinklight = { @@ -4938,11 +5170,10 @@ return -EINVAL; res = issue_thinkpad_cmos_command(cmos_cmd); - return (res)? res : count; + return (res) ? res : count; } -static struct device_attribute dev_attr_cmos_command = - __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store); +static DEVICE_ATTR_WO(cmos_command); /* --------------------------------------------------------------------- */ @@ -4962,7 +5193,7 @@ if (res) return res; - return (cmos_handle)? 0 : 1; + return (cmos_handle) ? 0 : 1; } static void cmos_exit(void) @@ -5072,9 +5303,9 @@ if (!acpi_evalf(ec_handle, &status, "GLED", "dd", 1 << led)) return -EIO; - led_s = (status == 0)? + led_s = (status == 0) ? TPACPI_LED_OFF : - ((status == 1)? + ((status == 1) ? TPACPI_LED_ON : TPACPI_LED_BLINK); tpacpi_led_state_cache[led] = led_s; @@ -5327,6 +5558,16 @@ led_supported = led_init_detect_mode(); + if (led_supported != TPACPI_LED_NONE) { + useful_leds = tpacpi_check_quirks(led_useful_qtable, + ARRAY_SIZE(led_useful_qtable)); + + if (!useful_leds) { + led_handle = NULL; + led_supported = TPACPI_LED_NONE; + } + } + vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n", str_supported(led_supported), led_supported); @@ -5340,10 +5581,9 @@ return -ENOMEM; } - useful_leds = tpacpi_check_quirks(led_useful_qtable, - ARRAY_SIZE(led_useful_qtable)); - for (i = 0; i < TPACPI_LED_NUMLEDS; i++) { + tpacpi_leds[i].led = -1; + if (!tpacpi_is_led_restricted(i) && test_bit(i, &useful_leds)) { rc = tpacpi_init_led(i); @@ -5401,9 +5641,13 @@ return -ENODEV; while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 15) + if (sscanf(cmd, "%d", &led) != 1) return -EINVAL; + if (led < 0 || led > (TPACPI_LED_NUMLEDS - 1) || + tpacpi_leds[led].led < 0) + return -ENODEV; + if (strstr(cmd, "off")) { s = TPACPI_LED_OFF; } else if (strstr(cmd, "on")) { @@ -5458,7 +5702,7 @@ tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1); - return (beep_handle)? 0 : 1; + return (beep_handle) ? 0 : 1; } static int beep_read(struct seq_file *m) @@ -6024,7 +6268,7 @@ { int res; - if (value > bright_maxlvl || value < 0) + if (value > bright_maxlvl) return -EINVAL; vdbg_printk(TPACPI_DBG_BRGHT, @@ -6108,19 +6352,28 @@ { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; + struct acpi_device *device, *child; int rc; - if (ACPI_SUCCESS(acpi_evaluate_object(handle, "_BCL", NULL, &buffer))) { + if (acpi_bus_get_device(handle, &device)) + return 0; + + rc = 0; + list_for_each_entry(child, &device->children, node) { + acpi_status status = acpi_evaluate_object(child->handle, "_BCL", + NULL, &buffer); + if (ACPI_FAILURE(status)) + continue; + obj = (union acpi_object *)buffer.pointer; if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) { pr_err("Unknown _BCL data, please report this to %s\n", - TPACPI_MAIL); + TPACPI_MAIL); rc = 0; } else { rc = obj->package.count; } - } else { - return 0; + break; } kfree(buffer.pointer); @@ -6136,7 +6389,7 @@ acpi_handle video_device; int bcl_levels = 0; - tpacpi_acpi_handle_locate("video", ACPI_VIDEO_HID, &video_device); + tpacpi_acpi_handle_locate("video", NULL, &video_device); if (video_device) bcl_levels = tpacpi_query_bcl_levels(video_device); @@ -6206,8 +6459,7 @@ pr_info("detected a 8-level brightness capable ThinkPad\n"); break; default: - pr_err("Unsupported brightness interface, " - "please contact %s\n", TPACPI_MAIL); + pr_info("Unsupported brightness interface\n"); tp_features.bright_unkfw = 1; bright_maxlvl = b - 1; } @@ -6239,7 +6491,7 @@ return 1; } - if (acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) { if (brightness_enable > 1) { pr_info("Standard ACPI backlight interface " "available, not loading native one\n"); @@ -6398,7 +6650,7 @@ if (!rc && ibm_backlight_device) backlight_force_update(ibm_backlight_device, BACKLIGHT_UPDATE_SYSFS); - return (rc == -EINTR)? -ERESTARTSYS : rc; + return (rc == -EINTR) ? -ERESTARTSYS : rc; } static struct ibm_struct brightness_driver_data = { @@ -6430,6 +6682,17 @@ * 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. + * + * On newer Lenovo ThinkPads, the EC can automatically change the volume + * in response to user input. Unfortunately, this rarely works well. + * The laptop changes the state of its internal MUTE gate and, on some + * models, sends KEY_MUTE, causing any user code that responds to the + * mute button to get confused. The hardware MUTE gate is also + * unnecessary, since user code can handle the mute button without + * kernel or EC help. + * + * To avoid confusing userspace, we simply disable all EC-based mute + * and volume controls when possible. */ #ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT @@ -6438,7 +6701,12 @@ #define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control" #define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME -static int alsa_index = ~((1 << (SNDRV_CARDS - 3)) - 1); /* last three slots */ +#if SNDRV_CARDS <= 32 +#define DEFAULT_ALSA_IDX ~((1 << (SNDRV_CARDS - 3)) - 1) +#else +#define DEFAULT_ALSA_IDX ~((1 << (32 - 3)) - 1) +#endif +static int alsa_index = DEFAULT_ALSA_IDX; /* last three slots */ static char *alsa_id = "ThinkPadEC"; static bool alsa_enable = SNDRV_DEFAULT_ENABLE1; @@ -6479,11 +6747,21 @@ TPACPI_VOL_CAP_MAX }; +enum tpacpi_mute_btn_mode { + TP_EC_MUTE_BTN_LATCH = 0, /* Mute mutes; up/down unmutes */ + /* We don't know what mode 1 is. */ + TP_EC_MUTE_BTN_NONE = 2, /* Mute and up/down are just keys */ + TP_EC_MUTE_BTN_TOGGLE = 3, /* Mute toggles; up/down unmutes */ +}; + static enum tpacpi_volume_access_mode volume_mode = TPACPI_VOL_MODE_MAX; static enum tpacpi_volume_capabilities volume_capabilities; static bool volume_control_allowed; +static bool software_mute_requested = true; +static bool software_mute_active; +static int software_mute_orig_mode; /* * Used to syncronize writers to TP_EC_AUDIO and @@ -6501,6 +6779,8 @@ return; if (!volume_control_allowed) return; + if (software_mute_active) + return; vdbg_printk(TPACPI_DBG_MIXER, "trying to checkpoint mixer state to NVRAM...\n"); @@ -6562,6 +6842,12 @@ dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status); + /* + * On X200s, and possibly on others, it can take a while for + * reads to become correct. + */ + msleep(1); + return 0; } @@ -6644,6 +6930,57 @@ return rc; } +static int volume_set_software_mute(bool startup) +{ + int result; + + if (!tpacpi_is_lenovo()) + return -ENODEV; + + if (startup) { + if (!acpi_evalf(ec_handle, &software_mute_orig_mode, + "HAUM", "qd")) + return -EIO; + + dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "Initial HAUM setting was %d\n", + software_mute_orig_mode); + } + + if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd", + (int)TP_EC_MUTE_BTN_NONE)) + return -EIO; + + if (result != TP_EC_MUTE_BTN_NONE) + pr_warn("Unexpected SAUM result %d\n", + result); + + /* + * In software mute mode, the standard codec controls take + * precendence, so we unmute the ThinkPad HW switch at + * startup. Just on case there are SAUM-capable ThinkPads + * with level controls, set max HW volume as well. + */ + if (tp_features.mixer_no_level_control) + result = volume_set_mute(false); + else + result = volume_set_status(TP_EC_VOLUME_MAX); + + if (result != 0) + pr_warn("Failed to unmute the HW mute switch\n"); + + return 0; +} + +static void volume_exit_software_mute(void) +{ + int r; + + if (!acpi_evalf(ec_handle, &r, "SAUM", "qdd", software_mute_orig_mode) + || r != software_mute_orig_mode) + pr_warn("Failed to restore mute mode\n"); +} + static int volume_alsa_set_volume(const u8 vol) { dbg_printk(TPACPI_DBG_MIXER, @@ -6726,7 +7063,7 @@ return volume_alsa_set_mute(!ucontrol->value.integer.value[0]); } -static struct snd_kcontrol_new volume_alsa_control_vol = { +static struct snd_kcontrol_new volume_alsa_control_vol __initdata = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Console Playback Volume", .index = 0, @@ -6735,7 +7072,7 @@ .get = volume_alsa_vol_get, }; -static struct snd_kcontrol_new volume_alsa_control_mute = { +static struct snd_kcontrol_new volume_alsa_control_mute __initdata = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Console Playback Switch", .index = 0, @@ -6751,7 +7088,12 @@ static void volume_resume(void) { - volume_alsa_notify_change(); + if (software_mute_active) { + if (volume_set_software_mute(false) < 0) + pr_warn("Failed to restore software mute\n"); + } else { + volume_alsa_notify_change(); + } } static void volume_shutdown(void) @@ -6767,6 +7109,9 @@ } tpacpi_volume_checkpoint_nvram(); + + if (software_mute_active) + volume_exit_software_mute(); } static int __init volume_create_alsa_mixer(void) @@ -6777,8 +7122,9 @@ struct snd_kcontrol *ctl_mute; int rc; - rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE, - sizeof(struct tpacpi_alsa_data), &card); + rc = snd_card_new(&tpacpi_pdev->dev, + alsa_index, alsa_id, THIS_MODULE, + sizeof(struct tpacpi_alsa_data), &card); if (rc < 0 || !card) { pr_err("Failed to create ALSA card structures: %d\n", rc); return 1; @@ -6829,7 +7175,6 @@ } data->ctl_mute_id = &ctl_mute->id; - snd_card_set_dev(card, &tpacpi_pdev->dev); rc = snd_card_register(card); if (rc < 0) { pr_err("Failed to register ALSA card: %d\n", rc); @@ -6951,16 +7296,20 @@ "mute is supported, volume control is %s\n", str_supported(!tp_features.mixer_no_level_control)); - rc = volume_create_alsa_mixer(); - if (rc) { - pr_err("Could not create the ALSA mixer interface\n"); - return rc; - } + if (software_mute_requested && volume_set_software_mute(true) == 0) { + software_mute_active = true; + } else { + rc = volume_create_alsa_mixer(); + if (rc) { + pr_err("Could not create the ALSA mixer interface\n"); + return rc; + } - pr_info("Console audio control enabled, mode: %s\n", - (volume_control_allowed) ? - "override (read/write)" : - "monitor (read only)"); + pr_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"); @@ -7791,9 +8140,8 @@ return count; } -static struct device_attribute dev_attr_fan_pwm1_enable = - __ATTR(pwm1_enable, S_IWUSR | S_IRUGO, - fan_pwm1_enable_show, fan_pwm1_enable_store); +static DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + fan_pwm1_enable_show, fan_pwm1_enable_store); /* sysfs fan pwm1 ------------------------------------------------------ */ static ssize_t fan_pwm1_show(struct device *dev, @@ -7850,12 +8198,10 @@ } mutex_unlock(&fan_mutex); - return (rc)? rc : count; + return (rc) ? rc : count; } -static struct device_attribute dev_attr_fan_pwm1 = - __ATTR(pwm1, S_IWUSR | S_IRUGO, - fan_pwm1_show, fan_pwm1_store); +static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, fan_pwm1_show, fan_pwm1_store); /* sysfs fan fan1_input ------------------------------------------------ */ static ssize_t fan_fan1_input_show(struct device *dev, @@ -7872,9 +8218,7 @@ return snprintf(buf, PAGE_SIZE, "%u\n", speed); } -static struct device_attribute dev_attr_fan_fan1_input = - __ATTR(fan1_input, S_IRUGO, - fan_fan1_input_show, NULL); +static DEVICE_ATTR(fan1_input, S_IRUGO, fan_fan1_input_show, NULL); /* sysfs fan fan2_input ------------------------------------------------ */ static ssize_t fan_fan2_input_show(struct device *dev, @@ -7891,9 +8235,7 @@ return snprintf(buf, PAGE_SIZE, "%u\n", speed); } -static struct device_attribute dev_attr_fan_fan2_input = - __ATTR(fan2_input, S_IRUGO, - fan_fan2_input_show, NULL); +static DEVICE_ATTR(fan2_input, S_IRUGO, fan_fan2_input_show, NULL); /* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */ static ssize_t fan_fan_watchdog_show(struct device_driver *drv, @@ -7926,8 +8268,8 @@ /* --------------------------------------------------------------------- */ static struct attribute *fan_attributes[] = { - &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, - &dev_attr_fan_fan1_input.attr, + &dev_attr_pwm1_enable.attr, &dev_attr_pwm1.attr, + &dev_attr_fan1_input.attr, NULL, /* for fan2_input */ NULL }; @@ -8061,7 +8403,7 @@ if (tp_features.second_fan) { /* attach second fan tachometer */ fan_attributes[ARRAY_SIZE(fan_attributes)-2] = - &dev_attr_fan_fan2_input.attr; + &dev_attr_fan2_input.attr; } rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group); @@ -8368,6 +8710,103 @@ .resume = fan_resume, }; +/************************************************************************* + * Mute LED subdriver + */ + + +struct tp_led_table { + acpi_string name; + int on_value; + int off_value; + int state; +}; + +static struct tp_led_table led_tables[] = { + [TPACPI_LED_MUTE] = { + .name = "SSMS", + .on_value = 1, + .off_value = 0, + }, + [TPACPI_LED_MICMUTE] = { + .name = "MMTS", + .on_value = 2, + .off_value = 0, + }, +}; + +static int mute_led_on_off(struct tp_led_table *t, bool state) +{ + acpi_handle temp; + int output; + + if (!ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp))) { + pr_warn("Thinkpad ACPI has no %s interface.\n", t->name); + return -EIO; + } + + if (!acpi_evalf(hkey_handle, &output, t->name, "dd", + state ? t->on_value : t->off_value)) + return -EIO; + + t->state = state; + return state; +} + +int tpacpi_led_set(int whichled, bool on) +{ + struct tp_led_table *t; + + if (whichled < 0 || whichled >= TPACPI_LED_MAX) + return -EINVAL; + + t = &led_tables[whichled]; + if (t->state < 0 || t->state == on) + return t->state; + return mute_led_on_off(t, on); +} +EXPORT_SYMBOL_GPL(tpacpi_led_set); + +static int mute_led_init(struct ibm_init_struct *iibm) +{ + acpi_handle temp; + int i; + + for (i = 0; i < TPACPI_LED_MAX; i++) { + struct tp_led_table *t = &led_tables[i]; + if (ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp))) + mute_led_on_off(t, false); + else + t->state = -ENODEV; + } + return 0; +} + +static void mute_led_exit(void) +{ + int i; + + for (i = 0; i < TPACPI_LED_MAX; i++) + tpacpi_led_set(i, false); +} + +static void mute_led_resume(void) +{ + int i; + + for (i = 0; i < TPACPI_LED_MAX; i++) { + struct tp_led_table *t = &led_tables[i]; + if (t->state >= 0) + mute_led_on_off(t, t->state); + } +} + +static struct ibm_struct mute_led_driver_data = { + .name = "mute_led", + .exit = mute_led_exit, + .resume = mute_led_resume, +}; + /**************************************************************************** **************************************************************************** * @@ -8412,8 +8851,7 @@ return snprintf(buf, PAGE_SIZE, "%s\n", TPACPI_NAME); } -static struct device_attribute dev_attr_thinkpad_acpi_pdev_name = - __ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL); +static DEVICE_ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL); /* --------------------------------------------------------------------- */ @@ -8431,7 +8869,7 @@ { static char text_unsupported[] __initdata = "not supported"; - return (is_supported)? &text_unsupported[4] : &text_unsupported[0]; + return (is_supported) ? &text_unsupported[4] : &text_unsupported[0]; } #endif /* CONFIG_THINKPAD_ACPI_DEBUG */ @@ -8552,7 +8990,7 @@ ibm->name, ret); ibm_exit(ibm); - return (ret < 0)? ret : 0; + return (ret < 0) ? ret : 0; } /* Probing */ @@ -8562,17 +9000,31 @@ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'); } -/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */ -static bool __pure __init tpacpi_is_valid_fw_id(const char* const s, +static bool __pure __init tpacpi_is_valid_fw_id(const char * const s, const char t) { - return s && strlen(s) >= 8 && + /* + * Most models: xxyTkkWW (#.##c) + * Ancient 570/600 and -SL lacks (#.##c) + */ + if (s && strlen(s) >= 8 && tpacpi_is_fw_digit(s[0]) && tpacpi_is_fw_digit(s[1]) && s[2] == t && (s[3] == 'T' || s[3] == 'N') && tpacpi_is_fw_digit(s[4]) && - tpacpi_is_fw_digit(s[5]); + tpacpi_is_fw_digit(s[5])) + return true; + + /* New models: xxxyTkkW (#.##c); T550 and some others */ + return s && strlen(s) >= 8 && + tpacpi_is_fw_digit(s[0]) && + tpacpi_is_fw_digit(s[1]) && + tpacpi_is_fw_digit(s[2]) && + s[3] == t && + (s[4] == 'T' || s[4] == 'N') && + tpacpi_is_fw_digit(s[5]) && + tpacpi_is_fw_digit(s[6]); } /* returns 0 - probe ok, or < 0 - probe error. @@ -8647,13 +9099,13 @@ } s = dmi_get_system_info(DMI_PRODUCT_VERSION); - if (s && !(strnicmp(s, "ThinkPad", 8) && strnicmp(s, "Lenovo", 6))) { + if (s && !(strncasecmp(s, "ThinkPad", 8) && strncasecmp(s, "Lenovo", 6))) { tp->model_str = kstrdup(s, GFP_KERNEL); if (!tp->model_str) return -ENOMEM; } else { s = dmi_get_system_info(DMI_BIOS_VENDOR); - if (s && !(strnicmp(s, "Lenovo", 6))) { + if (s && !(strncasecmp(s, "Lenovo", 6))) { tp->model_str = kstrdup(s, GFP_KERNEL); if (!tp->model_str) return -ENOMEM; @@ -8786,6 +9238,10 @@ .init = fan_init, .data = &fan_driver_data, }, + { + .init = mute_led_init, + .data = &mute_led_driver_data, + }, }; static int __init set_ibm_param(const char *val, struct kernel_param *kp) @@ -8840,11 +9296,6 @@ MODULE_PARM_DESC(brightness_enable, "Enables backlight control when 1, disables when 0"); -module_param(hotkey_report_mode, uint, 0444); -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, @@ -8861,6 +9312,10 @@ "Enables software override for the console audio " "control when true"); +module_param_named(software_mute, software_mute_requested, bool, 0444); +MODULE_PARM_DESC(software_mute, + "Request full software mute control"); + /* ALSA module API parameters */ module_param_named(index, alsa_index, int, 0444); MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer"); @@ -8938,8 +9393,7 @@ hwmon_device_unregister(tpacpi_hwmon); if (tp_features.sensors_pdev_attrs_registered) - device_remove_file(&tpacpi_sensors_pdev->dev, - &dev_attr_thinkpad_acpi_pdev_name); + device_remove_file(&tpacpi_sensors_pdev->dev, &dev_attr_name); if (tpacpi_sensors_pdev) platform_device_unregister(tpacpi_sensors_pdev); if (tpacpi_pdev) @@ -8975,10 +9429,6 @@ tpacpi_lifecycle = TPACPI_LIFE_INIT; - /* Parameter checking */ - if (hotkey_report_mode > 2) - return -EINVAL; - /* Driver-level probe */ ret = get_thinkpad_model_data(&thinkpad_id); @@ -9064,8 +9514,7 @@ thinkpad_acpi_module_exit(); return ret; } - ret = device_create_file(&tpacpi_sensors_pdev->dev, - &dev_attr_thinkpad_acpi_pdev_name); + ret = device_create_file(&tpacpi_sensors_pdev->dev, &dev_attr_name); if (ret) { pr_err("unable to create sysfs hwmon device attributes\n"); thinkpad_acpi_module_exit(); @@ -9083,7 +9532,6 @@ mutex_init(&tpacpi_inputdev_send_mutex); tpacpi_inputdev = input_allocate_device(); if (!tpacpi_inputdev) { - pr_err("unable to allocate input device\n"); thinkpad_acpi_module_exit(); return -ENOMEM; } else {