From: Torstein Eide <torsteine+linux@gmail.com>
To: Jiri Kosina <jikos@kernel.org>, Benjamin Tissoires <bentiss@kernel.org>
Cc: Hans de Goede <hansg@kernel.org>,
Maoyi Xie <maoyixie.tju@gmail.com>,
linux-input@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH 1/4] HID: lg-g15: add LCD contrast LED
Date: Mon, 9 Nov 2026 10:44:23 +0100 [thread overview]
Message-ID: <20261109094426.1432465-2-torsteine+linux@gmail.com> (raw)
In-Reply-To: <20261109094426.1432465-1-torsteine+linux@gmail.com>
Wire values ({2, 32, 129, value}) match libg15's setLCDContrast()
byte-for-byte.
Tested on Logitech G15v2.
Based on libg15.c.
Signed-off-by: Torstein Eide <torsteine+linux@gmail.com>
Based-on-work-by: Philip Lawatsch <philip@lawatsch.at>
Based-on-work-by: Alex Ibrado <alex@ibrado.org>
Based-on-work-by: Anthony J. Mirabella <mirabeaj@gmail.com>
Based-on-work-by: Mike Lampard <mlampard@users.sourceforge.net>
Assisted-by: Claude:claude-sonnet-5
---
drivers/hid/hid-lg-g15.c | 101 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 99 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c
index 02ef3e2094b4..50abe6c58b10 100644
--- a/drivers/hid/hid-lg-g15.c
+++ b/drivers/hid/hid-lg-g15.c
@@ -59,7 +59,16 @@ enum lg_g15_led_type {
LG_G15_MACRO_PRESET2,
LG_G15_MACRO_PRESET3,
LG_G15_MACRO_RECORD,
- LG_G15_LED_MAX
+ LG_G15_LED_MAX,
+ /*
+ * LCD contrast uses a different feature-report sub-format than the
+ * brightness/macro fields above (see lg_g15_contrast_set()) and has
+ * no hardware readback, so it lives outside [0, LG_G15_LED_MAX) to
+ * avoid being swept into the G13/macro loops bounded by
+ * LG_G15_LED_MAX. G15/G15 v2 only; registered explicitly.
+ */
+ LG_G15_LCD_CONTRAST = LG_G15_LED_MAX,
+ LG_G15_LED_ARRAY_SIZE
};
struct g13_input_report {
@@ -89,9 +98,10 @@ struct lg_g15_data {
struct input_dev *input_js; /* Separate joystick device for G13. */
struct hid_device *hdev;
enum lg_g15_model model;
- struct lg_g15_led leds[LG_G15_LED_MAX];
+ struct lg_g15_led leds[LG_G15_LED_ARRAY_SIZE];
bool game_mode_enabled;
bool backlight_disabled; /* true == HW backlight toggled *OFF* */
+ bool lcd_contrast_set; /* LCD contrast written at least once */
};
/********* G13 LED functions ***********/
@@ -365,6 +375,57 @@ static int lg_g15_led_set(struct led_classdev *led_cdev,
return ret;
}
+/*
+ * LCD contrast, report 2 field 32: {2, 32, 129, value}, value in
+ * {18, 22, 26} for low/medium/high. Distinct sub-format from the
+ * brightness/macro fields (value sits in byte 3, not byte 2, and byte 2 is
+ * a fixed marker rather than the field selector) and has no GET_REPORT
+ * readback, so brightness_get just returns the last value we set.
+ */
+static int lg_g15_contrast_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct lg_g15_led *g15_led =
+ container_of(led_cdev, struct lg_g15_led, cdev);
+ struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
+ static const u8 contrast_values[] = { 18, 22, 26 };
+ int ret;
+
+ if (led_cdev->flags & LED_UNREGISTERING)
+ return 0;
+
+ mutex_lock(&g15->mutex);
+
+ g15->transfer_buf[0] = LG_G15_FEATURE_REPORT;
+ g15->transfer_buf[1] = 32;
+ g15->transfer_buf[2] = 129;
+ g15->transfer_buf[3] = contrast_values[brightness];
+
+ ret = hid_hw_raw_request(g15->hdev, LG_G15_FEATURE_REPORT,
+ g15->transfer_buf, 4,
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret == 4) {
+ g15_led->brightness = brightness;
+ g15->lcd_contrast_set = true;
+ ret = 0;
+ } else {
+ hid_err(g15->hdev, "Error setting LCD contrast: %d\n", ret);
+ ret = (ret < 0) ? ret : -EIO;
+ }
+
+ mutex_unlock(&g15->mutex);
+
+ return ret;
+}
+
+static enum led_brightness lg_g15_contrast_get(struct led_classdev *led_cdev)
+{
+ struct lg_g15_led *g15_led =
+ container_of(led_cdev, struct lg_g15_led, cdev);
+
+ return g15_led->brightness;
+}
+
static void lg_g15_leds_changed_work(struct work_struct *work)
{
struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work);
@@ -1017,6 +1078,13 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
break;
case LG_G15:
case LG_G15_V2:
+ if (i == LG_G15_LCD_CONTRAST) {
+ g15->leds[i].cdev.brightness_set_blocking = lg_g15_contrast_set;
+ g15->leds[i].cdev.brightness_get = lg_g15_contrast_get;
+ g15->leds[i].cdev.max_brightness = 2;
+ ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
+ break;
+ }
g15->leds[i].cdev.brightness_get = lg_g15_led_get;
fallthrough;
case LG_Z10:
@@ -1333,6 +1401,14 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto error_hw_stop;
}
+ /* The G11 shares the LG_G15 model but has no LCD */
+ if ((g15->model == LG_G15 && hdev->product != USB_DEVICE_ID_LOGITECH_G11) ||
+ g15->model == LG_G15_V2) {
+ ret = lg_g15_register_led(g15, LG_G15_LCD_CONTRAST, "g15::lcd_contrast");
+ if (ret)
+ goto error_hw_stop;
+ }
+
return 0;
error_hw_stop:
@@ -1389,12 +1465,33 @@ static void lg_g15_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
}
+/*
+ * The LCD does not retain its contrast across a USB reset or power loss;
+ * re-apply the last value userspace set, if any.
+ */
+static int lg_g15_resume(struct hid_device *hdev)
+{
+ struct lg_g15_data *g15 = hid_get_drvdata(hdev);
+ struct lg_g15_led *contrast;
+
+ if (!g15)
+ return 0;
+
+ contrast = &g15->leds[LG_G15_LCD_CONTRAST];
+ if (contrast->cdev.dev && g15->lcd_contrast_set)
+ lg_g15_contrast_set(&contrast->cdev, contrast->brightness);
+
+ return 0;
+}
+
static struct hid_driver lg_g15_driver = {
.name = "lg-g15",
.id_table = lg_g15_devices,
.raw_event = lg_g15_raw_event,
.probe = lg_g15_probe,
.remove = lg_g15_remove,
+ .resume = lg_g15_resume,
+ .reset_resume = lg_g15_resume,
};
module_hid_driver(lg_g15_driver);
--
2.53.0
next prev parent reply other threads:[~2026-11-09 9:44 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 9:44 [PATCH 0/4] HID: lg-g15: G15 v2 LCD and game-mode support Torstein Eide
2026-11-09 9:44 ` Torstein Eide [this message]
2026-11-09 10:00 ` [PATCH 1/4] HID: lg-g15: add LCD contrast LED sashiko-bot
2026-11-09 9:44 ` [PATCH 2/4] HID: lg-g15: add LCD pixel-content framebuffer Torstein Eide
2026-11-09 10:21 ` sashiko-bot
2026-11-09 9:44 ` [PATCH 3/4] HID: lg-g15: report Super-key-lock state on G15 v2's keyboard interface Torstein Eide
2026-11-09 10:36 ` sashiko-bot
2026-11-09 9:44 ` [PATCH 4/4] HID: lg-g15: cancel LED work only after stopping the hardware Torstein Eide
2026-11-09 14:01 ` Hans de Goede
2026-11-09 13:49 ` [PATCH 0/4] HID: lg-g15: G15 v2 LCD and game-mode support Hans de Goede
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=20261109094426.1432465-2-torsteine+linux@gmail.com \
--to=torsteine+linux@gmail.com \
--cc=bentiss@kernel.org \
--cc=hansg@kernel.org \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maoyixie.tju@gmail.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.