summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/dell-laptop.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/dell-laptop.c')
-rw-r--r--drivers/platform/x86/dell-laptop.c254
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();