Skip to content

Commit 1f8cde2

Browse files
jwrdegoedeJiri Kosina
authored andcommitted
HID: lg-g15: Add support for controlling the G510's RGB backlight
Note that the keyboard has a backlight on/off toggle button. If the backlight is turned off through that button, then any changes we make will be ignored and we cannot turn it back on again from the host. To workaround this we write the last set RGB values when we receive an event indicating that the backlight has been turned on again. Signed-off-by: Hans de Goede <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent ad4203f commit 1f8cde2

File tree

1 file changed

+257
-15
lines changed

1 file changed

+257
-15
lines changed

drivers/hid/hid-lg-g15.c

Lines changed: 257 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919

2020
#define LG_G15_FEATURE_REPORT 0x02
2121

22+
#define LG_G510_FEATURE_M_KEYS_LEDS 0x04
23+
#define LG_G510_FEATURE_BACKLIGHT_RGB 0x05
24+
#define LG_G510_FEATURE_POWER_ON_RGB 0x06
25+
2226
enum lg_g15_model {
2327
LG_G15,
2428
LG_G15_V2,
@@ -41,6 +45,7 @@ struct lg_g15_led {
4145
struct led_classdev cdev;
4246
enum led_brightness brightness;
4347
enum lg_g15_led_type led;
48+
u8 red, green, blue;
4449
};
4550

4651
struct lg_g15_data {
@@ -56,13 +61,12 @@ struct lg_g15_data {
5661
bool game_mode_enabled;
5762
};
5863

64+
/******** G15 and G15 v2 LED functions ********/
65+
5966
static int lg_g15_update_led_brightness(struct lg_g15_data *g15)
6067
{
6168
int ret;
6269

63-
if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO)
64-
return 0;
65-
6670
ret = hid_hw_raw_request(g15->hdev, LG_G15_FEATURE_REPORT,
6771
g15->transfer_buf, 4,
6872
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
@@ -183,6 +187,198 @@ static void lg_g15_leds_changed_work(struct work_struct *work)
183187
}
184188
}
185189

190+
/******** G510 LED functions ********/
191+
192+
static int lg_g510_get_initial_led_brightness(struct lg_g15_data *g15, int i)
193+
{
194+
int ret, high;
195+
196+
ret = hid_hw_raw_request(g15->hdev, LG_G510_FEATURE_BACKLIGHT_RGB + i,
197+
g15->transfer_buf, 4,
198+
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
199+
if (ret != 4) {
200+
hid_err(g15->hdev, "Error getting LED brightness: %d\n", ret);
201+
return (ret < 0) ? ret : -EIO;
202+
}
203+
204+
high = max3(g15->transfer_buf[1], g15->transfer_buf[2],
205+
g15->transfer_buf[3]);
206+
207+
if (high) {
208+
g15->leds[i].red =
209+
DIV_ROUND_CLOSEST(g15->transfer_buf[1] * 255, high);
210+
g15->leds[i].green =
211+
DIV_ROUND_CLOSEST(g15->transfer_buf[2] * 255, high);
212+
g15->leds[i].blue =
213+
DIV_ROUND_CLOSEST(g15->transfer_buf[3] * 255, high);
214+
g15->leds[i].brightness = high;
215+
} else {
216+
g15->leds[i].red = 255;
217+
g15->leds[i].green = 255;
218+
g15->leds[i].blue = 255;
219+
g15->leds[i].brightness = 0;
220+
}
221+
222+
return 0;
223+
}
224+
225+
/* Must be called with g15->mutex locked */
226+
static int lg_g510_kbd_led_write(struct lg_g15_data *g15,
227+
struct lg_g15_led *g15_led,
228+
enum led_brightness brightness)
229+
{
230+
int ret;
231+
232+
g15->transfer_buf[0] = 5 + g15_led->led;
233+
g15->transfer_buf[1] =
234+
DIV_ROUND_CLOSEST(g15_led->red * brightness, 255);
235+
g15->transfer_buf[2] =
236+
DIV_ROUND_CLOSEST(g15_led->green * brightness, 255);
237+
g15->transfer_buf[3] =
238+
DIV_ROUND_CLOSEST(g15_led->blue * brightness, 255);
239+
240+
ret = hid_hw_raw_request(g15->hdev,
241+
LG_G510_FEATURE_BACKLIGHT_RGB + g15_led->led,
242+
g15->transfer_buf, 4,
243+
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
244+
if (ret == 4) {
245+
/* Success */
246+
g15_led->brightness = brightness;
247+
ret = 0;
248+
} else {
249+
hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret);
250+
ret = (ret < 0) ? ret : -EIO;
251+
}
252+
253+
return ret;
254+
}
255+
256+
static int lg_g510_kbd_led_set(struct led_classdev *led_cdev,
257+
enum led_brightness brightness)
258+
{
259+
struct lg_g15_led *g15_led =
260+
container_of(led_cdev, struct lg_g15_led, cdev);
261+
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
262+
int ret;
263+
264+
/* Ignore LED off on unregister / keyboard unplug */
265+
if (led_cdev->flags & LED_UNREGISTERING)
266+
return 0;
267+
268+
mutex_lock(&g15->mutex);
269+
ret = lg_g510_kbd_led_write(g15, g15_led, brightness);
270+
mutex_unlock(&g15->mutex);
271+
272+
return ret;
273+
}
274+
275+
static enum led_brightness lg_g510_kbd_led_get(struct led_classdev *led_cdev)
276+
{
277+
struct lg_g15_led *g15_led =
278+
container_of(led_cdev, struct lg_g15_led, cdev);
279+
280+
return g15_led->brightness;
281+
}
282+
283+
static ssize_t color_store(struct device *dev, struct device_attribute *attr,
284+
const char *buf, size_t count)
285+
{
286+
struct led_classdev *led_cdev = dev_get_drvdata(dev);
287+
struct lg_g15_led *g15_led =
288+
container_of(led_cdev, struct lg_g15_led, cdev);
289+
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
290+
unsigned long value;
291+
int ret;
292+
293+
if (count < 7 || (count == 8 && buf[7] != '\n') || count > 8)
294+
return -EINVAL;
295+
296+
if (buf[0] != '#')
297+
return -EINVAL;
298+
299+
ret = kstrtoul(buf + 1, 16, &value);
300+
if (ret)
301+
return ret;
302+
303+
mutex_lock(&g15->mutex);
304+
g15_led->red = (value & 0xff0000) >> 16;
305+
g15_led->green = (value & 0x00ff00) >> 8;
306+
g15_led->blue = (value & 0x0000ff);
307+
ret = lg_g510_kbd_led_write(g15, g15_led, g15_led->brightness);
308+
mutex_unlock(&g15->mutex);
309+
310+
return (ret < 0) ? ret : count;
311+
}
312+
313+
static ssize_t color_show(struct device *dev, struct device_attribute *attr,
314+
char *buf)
315+
{
316+
struct led_classdev *led_cdev = dev_get_drvdata(dev);
317+
struct lg_g15_led *g15_led =
318+
container_of(led_cdev, struct lg_g15_led, cdev);
319+
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
320+
ssize_t ret;
321+
322+
mutex_lock(&g15->mutex);
323+
ret = sprintf(buf, "#%02x%02x%02x\n",
324+
g15_led->red, g15_led->green, g15_led->blue);
325+
mutex_unlock(&g15->mutex);
326+
327+
return ret;
328+
}
329+
330+
static DEVICE_ATTR_RW(color);
331+
332+
static struct attribute *lg_g510_kbd_led_attrs[] = {
333+
&dev_attr_color.attr,
334+
NULL,
335+
};
336+
337+
static const struct attribute_group lg_g510_kbd_led_group = {
338+
.attrs = lg_g510_kbd_led_attrs,
339+
};
340+
341+
static const struct attribute_group *lg_g510_kbd_led_groups[] = {
342+
&lg_g510_kbd_led_group,
343+
NULL,
344+
};
345+
346+
static void lg_g510_leds_sync_work(struct work_struct *work)
347+
{
348+
struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work);
349+
350+
mutex_lock(&g15->mutex);
351+
lg_g510_kbd_led_write(g15, &g15->leds[LG_G15_KBD_BRIGHTNESS],
352+
g15->leds[LG_G15_KBD_BRIGHTNESS].brightness);
353+
mutex_unlock(&g15->mutex);
354+
}
355+
356+
/******** Generic LED functions ********/
357+
static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
358+
{
359+
int ret;
360+
361+
switch (g15->model) {
362+
case LG_G15:
363+
case LG_G15_V2:
364+
return lg_g15_update_led_brightness(g15);
365+
case LG_G510:
366+
case LG_G510_USB_AUDIO:
367+
ret = lg_g510_get_initial_led_brightness(g15, 0);
368+
if (ret)
369+
return ret;
370+
371+
ret = lg_g510_get_initial_led_brightness(g15, 1);
372+
if (ret)
373+
return ret;
374+
375+
return 0;
376+
}
377+
return -EINVAL; /* Never reached */
378+
}
379+
380+
/******** Input functions ********/
381+
186382
/* On the G15 Mark I Logitech has been quite creative with which bit is what */
187383
static int lg_g15_event(struct lg_g15_data *g15, u8 *data, int size)
188384
{
@@ -306,6 +502,22 @@ static int lg_g510_event(struct lg_g15_data *g15, u8 *data, int size)
306502
return 0;
307503
}
308504

505+
static int lg_g510_leds_event(struct lg_g15_data *g15, u8 *data, int size)
506+
{
507+
bool backlight_disabled;
508+
509+
/*
510+
* The G510 ignores backlight updates when the backlight is turned off
511+
* through the light toggle button on the keyboard, to work around this
512+
* we queue a workitem to sync values when the backlight is turned on.
513+
*/
514+
backlight_disabled = data[1] & 0x04;
515+
if (!backlight_disabled)
516+
schedule_work(&g15->work);
517+
518+
return 0;
519+
}
520+
309521
static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
310522
u8 *data, int size)
311523
{
@@ -327,6 +539,8 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
327539
case LG_G510_USB_AUDIO:
328540
if (data[0] == 0x03 && size == 5)
329541
return lg_g510_event(g15, data, size);
542+
if (data[0] == 0x04 && size == 2)
543+
return lg_g510_leds_event(g15, data, size);
330544
break;
331545
}
332546

@@ -360,13 +574,42 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i)
360574

361575
g15->leds[i].led = i;
362576
g15->leds[i].cdev.name = led_names[i];
363-
g15->leds[i].cdev.brightness_set_blocking = lg_g15_led_set;
364-
g15->leds[i].cdev.brightness_get = lg_g15_led_get;
365-
if (i < LG_G15_BRIGHTNESS_MAX) {
366-
g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED;
367-
g15->leds[i].cdev.max_brightness = 2;
368-
} else {
369-
g15->leds[i].cdev.max_brightness = 1;
577+
578+
switch (g15->model) {
579+
case LG_G15:
580+
case LG_G15_V2:
581+
g15->leds[i].cdev.brightness_set_blocking = lg_g15_led_set;
582+
g15->leds[i].cdev.brightness_get = lg_g15_led_get;
583+
if (i < LG_G15_BRIGHTNESS_MAX) {
584+
g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED;
585+
g15->leds[i].cdev.max_brightness = 2;
586+
} else {
587+
g15->leds[i].cdev.max_brightness = 1;
588+
}
589+
break;
590+
case LG_G510:
591+
case LG_G510_USB_AUDIO:
592+
switch (i) {
593+
case LG_G15_LCD_BRIGHTNESS:
594+
/*
595+
* The G510 does not have a separate LCD brightness,
596+
* but it does have a separate power-on (reset) value.
597+
*/
598+
g15->leds[i].cdev.name = "g15::power_on_backlight_val";
599+
/* fall through */
600+
case LG_G15_KBD_BRIGHTNESS:
601+
g15->leds[i].cdev.brightness_set_blocking =
602+
lg_g510_kbd_led_set;
603+
g15->leds[i].cdev.brightness_get =
604+
lg_g510_kbd_led_get;
605+
g15->leds[i].cdev.max_brightness = 255;
606+
g15->leds[i].cdev.groups = lg_g510_kbd_led_groups;
607+
break;
608+
default:
609+
/* TODO: Add support for M1 - M3 and MR leds */
610+
return 0;
611+
}
612+
break;
370613
}
371614

372615
return devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
@@ -414,11 +657,11 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
414657

415658
g15->hdev = hdev;
416659
g15->model = id->driver_data;
417-
INIT_WORK(&g15->work, lg_g15_leds_changed_work);
418660
hid_set_drvdata(hdev, (void *)g15);
419661

420662
switch (g15->model) {
421663
case LG_G15:
664+
INIT_WORK(&g15->work, lg_g15_leds_changed_work);
422665
/*
423666
* The G15 and G15 v2 use a separate usb-device (on a builtin
424667
* hub) which emulates a keyboard for the F1 - F12 emulation
@@ -430,12 +673,14 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
430673
gkeys = 18;
431674
break;
432675
case LG_G15_V2:
676+
INIT_WORK(&g15->work, lg_g15_leds_changed_work);
433677
connect_mask = HID_CONNECT_HIDRAW;
434678
gkeys_settings_output_report = 0x02;
435679
gkeys = 6;
436680
break;
437681
case LG_G510:
438682
case LG_G510_USB_AUDIO:
683+
INIT_WORK(&g15->work, lg_g510_leds_sync_work);
439684
connect_mask = HID_CONNECT_HIDINPUT | HID_CONNECT_HIDRAW;
440685
gkeys_settings_feature_report = 0x01;
441686
gkeys = 18;
@@ -476,7 +721,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
476721
}
477722

478723
/* Get initial brightness levels */
479-
ret = lg_g15_update_led_brightness(g15);
724+
ret = lg_g15_get_initial_led_brightness(g15);
480725
if (ret)
481726
goto error_hw_stop;
482727

@@ -523,9 +768,6 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
523768
if (ret)
524769
goto error_hw_stop;
525770

526-
if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO)
527-
return 0;
528-
529771
/* Register LED devices */
530772
for (i = 0; i < LG_G15_LED_MAX; i++) {
531773
ret = lg_g15_register_led(g15, i);

0 commit comments

Comments
 (0)