[6/6] platform/x86: ideapad-laptop: Keyboard backlight support for more IdeaPads

Message ID 20221026190106.28441-7-erayorcunus@gmail.com
State New
Headers
Series Add camera access keys, IdeaPad driver improvements |

Commit Message

Eray Orçunus Oct. 26, 2022, 7:01 p.m. UTC
  IdeaPads with HALS_KBD_BL_SUPPORT_BIT have full keyboard light support,
and they send an event via ACPI on light state change. Whereas some
IdeaPads that don't have this bit set, i.e. 520-15ikb, 330-17ich and
5 (15), don't send an event, yet they still support switching keyboard
light via KBLO object on DSDT. Detect these IdeaPads with searching for
KBLO object, set their kbd_bl_partial to true and register led device
for them. Tested on 520-15ikb.

Signed-off-by: Eray Orçunus <erayorcunus@gmail.com>
---
 drivers/platform/x86/ideapad-laptop.c | 79 ++++++++++++++++++++++++---
 1 file changed, 70 insertions(+), 9 deletions(-)
  

Patch

diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
index 65eea2e65bbe..c30bbe0ca156 100644
--- a/drivers/platform/x86/ideapad-laptop.c
+++ b/drivers/platform/x86/ideapad-laptop.c
@@ -149,6 +149,7 @@  struct ideapad_private {
 		bool fn_lock              : 1;
 		bool hw_rfkill_switch     : 1;
 		bool kbd_bl               : 1;
+		bool kbd_bl_partial       : 1;
 		bool cam_ctrl_via_ec      : 1;
 		bool touchpad_ctrl_via_ec : 1;
 		bool usb_charging         : 1;
@@ -157,6 +158,9 @@  struct ideapad_private {
 		bool initialized;
 		struct led_classdev led;
 		unsigned int last_brightness;
+		/* Below are used only if kbd_bl_partial is set */
+		acpi_handle lfcm_mutex;
+		acpi_handle kblo_obj;
 	} kbd_bl;
 };
 
@@ -1302,19 +1306,52 @@  static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
 		backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
 }
 
+#define IDEAPAD_ACPI_MUTEX_TIMEOUT 1500
+
 /*
  * keyboard backlight
  */
 static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
 {
-	unsigned long hals;
+	unsigned long ret_val;
 	int err;
+	acpi_status status;
 
-	err = eval_hals(priv->adev->handle, &hals);
+	/*
+	 * Some IdeaPads with partially implemented keyboard lights don't give
+	 * us the light state on HALS_KBD_BL_STATE_BIT in the return value of HALS,
+	 * i.e. 5 (15) and 330-17ich. Fortunately we know how to gather it.
+	 * Even if it won't work, we will still give HALS a try, because
+	 * some IdeaPads with kbd_bl_partial, i.e. 520-15ikb,
+	 * correctly sets HALS_KBD_BL_STATE_BIT in HALS return value.
+	 */
+
+	if (priv->features.kbd_bl_partial &&
+	    priv->kbd_bl.lfcm_mutex != NULL && priv->kbd_bl.kblo_obj != NULL) {
+
+		status = acpi_acquire_mutex(priv->kbd_bl.lfcm_mutex, NULL,
+					    IDEAPAD_ACPI_MUTEX_TIMEOUT);
+
+		if (ACPI_SUCCESS(status)) {
+			err = eval_int(priv->kbd_bl.kblo_obj, NULL, &ret_val);
+
+			status = acpi_release_mutex(priv->kbd_bl.lfcm_mutex, NULL);
+			if (ACPI_FAILURE(status))
+				dev_err(&priv->platform_device->dev,
+					"Failed to release LFCM mutex");
+
+			if (err)
+				return err;
+
+			return !!ret_val;
+		}
+	}
+
+	err = eval_hals(priv->adev->handle, &ret_val);
 	if (err)
 		return err;
 
-	return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals);
+	return !!test_bit(HALS_KBD_BL_STATE_BIT, &ret_val);
 }
 
 static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev)
@@ -1331,7 +1368,8 @@  static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned
 	if (err)
 		return err;
 
-	priv->kbd_bl.last_brightness = brightness;
+	if (!priv->features.kbd_bl_partial)
+		priv->kbd_bl.last_brightness = brightness;
 
 	return 0;
 }
@@ -1351,6 +1389,9 @@  static void ideapad_kbd_bl_notify(struct ideapad_private *priv)
 	if (!priv->kbd_bl.initialized)
 		return;
 
+	if (priv->features.kbd_bl_partial)
+		return;
+
 	brightness = ideapad_kbd_bl_brightness_get(priv);
 	if (brightness < 0)
 		return;
@@ -1373,17 +1414,20 @@  static int ideapad_kbd_bl_init(struct ideapad_private *priv)
 	if (WARN_ON(priv->kbd_bl.initialized))
 		return -EEXIST;
 
-	brightness = ideapad_kbd_bl_brightness_get(priv);
-	if (brightness < 0)
-		return brightness;
+	/* IdeaPads with kbd_bl_partial don't have keyboard backlight event */
+	if (!priv->features.kbd_bl_partial) {
+		brightness = ideapad_kbd_bl_brightness_get(priv);
+		if (brightness < 0)
+			return brightness;
 
-	priv->kbd_bl.last_brightness = brightness;
+		priv->kbd_bl.last_brightness         = brightness;
+		priv->kbd_bl.led.flags               = LED_BRIGHT_HW_CHANGED;
+	}
 
 	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;
 
 	err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led);
 	if (err)
@@ -1595,8 +1639,25 @@  static void ideapad_check_features(struct ideapad_private *priv)
 			if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val))
 				priv->features.fn_lock = true;
 
+			/*
+			 * IdeaPads with HALS_KBD_BL_SUPPORT_BIT have full keyboard
+			 * light support, and they send an event via ACPI on light
+			 * state change. Whereas some IdeaPads, at least 520-15ikb
+			 * and 5 (15), don't send an event, yet they still have
+			 * KBLO object. In this case, set kbd_bl_partial to true
+			 * and cache the LFCM mutex, it might be useful while
+			 * getting the brightness.
+			 */
+
 			if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val))
 				priv->features.kbd_bl = true;
+			else if (ACPI_SUCCESS(acpi_get_handle(handle, "^KBLO",
+							      &priv->kbd_bl.kblo_obj))) {
+				priv->features.kbd_bl = true;
+				priv->features.kbd_bl_partial = true;
+				(void)acpi_get_handle(handle, "^LFCM",
+						      &priv->kbd_bl.lfcm_mutex);
+			}
 
 			if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val))
 				priv->features.usb_charging = true;