thinkpad_acpi: Add support for keyboard backlight
authorPali Rohár <pali.rohar@gmail.com>
Wed, 30 Dec 2015 22:27:41 +0000 (23:27 +0100)
committerDarren Hart <dvhart@linux.intel.com>
Wed, 20 Jan 2016 01:35:49 +0000 (17:35 -0800)
This patch adds support for controlling keyboard backlight via standard
linux led class interface (::kbd_backlight). It uses ACPI HKEY device with
MLCG and MLCS methods.

Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
Tested-by: Fabio D'Urso <fabiodurso@hotmail.it>
Acked-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Darren Hart <dvhart@linux.intel.com>
drivers/platform/x86/thinkpad_acpi.c

index 0bed4733c4f04925831a174b6b58e674ca2fe56c..a268a7abf8abe754f30fba55941b04fef2ffdef8 100644 (file)
@@ -303,6 +303,7 @@ static struct {
        u32 hotkey_mask:1;
        u32 hotkey_wlsw:1;
        u32 hotkey_tablet:1;
+       u32 kbdlight:1;
        u32 light:1;
        u32 light_status:1;
        u32 bright_acpimode:1;
@@ -4985,6 +4986,207 @@ static struct ibm_struct video_driver_data = {
 
 #endif /* CONFIG_THINKPAD_ACPI_VIDEO */
 
+/*************************************************************************
+ * Keyboard backlight subdriver
+ */
+
+static int kbdlight_set_level(int level)
+{
+       if (!hkey_handle)
+               return -ENXIO;
+
+       if (!acpi_evalf(hkey_handle, NULL, "MLCS", "dd", level))
+               return -EIO;
+
+       return 0;
+}
+
+static int kbdlight_get_level(void)
+{
+       int status = 0;
+
+       if (!hkey_handle)
+               return -ENXIO;
+
+       if (!acpi_evalf(hkey_handle, &status, "MLCG", "dd", 0))
+               return -EIO;
+
+       if (status < 0)
+               return status;
+
+       return status & 0x3;
+}
+
+static bool kbdlight_is_supported(void)
+{
+       int status = 0;
+
+       if (!hkey_handle)
+               return false;
+
+       if (!acpi_has_method(hkey_handle, "MLCG")) {
+               vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG is unavailable\n");
+               return false;
+       }
+
+       if (!acpi_evalf(hkey_handle, &status, "MLCG", "qdd", 0)) {
+               vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG failed\n");
+               return false;
+       }
+
+       if (status < 0) {
+               vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG err: %d\n", status);
+               return false;
+       }
+
+       vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG returned 0x%x\n", status);
+       /*
+        * Guessed test for keyboard backlight:
+        *
+        * Machines with backlight keyboard return:
+        *   b010100000010000000XX - ThinkPad X1 Carbon 3rd
+        *   b110100010010000000XX - ThinkPad x230
+        *   b010100000010000000XX - ThinkPad x240
+        *   b010100000010000000XX - ThinkPad W541
+        * (XX is current backlight level)
+        *
+        * Machines without backlight keyboard return:
+        *   b10100001000000000000 - ThinkPad x230
+        *   b10110001000000000000 - ThinkPad E430
+        *   b00000000000000000000 - ThinkPad E450
+        *
+        * Candidate BITs for detection test (XOR):
+        *   b01000000001000000000
+        *              ^
+        */
+       return status & BIT(9);
+}
+
+static void kbdlight_set_worker(struct work_struct *work)
+{
+       struct tpacpi_led_classdev *data =
+                       container_of(work, struct tpacpi_led_classdev, work);
+
+       if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
+               kbdlight_set_level(data->new_state);
+}
+
+static void kbdlight_sysfs_set(struct led_classdev *led_cdev,
+                       enum led_brightness brightness)
+{
+       struct tpacpi_led_classdev *data =
+                       container_of(led_cdev,
+                                    struct tpacpi_led_classdev,
+                                    led_classdev);
+       data->new_state = brightness;
+       queue_work(tpacpi_wq, &data->work);
+}
+
+static enum led_brightness kbdlight_sysfs_get(struct led_classdev *led_cdev)
+{
+       int level;
+
+       level = kbdlight_get_level();
+       if (level < 0)
+               return 0;
+
+       return level;
+}
+
+static struct tpacpi_led_classdev tpacpi_led_kbdlight = {
+       .led_classdev = {
+               .name           = "tpacpi::kbd_backlight",
+               .max_brightness = 2,
+               .brightness_set = &kbdlight_sysfs_set,
+               .brightness_get = &kbdlight_sysfs_get,
+               .flags          = LED_CORE_SUSPENDRESUME,
+       }
+};
+
+static int __init kbdlight_init(struct ibm_init_struct *iibm)
+{
+       int rc;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing kbdlight subdriver\n");
+
+       TPACPI_ACPIHANDLE_INIT(hkey);
+       INIT_WORK(&tpacpi_led_kbdlight.work, kbdlight_set_worker);
+
+       if (!kbdlight_is_supported()) {
+               tp_features.kbdlight = 0;
+               vdbg_printk(TPACPI_DBG_INIT, "kbdlight is unsupported\n");
+               return 1;
+       }
+
+       tp_features.kbdlight = 1;
+
+       rc = led_classdev_register(&tpacpi_pdev->dev,
+                                  &tpacpi_led_kbdlight.led_classdev);
+       if (rc < 0) {
+               tp_features.kbdlight = 0;
+               return rc;
+       }
+
+       return 0;
+}
+
+static void kbdlight_exit(void)
+{
+       if (tp_features.kbdlight)
+               led_classdev_unregister(&tpacpi_led_kbdlight.led_classdev);
+       flush_workqueue(tpacpi_wq);
+}
+
+static int kbdlight_read(struct seq_file *m)
+{
+       int level;
+
+       if (!tp_features.kbdlight) {
+               seq_printf(m, "status:\t\tnot supported\n");
+       } else {
+               level = kbdlight_get_level();
+               if (level < 0)
+                       seq_printf(m, "status:\t\terror %d\n", level);
+               else
+                       seq_printf(m, "status:\t\t%d\n", level);
+               seq_printf(m, "commands:\t0, 1, 2\n");
+       }
+
+       return 0;
+}
+
+static int kbdlight_write(char *buf)
+{
+       char *cmd;
+       int level = -1;
+
+       if (!tp_features.kbdlight)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "0") == 0)
+                       level = 0;
+               else if (strlencmp(cmd, "1") == 0)
+                       level = 1;
+               else if (strlencmp(cmd, "2") == 0)
+                       level = 2;
+               else
+                       return -EINVAL;
+       }
+
+       if (level == -1)
+               return -EINVAL;
+
+       return kbdlight_set_level(level);
+}
+
+static struct ibm_struct kbdlight_driver_data = {
+       .name = "kbdlight",
+       .read = kbdlight_read,
+       .write = kbdlight_write,
+       .exit = kbdlight_exit,
+};
+
 /*************************************************************************
  * Light (thinklight) subdriver
  */
@@ -9206,6 +9408,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
                .data = &video_driver_data,
        },
 #endif
+       {
+               .init = kbdlight_init,
+               .data = &kbdlight_driver_data,
+       },
        {
                .init = light_init,
                .data = &light_driver_data,