From: Ard Biesheuvel <ardb+git@google.com>
To: linux-arm-kernel@lists.infradead.org
Cc: Ard Biesheuvel <ardb@kernel.org>,
Bjorn Andersson <andersson@kernel.org>,
Konrad Dybcio <konradybcio@kernel.org>,
Abel Vesa <abelvesa@kernel.org>,
Dmitry Baryshkov <lumag@kernel.org>,
linux-arm-msm@vger.kernel.org
Subject: [PATCH 7/7] platform: arm64: lenovo-yoga-c630: Expose the keyboard backlight as an LED
Date: Wed, 11 Nov 2026 12:53:11 +0100 [thread overview]
Message-ID: <20261111115303.3749380-16-ardb+git@google.com> (raw)
In-Reply-To: <20261111115303.3749380-9-ardb+git@google.com>
From: Ard Biesheuvel <ardb@kernel.org>
The EC turns the keyboard backlight on and off as Fn+Space is pressed,
cycling through two brightness levels, which the EC registers do not
seem to reflect. However, bit 0 of EC register 0x03 reflects whether
the backlight is on, and setting or clearing it, using the EC's register
write command (0xb1, as used by the DSDT's ECNW method), turns the
backlight on at the selected brightness level, or off.
Expose this as a platform::kbd_backlight LED with a maximum brightness
of 1, so that userspace can turn off the keyboard backlight, e.g., when
the system is idle. Whenever the EC raises an event, check whether the
backlight was turned on or off, and report any such change through
brightness_hw_changed, in case the EC raises an event when Fn+Space is
pressed.
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
drivers/platform/arm64/Kconfig | 4 +-
drivers/platform/arm64/lenovo-yoga-c630.c | 121 +++++++++++++++++++-
2 files changed, 120 insertions(+), 5 deletions(-)
diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
index 2a84775a13a9..44d5a749f764 100644
--- a/drivers/platform/arm64/Kconfig
+++ b/drivers/platform/arm64/Kconfig
@@ -75,10 +75,12 @@ config EC_LENOVO_YOGA_C630
depends on ARCH_QCOM || COMPILE_TEST
depends on I2C
select AUXILIARY_BUS
+ select LEDS_CLASS
+ select NEW_LEDS
help
Driver for the Embedded Controller in the Qualcomm Snapdragon-based
Lenovo Yoga C630, which provides battery and power adapter
- information.
+ information, and access to the keyboard backlight.
This driver provides battery and AC status support for the mentioned
laptop where this information is not properly exposed via the
diff --git a/drivers/platform/arm64/lenovo-yoga-c630.c b/drivers/platform/arm64/lenovo-yoga-c630.c
index 2776063bfb58..7f49067ec6b7 100644
--- a/drivers/platform/arm64/lenovo-yoga-c630.c
+++ b/drivers/platform/arm64/lenovo-yoga-c630.c
@@ -6,12 +6,14 @@
* Dmitry Baryshkov
*/
#include <linux/auxiliary_bus.h>
+#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
+#include <linux/leds.h>
#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -27,14 +29,20 @@
#define LENOVO_EC_UCSI_READ 0x21
#define LENOVO_EC_READ_REG 0xb0
+#define LENOVO_EC_WRITE_REG 0xb1
#define LENOVO_EC_REQUEST_NEXT_EVENT 0x84
#define LENOVO_EC_UCSI_VERSION 0x20
+#define LENOVO_EC_KBD_BACKLIGHT_REG 0x03
+#define LENOVO_EC_KBD_BACKLIGHT_ON BIT(0)
+
struct yoga_c630_ec {
struct i2c_client *client;
struct mutex lock;
struct blocking_notifier_head notifier_list;
+ struct led_classdev kbd_backlight;
+ enum led_brightness kbd_backlight_brightness;
};
static int yoga_c630_ec_request(struct yoga_c630_ec *ec, u8 *req, size_t req_len,
@@ -53,21 +61,27 @@ static int yoga_c630_ec_request(struct yoga_c630_ec *ec, u8 *req, size_t req_len
resp_len, resp);
}
-int yoga_c630_ec_read8(struct yoga_c630_ec *ec, u8 addr)
+static int __yoga_c630_ec_read8(struct yoga_c630_ec *ec, u8 addr)
{
- u8 req[2] = { LENOVO_EC_READ_REG, };
+ u8 req[2] = { LENOVO_EC_READ_REG, addr };
int ret;
u8 val;
- guard(mutex)(&ec->lock);
+ lockdep_assert_held(&ec->lock);
- req[1] = addr;
ret = yoga_c630_ec_request(ec, req, sizeof(req), &val, 1);
if (ret < 0)
return ret;
return val;
}
+
+int yoga_c630_ec_read8(struct yoga_c630_ec *ec, u8 addr)
+{
+ guard(mutex)(&ec->lock);
+
+ return __yoga_c630_ec_read8(ec, addr);
+}
EXPORT_SYMBOL_GPL(yoga_c630_ec_read8);
int yoga_c630_ec_read16(struct yoga_c630_ec *ec, u8 addr)
@@ -97,6 +111,31 @@ int yoga_c630_ec_read16(struct yoga_c630_ec *ec, u8 addr)
}
EXPORT_SYMBOL_GPL(yoga_c630_ec_read16);
+/*
+ * Update the bits in @mask of EC register @addr to those in @val. Return the
+ * previous value of the register, or a negative error code.
+ */
+static int yoga_c630_ec_update_bits(struct yoga_c630_ec *ec, u8 addr, u8 mask,
+ u8 val)
+{
+ u8 req[3] = { LENOVO_EC_WRITE_REG, addr, };
+ int old, ret;
+
+ guard(mutex)(&ec->lock);
+
+ old = __yoga_c630_ec_read8(ec, addr);
+ if (old < 0 || (old & mask) == (val & mask))
+ return old;
+
+ req[2] = (old & ~mask) | (val & mask);
+ ret = i2c_smbus_write_i2c_block_data(ec->client, LENOVO_EC_REQUEST_REG,
+ sizeof(req), req);
+ if (ret < 0)
+ return ret;
+
+ return old;
+}
+
u16 yoga_c630_ec_ucsi_get_version(struct yoga_c630_ec *ec)
{
u8 req[3] = { 0xb3, 0xf2, };
@@ -148,6 +187,73 @@ int yoga_c630_ec_ucsi_read(struct yoga_c630_ec *ec,
}
EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_read);
+static enum led_brightness
+yoga_c630_ec_kbd_backlight_get(struct led_classdev *led_cdev)
+{
+ struct yoga_c630_ec *ec = container_of(led_cdev, struct yoga_c630_ec,
+ kbd_backlight);
+ int val;
+
+ val = yoga_c630_ec_read8(ec, LENOVO_EC_KBD_BACKLIGHT_REG);
+ if (val < 0)
+ return val;
+
+ return val & LENOVO_EC_KBD_BACKLIGHT_ON ? LED_ON : LED_OFF;
+}
+
+static int yoga_c630_ec_kbd_backlight_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct yoga_c630_ec *ec = container_of(led_cdev, struct yoga_c630_ec,
+ kbd_backlight);
+ int old;
+
+ old = yoga_c630_ec_update_bits(ec, LENOVO_EC_KBD_BACKLIGHT_REG,
+ LENOVO_EC_KBD_BACKLIGHT_ON,
+ brightness ? LENOVO_EC_KBD_BACKLIGHT_ON : 0);
+ if (old < 0)
+ return old;
+
+ WRITE_ONCE(ec->kbd_backlight_brightness, brightness ? LED_ON : LED_OFF);
+
+ return 0;
+}
+
+/* Report changes made by the EC itself, i.e., when Fn+Space is pressed */
+static void yoga_c630_ec_kbd_backlight_update(struct yoga_c630_ec *ec)
+{
+ int brightness;
+
+ brightness = yoga_c630_ec_kbd_backlight_get(&ec->kbd_backlight);
+ if (brightness < 0 ||
+ brightness == READ_ONCE(ec->kbd_backlight_brightness))
+ return;
+
+ WRITE_ONCE(ec->kbd_backlight_brightness, brightness);
+ led_classdev_notify_brightness_hw_changed(&ec->kbd_backlight,
+ brightness);
+}
+
+static int yoga_c630_ec_kbd_backlight_init(struct yoga_c630_ec *ec)
+{
+ struct led_classdev *led_cdev = &ec->kbd_backlight;
+ int brightness;
+
+ brightness = yoga_c630_ec_kbd_backlight_get(led_cdev);
+ if (brightness < 0)
+ return brightness;
+
+ ec->kbd_backlight_brightness = brightness;
+
+ led_cdev->name = "platform::kbd_backlight";
+ led_cdev->flags = LED_BRIGHT_HW_CHANGED;
+ led_cdev->max_brightness = LED_ON;
+ led_cdev->brightness_get = yoga_c630_ec_kbd_backlight_get;
+ led_cdev->brightness_set_blocking = yoga_c630_ec_kbd_backlight_set;
+
+ return devm_led_classdev_register(&ec->client->dev, led_cdev);
+}
+
static irqreturn_t yoga_c630_ec_thread_intr(int irq, void *data)
{
u8 req[] = { LENOVO_EC_REQUEST_NEXT_EVENT };
@@ -163,6 +269,8 @@ static irqreturn_t yoga_c630_ec_thread_intr(int irq, void *data)
blocking_notifier_call_chain(&ec->notifier_list, event, ec);
+ yoga_c630_ec_kbd_backlight_update(ec);
+
return IRQ_HANDLED;
}
@@ -220,6 +328,11 @@ static int yoga_c630_ec_probe(struct i2c_client *client)
BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list);
i2c_set_clientdata(client, ec);
+ ret = yoga_c630_ec_kbd_backlight_init(ec);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register keyboard backlight\n");
+
ret = devm_request_threaded_irq(dev, client->irq,
NULL, yoga_c630_ec_thread_intr,
IRQF_ONESHOT, "yoga_c630_ec", ec);
--
2.56.0.611.g1030f0b940-goog
prev parent reply other threads:[~2026-11-11 11:53 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 11:53 [PATCH 0/7] Lenovo Yoga C630 (sdm845) fixes Ard Biesheuvel
2026-11-11 11:53 ` [PATCH 1/7] arm64: dts: qcom: sdm845: Bump fastrpc shared DMA pool to 32MB for 16k pages Ard Biesheuvel
2026-11-11 14:42 ` Abel Vesa
2026-11-11 11:53 ` [PATCH 2/7] arm64: dts: qcom: c630: Use LN_BB_CLK2 as the eDP bridge reference clock Ard Biesheuvel
2026-11-11 12:43 ` Dmitry Baryshkov
2026-11-11 13:15 ` Ard Biesheuvel
2026-11-11 13:45 ` Ard Biesheuvel
2026-11-11 11:53 ` [PATCH 3/7] arm64: dts: qcom: c630: Enable DisplayPort alt mode Ard Biesheuvel
2026-11-11 12:50 ` Dmitry Baryshkov
2026-11-11 14:42 ` Abel Vesa
2026-11-11 11:53 ` [PATCH 4/7] arm64: dts: qcom: c630: Use the idle states from the ACPI tables Ard Biesheuvel
2026-11-11 12:56 ` Dmitry Baryshkov
2026-11-11 14:42 ` Abel Vesa
2026-11-11 11:53 ` [PATCH 5/7] arm64: dts: qcom: c630: Wait for the touchpad at resume Ard Biesheuvel
2026-11-11 12:22 ` Ard Biesheuvel
2026-11-11 12:58 ` Dmitry Baryshkov
2026-11-11 13:16 ` Ard Biesheuvel
2026-11-11 12:56 ` Dmitry Baryshkov
2026-11-11 11:53 ` [PATCH 6/7] platform: arm64: lenovo-yoga-c630: Disable the EC interrupt while suspended Ard Biesheuvel
2026-11-11 12:57 ` Dmitry Baryshkov
2026-11-11 11:53 ` Ard Biesheuvel [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261111115303.3749380-16-ardb+git@google.com \
--to=ardb+git@google.com \
--cc=abelvesa@kernel.org \
--cc=andersson@kernel.org \
--cc=ardb@kernel.org \
--cc=konradybcio@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=lumag@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox