platform/x86: ideapad-laptop: Add support for keyboard backlights using KBLC ACPI...
authorStuart Hayhurst <stuart.a.hayhurst@gmail.com>
Sun, 27 Aug 2023 16:19:41 +0000 (17:19 +0100)
committerHans de Goede <hdegoede@redhat.com>
Mon, 28 Aug 2023 08:48:53 +0000 (10:48 +0200)
Newer Lenovo laptops seem to use the KBLC symbol to control the backlight
Add support for handling the keyboard backlight on these devices

Signed-off-by: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
Link: https://lore.kernel.org/r/20230827161940.485200-1-stuart.a.hayhurst@gmail.com
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
drivers/platform/x86/ideapad-laptop.c

index 6d9297c..ac03754 100644 (file)
@@ -10,6 +10,7 @@
 
 #include <linux/acpi.h>
 #include <linux/backlight.h>
+#include <linux/bitfield.h>
 #include <linux/bitops.h>
 #include <linux/bug.h>
 #include <linux/debugfs.h>
@@ -85,6 +86,31 @@ enum {
        SALS_FNLOCK_OFF       = 0xf,
 };
 
+/*
+ * These correspond to the number of supported states - 1
+ * Future keyboard types may need a new system, if there's a collision
+ * KBD_BL_TRISTATE_AUTO has no way to report or set the auto state
+ * so it effectively has 3 states, but needs to handle 4
+ */
+enum {
+       KBD_BL_STANDARD      = 1,
+       KBD_BL_TRISTATE      = 2,
+       KBD_BL_TRISTATE_AUTO = 3,
+};
+
+#define KBD_BL_QUERY_TYPE              0x1
+#define KBD_BL_TRISTATE_TYPE           0x5
+#define KBD_BL_TRISTATE_AUTO_TYPE      0x7
+
+#define KBD_BL_COMMAND_GET             0x2
+#define KBD_BL_COMMAND_SET             0x3
+#define KBD_BL_COMMAND_TYPE            GENMASK(7, 4)
+
+#define KBD_BL_GET_BRIGHTNESS          GENMASK(15, 1)
+#define KBD_BL_SET_BRIGHTNESS          GENMASK(19, 16)
+
+#define KBD_BL_KBLC_CHANGED_EVENT      12
+
 struct ideapad_dytc_priv {
        enum platform_profile_option current_profile;
        struct platform_profile_handler pprof;
@@ -122,6 +148,7 @@ struct ideapad_private {
        } features;
        struct {
                bool initialized;
+               int type;
                struct led_classdev led;
                unsigned int last_brightness;
        } kbd_bl;
@@ -242,6 +269,16 @@ static int exec_sals(acpi_handle handle, unsigned long arg)
        return exec_simple_method(handle, "SALS", arg);
 }
 
+static int exec_kblc(acpi_handle handle, unsigned long arg)
+{
+       return exec_simple_method(handle, "KBLC", arg);
+}
+
+static int eval_kblc(acpi_handle handle, unsigned long cmd, unsigned long *res)
+{
+       return eval_int_with_arg(handle, "KBLC", cmd, res);
+}
+
 static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res)
 {
        return eval_int_with_arg(handle, "DYTC", cmd, res);
@@ -1275,16 +1312,47 @@ static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
 /*
  * keyboard backlight
  */
+static int ideapad_kbd_bl_check_tristate(int type)
+{
+       return (type == KBD_BL_TRISTATE) || (type == KBD_BL_TRISTATE_AUTO);
+}
+
 static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
 {
-       unsigned long hals;
+       unsigned long value;
        int err;
 
-       err = eval_hals(priv->adev->handle, &hals);
+       if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
+               err = eval_kblc(priv->adev->handle,
+                               FIELD_PREP(KBD_BL_COMMAND_TYPE, priv->kbd_bl.type) |
+                               KBD_BL_COMMAND_GET,
+                               &value);
+
+               if (err)
+                       return err;
+
+               /* Convert returned value to brightness level */
+               value = FIELD_GET(KBD_BL_GET_BRIGHTNESS, value);
+
+               /* Off, low or high */
+               if (value <= priv->kbd_bl.led.max_brightness)
+                       return value;
+
+               /* Auto, report as off */
+               if (value == priv->kbd_bl.led.max_brightness + 1)
+                       return 0;
+
+               /* Unknown value */
+               dev_warn(&priv->platform_device->dev,
+                        "Unknown keyboard backlight value: %lu", value);
+               return -EINVAL;
+       }
+
+       err = eval_hals(priv->adev->handle, &value);
        if (err)
                return err;
 
-       return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals);
+       return !!test_bit(HALS_KBD_BL_STATE_BIT, &value);
 }
 
 static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev)
@@ -1296,7 +1364,21 @@ static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_cla
 
 static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness)
 {
-       int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF);
+       int err;
+       unsigned long value;
+       int type = priv->kbd_bl.type;
+
+       if (ideapad_kbd_bl_check_tristate(type)) {
+               if (brightness > priv->kbd_bl.led.max_brightness)
+                       return -EINVAL;
+
+               value = FIELD_PREP(KBD_BL_SET_BRIGHTNESS, brightness) |
+                       FIELD_PREP(KBD_BL_COMMAND_TYPE, type) |
+                       KBD_BL_COMMAND_SET;
+               err = exec_kblc(priv->adev->handle, value);
+       } else {
+               err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF);
+       }
 
        if (err)
                return err;
@@ -1349,8 +1431,13 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
 
        priv->kbd_bl.last_brightness = brightness;
 
+       if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
+               priv->kbd_bl.led.max_brightness = 2;
+       } else {
+               priv->kbd_bl.led.max_brightness = 1;
+       }
+
        priv->kbd_bl.led.name                    = "platform::" LED_FUNCTION_KBD_BACKLIGHT;
-       priv->kbd_bl.led.max_brightness          = 1;
        priv->kbd_bl.led.brightness_get          = ideapad_kbd_bl_led_cdev_brightness_get;
        priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set;
        priv->kbd_bl.led.flags                   = LED_BRIGHT_HW_CHANGED;
@@ -1461,6 +1548,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
                case 2:
                        ideapad_backlight_notify_power(priv);
                        break;
+               case KBD_BL_KBLC_CHANGED_EVENT:
                case 1:
                        /*
                         * Some IdeaPads report event 1 every ~20
@@ -1562,13 +1650,31 @@ static void ideapad_check_features(struct ideapad_private *priv)
                        if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val))
                                priv->features.fn_lock = true;
 
-                       if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val))
+                       if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) {
                                priv->features.kbd_bl = true;
+                               priv->kbd_bl.type = KBD_BL_STANDARD;
+                       }
 
                        if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val))
                                priv->features.usb_charging = true;
                }
        }
+
+       if (acpi_has_method(handle, "KBLC")) {
+               if (!eval_kblc(priv->adev->handle, KBD_BL_QUERY_TYPE, &val)) {
+                       if (val == KBD_BL_TRISTATE_TYPE) {
+                               priv->features.kbd_bl = true;
+                               priv->kbd_bl.type = KBD_BL_TRISTATE;
+                       } else if (val == KBD_BL_TRISTATE_AUTO_TYPE) {
+                               priv->features.kbd_bl = true;
+                               priv->kbd_bl.type = KBD_BL_TRISTATE_AUTO;
+                       } else {
+                               dev_warn(&priv->platform_device->dev,
+                                        "Unknown keyboard type: %lu",
+                                        val);
+                       }
+               }
+       }
 }
 
 #if IS_ENABLED(CONFIG_ACPI_WMI)