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

From: Eray Orçunus
Date: Wed Oct 26 2022 - 15:03:48 EST


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@xxxxxxxxx>
---
drivers/platform/x86/ideapad-laptop.c | 79 ++++++++++++++++++++++++---
1 file changed, 70 insertions(+), 9 deletions(-)

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;
--
2.34.1