diff options
Diffstat (limited to 'drivers/platform/x86/dell-laptop.c')
-rw-r--r-- | drivers/platform/x86/dell-laptop.c | 254 |
1 files changed, 189 insertions, 65 deletions
diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 2e237bad4995..ec202094bd50 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -45,6 +45,7 @@ #define KBD_LED_AUTO_100_TOKEN 0x02F6 #define GLOBAL_MIC_MUTE_ENABLE 0x0364 #define GLOBAL_MIC_MUTE_DISABLE 0x0365 +#define KBD_LED_AC_TOKEN 0x0451 struct quirk_entry { u8 touchpad_led; @@ -1027,7 +1028,7 @@ static void touchpad_led_exit(void) * bit 2 Pointing stick * bit 3 Any mouse * bits 4-7 Reserved for future use - * cbRES2, byte3 Current Timeout + * cbRES2, byte3 Current Timeout on battery * bits 7:6 Timeout units indicator: * 00b Seconds * 01b Minutes @@ -1039,6 +1040,15 @@ static void touchpad_led_exit(void) * cbRES3, byte0 Current setting of ALS value that turns the light on or off. * cbRES3, byte1 Current ALS reading * cbRES3, byte2 Current keyboard light level. + * cbRES3, byte3 Current timeout on AC Power + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * Bits 5:0 Timeout value (0-63) in sec/min/hr/day + * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte2 + * are set upon return from the upon return from the [Get Feature information] call. * * cbArg1 0x2 = Set New State * cbRES1 Standard return codes (0, -1, -2) @@ -1061,7 +1071,7 @@ static void touchpad_led_exit(void) * bit 2 Pointing stick * bit 3 Any mouse * bits 4-7 Reserved for future use - * cbArg2, byte3 Desired Timeout + * cbArg2, byte3 Desired Timeout on battery * bits 7:6 Timeout units indicator: * 00b Seconds * 01b Minutes @@ -1070,6 +1080,13 @@ static void touchpad_led_exit(void) * bits 5:0 Timeout value (0-63) in sec/min/hr/day * cbArg3, byte0 Desired setting of ALS value that turns the light on or off. * cbArg3, byte2 Desired keyboard light level. + * cbArg3, byte3 Desired Timeout on AC power + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day */ @@ -1115,6 +1132,8 @@ struct kbd_state { u8 triggers; u8 timeout_value; u8 timeout_unit; + u8 timeout_value_ac; + u8 timeout_unit_ac; u8 als_setting; u8 als_value; u8 level; @@ -1134,6 +1153,7 @@ static u16 kbd_token_bits; static struct kbd_info kbd_info; static bool kbd_als_supported; static bool kbd_triggers_supported; +static bool kbd_timeout_ac_supported; static u8 kbd_mode_levels[16]; static int kbd_mode_levels_count; @@ -1142,6 +1162,7 @@ static u8 kbd_previous_level; static u8 kbd_previous_mode_bit; static bool kbd_led_present; +static DEFINE_MUTEX(kbd_led_mutex); /* * NOTE: there are three ways to set the keyboard backlight level. @@ -1272,6 +1293,8 @@ static int kbd_get_state(struct kbd_state *state) state->als_setting = buffer->output[2] & 0xFF; state->als_value = (buffer->output[2] >> 8) & 0xFF; state->level = (buffer->output[2] >> 16) & 0xFF; + state->timeout_value_ac = (buffer->output[2] >> 24) & 0x3F; + state->timeout_unit_ac = (buffer->output[2] >> 30) & 0x3; out: dell_smbios_release_buffer(); @@ -1291,6 +1314,8 @@ static int kbd_set_state(struct kbd_state *state) buffer->input[1] |= (state->timeout_unit & 0x3) << 30; buffer->input[2] = state->als_setting & 0xFF; buffer->input[2] |= (state->level & 0xFF) << 16; + buffer->input[2] |= (state->timeout_value_ac & 0x3F) << 24; + buffer->input[2] |= (state->timeout_unit_ac & 0x3) << 30; dell_smbios_send_request(4, 11); ret = buffer->output[0]; dell_smbios_release_buffer(); @@ -1397,6 +1422,13 @@ static inline int kbd_init_info(void) if (ret) return ret; + /* NOTE: Old models without KBD_LED_AC_TOKEN token supports only one + * timeout value which is shared for both battery and AC power + * settings. So do not try to set AC values on old models. + */ + if (dell_smbios_find_token(KBD_LED_AC_TOKEN)) + kbd_timeout_ac_supported = true; + kbd_get_state(&state); /* NOTE: timeout value is stored in 6 bits so max value is 63 */ @@ -1571,35 +1603,56 @@ static ssize_t kbd_led_timeout_store(struct device *dev, } } + mutex_lock(&kbd_led_mutex); + ret = kbd_get_state(&state); if (ret) - return ret; + goto out; new_state = state; - new_state.timeout_value = value; - new_state.timeout_unit = unit; + + if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { + new_state.timeout_value_ac = value; + new_state.timeout_unit_ac = unit; + } else { + new_state.timeout_value = value; + new_state.timeout_unit = unit; + } ret = kbd_set_state_safe(&new_state, &state); if (ret) - return ret; + goto out; - return count; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static ssize_t kbd_led_timeout_show(struct device *dev, struct device_attribute *attr, char *buf) { struct kbd_state state; + int value; int ret; int len; + u8 unit; ret = kbd_get_state(&state); if (ret) return ret; - len = sprintf(buf, "%d", state.timeout_value); + if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { + value = state.timeout_value_ac; + unit = state.timeout_unit_ac; + } else { + value = state.timeout_value; + unit = state.timeout_unit; + } + + len = sprintf(buf, "%d", value); - switch (state.timeout_unit) { + switch (unit) { case KBD_TIMEOUT_SECONDS: return len + sprintf(buf+len, "s\n"); case KBD_TIMEOUT_MINUTES: @@ -1643,9 +1696,11 @@ static ssize_t kbd_led_triggers_store(struct device *dev, if (trigger[0] != '+' && trigger[0] != '-') return -EINVAL; + mutex_lock(&kbd_led_mutex); + ret = kbd_get_state(&state); if (ret) - return ret; + goto out; if (kbd_triggers_supported) triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); @@ -1659,48 +1714,62 @@ static ssize_t kbd_led_triggers_store(struct device *dev, if (strcmp(trigger+1, kbd_led_triggers[i]) != 0) continue; if (trigger[0] == '+' && - triggers_enabled && (state.triggers & BIT(i))) - return count; + triggers_enabled && (state.triggers & BIT(i))) { + ret = count; + goto out; + } if (trigger[0] == '-' && - (!triggers_enabled || !(state.triggers & BIT(i)))) - return count; + (!triggers_enabled || !(state.triggers & BIT(i)))) { + ret = count; + goto out; + } trigger_bit = i; break; } } - if (trigger_bit != -1) { - new_state = state; - if (trigger[0] == '+') - new_state.triggers |= BIT(trigger_bit); - else { - new_state.triggers &= ~BIT(trigger_bit); - /* NOTE: trackstick bit (2) must be disabled when - * disabling touchpad bit (1), otherwise touchpad - * bit (1) will not be disabled */ - if (trigger_bit == 1) - new_state.triggers &= ~BIT(2); - } - if ((kbd_info.triggers & new_state.triggers) != - new_state.triggers) - return -EINVAL; - if (new_state.triggers && !triggers_enabled) { - new_state.mode_bit = KBD_MODE_BIT_TRIGGER; - kbd_set_level(&new_state, kbd_previous_level); - } else if (new_state.triggers == 0) { - kbd_set_level(&new_state, 0); - } - if (!(kbd_info.modes & BIT(new_state.mode_bit))) - return -EINVAL; - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - return ret; - if (new_state.mode_bit != KBD_MODE_BIT_OFF) - kbd_previous_mode_bit = new_state.mode_bit; - return count; + if (trigger_bit == -1) { + ret = -EINVAL; + goto out; } - return -EINVAL; + new_state = state; + if (trigger[0] == '+') + new_state.triggers |= BIT(trigger_bit); + else { + new_state.triggers &= ~BIT(trigger_bit); + /* + * NOTE: trackstick bit (2) must be disabled when + * disabling touchpad bit (1), otherwise touchpad + * bit (1) will not be disabled + */ + if (trigger_bit == 1) + new_state.triggers &= ~BIT(2); + } + if ((kbd_info.triggers & new_state.triggers) != + new_state.triggers) { + ret = -EINVAL; + goto out; + } + if (new_state.triggers && !triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else if (new_state.triggers == 0) { + kbd_set_level(&new_state, 0); + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) { + ret = -EINVAL; + goto out; + } + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + if (new_state.mode_bit != KBD_MODE_BIT_OFF) + kbd_previous_mode_bit = new_state.mode_bit; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static ssize_t kbd_led_triggers_show(struct device *dev, @@ -1757,12 +1826,16 @@ static ssize_t kbd_led_als_enabled_store(struct device *dev, if (ret) return ret; + mutex_lock(&kbd_led_mutex); + ret = kbd_get_state(&state); if (ret) - return ret; + goto out; - if (enable == kbd_is_als_mode_bit(state.mode_bit)) - return count; + if (enable == kbd_is_als_mode_bit(state.mode_bit)) { + ret = count; + goto out; + } new_state = state; @@ -1782,15 +1855,20 @@ static ssize_t kbd_led_als_enabled_store(struct device *dev, new_state.mode_bit = KBD_MODE_BIT_ON; } } - if (!(kbd_info.modes & BIT(new_state.mode_bit))) - return -EINVAL; + if (!(kbd_info.modes & BIT(new_state.mode_bit))) { + ret = -EINVAL; + goto out; + } ret = kbd_set_state_safe(&new_state, &state); if (ret) - return ret; + goto out; kbd_previous_mode_bit = new_state.mode_bit; - return count; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static ssize_t kbd_led_als_enabled_show(struct device *dev, @@ -1825,18 +1903,23 @@ static ssize_t kbd_led_als_setting_store(struct device *dev, if (ret) return ret; + mutex_lock(&kbd_led_mutex); + ret = kbd_get_state(&state); if (ret) - return ret; + goto out; new_state = state; new_state.als_setting = setting; ret = kbd_set_state_safe(&new_state, &state); if (ret) - return ret; + goto out; - return count; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static ssize_t kbd_led_als_setting_show(struct device *dev, @@ -1921,31 +2004,37 @@ static int kbd_led_level_set(struct led_classdev *led_cdev, u16 num; int ret; + mutex_lock(&kbd_led_mutex); + if (kbd_get_max_level()) { ret = kbd_get_state(&state); if (ret) - return ret; + goto out; new_state = state; ret = kbd_set_level(&new_state, value); if (ret) - return ret; - return kbd_set_state_safe(&new_state, &state); - } - - if (kbd_get_valid_token_counts()) { + goto out; + ret = kbd_set_state_safe(&new_state, &state); + } else if (kbd_get_valid_token_counts()) { for (num = kbd_token_bits; num != 0 && value > 0; --value) num &= num - 1; /* clear the first bit set */ if (num == 0) - return 0; - return kbd_set_token_bit(ffs(num) - 1); + ret = 0; + else + ret = kbd_set_token_bit(ffs(num) - 1); + } else { + pr_warn("Keyboard brightness level control not supported\n"); + ret = -ENXIO; } - pr_warn("Keyboard brightness level control not supported\n"); - return -ENXIO; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static struct led_classdev kbd_led = { .name = "dell::kbd_backlight", + .flags = LED_BRIGHT_HW_CHANGED, .brightness_set_blocking = kbd_led_level_set, .brightness_get = kbd_led_level_get, .groups = kbd_led_groups, @@ -1953,6 +2042,8 @@ static struct led_classdev kbd_led = { static int __init kbd_led_init(struct device *dev) { + int ret; + kbd_init(); if (!kbd_led_present) return -ENODEV; @@ -1964,7 +2055,11 @@ static int __init kbd_led_init(struct device *dev) if (kbd_led.max_brightness) kbd_led.max_brightness--; } - return led_classdev_register(dev, &kbd_led); + ret = led_classdev_register(dev, &kbd_led); + if (ret) + kbd_led_present = false; + + return ret; } static void brightness_set_exit(struct led_classdev *led_cdev, @@ -1981,6 +2076,26 @@ static void kbd_led_exit(void) led_classdev_unregister(&kbd_led); } +static int dell_laptop_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + switch (action) { + case DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED: + if (!kbd_led_present) + break; + + led_classdev_notify_brightness_hw_changed(&kbd_led, + kbd_led_level_get(&kbd_led)); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block dell_laptop_notifier = { + .notifier_call = dell_laptop_notifier_call, +}; + int dell_micmute_led_set(int state) { struct calling_interface_buffer *buffer; @@ -2049,6 +2164,8 @@ static int __init dell_init(void) debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, &dell_debugfs_fops); + dell_laptop_register_notifier(&dell_laptop_notifier); + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) return 0; @@ -2081,11 +2198,17 @@ static int __init dell_init(void) dell_backlight_device->props.brightness = dell_get_intensity(dell_backlight_device); + if (dell_backlight_device->props.brightness < 0) { + ret = dell_backlight_device->props.brightness; + goto fail_get_brightness; + } backlight_update_status(dell_backlight_device); } return 0; +fail_get_brightness: + backlight_device_unregister(dell_backlight_device); fail_backlight: dell_cleanup_rfkill(); fail_rfkill: @@ -2100,6 +2223,7 @@ fail_platform_driver: static void __exit dell_exit(void) { + dell_laptop_unregister_notifier(&dell_laptop_notifier); debugfs_remove_recursive(dell_laptop_dir); if (quirks && quirks->touchpad_led) touchpad_led_exit(); |