19
19
20
20
#define LG_G15_FEATURE_REPORT 0x02
21
21
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
+
22
26
enum lg_g15_model {
23
27
LG_G15 ,
24
28
LG_G15_V2 ,
@@ -41,6 +45,7 @@ struct lg_g15_led {
41
45
struct led_classdev cdev ;
42
46
enum led_brightness brightness ;
43
47
enum lg_g15_led_type led ;
48
+ u8 red , green , blue ;
44
49
};
45
50
46
51
struct lg_g15_data {
@@ -56,13 +61,12 @@ struct lg_g15_data {
56
61
bool game_mode_enabled ;
57
62
};
58
63
64
+ /******** G15 and G15 v2 LED functions ********/
65
+
59
66
static int lg_g15_update_led_brightness (struct lg_g15_data * g15 )
60
67
{
61
68
int ret ;
62
69
63
- if (g15 -> model == LG_G510 || g15 -> model == LG_G510_USB_AUDIO )
64
- return 0 ;
65
-
66
70
ret = hid_hw_raw_request (g15 -> hdev , LG_G15_FEATURE_REPORT ,
67
71
g15 -> transfer_buf , 4 ,
68
72
HID_FEATURE_REPORT , HID_REQ_GET_REPORT );
@@ -183,6 +187,198 @@ static void lg_g15_leds_changed_work(struct work_struct *work)
183
187
}
184
188
}
185
189
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
+
186
382
/* On the G15 Mark I Logitech has been quite creative with which bit is what */
187
383
static int lg_g15_event (struct lg_g15_data * g15 , u8 * data , int size )
188
384
{
@@ -306,6 +502,22 @@ static int lg_g510_event(struct lg_g15_data *g15, u8 *data, int size)
306
502
return 0 ;
307
503
}
308
504
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
+
309
521
static int lg_g15_raw_event (struct hid_device * hdev , struct hid_report * report ,
310
522
u8 * data , int size )
311
523
{
@@ -327,6 +539,8 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
327
539
case LG_G510_USB_AUDIO :
328
540
if (data [0 ] == 0x03 && size == 5 )
329
541
return lg_g510_event (g15 , data , size );
542
+ if (data [0 ] == 0x04 && size == 2 )
543
+ return lg_g510_leds_event (g15 , data , size );
330
544
break ;
331
545
}
332
546
@@ -360,13 +574,42 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i)
360
574
361
575
g15 -> leds [i ].led = i ;
362
576
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 ;
370
613
}
371
614
372
615
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)
414
657
415
658
g15 -> hdev = hdev ;
416
659
g15 -> model = id -> driver_data ;
417
- INIT_WORK (& g15 -> work , lg_g15_leds_changed_work );
418
660
hid_set_drvdata (hdev , (void * )g15 );
419
661
420
662
switch (g15 -> model ) {
421
663
case LG_G15 :
664
+ INIT_WORK (& g15 -> work , lg_g15_leds_changed_work );
422
665
/*
423
666
* The G15 and G15 v2 use a separate usb-device (on a builtin
424
667
* 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)
430
673
gkeys = 18 ;
431
674
break ;
432
675
case LG_G15_V2 :
676
+ INIT_WORK (& g15 -> work , lg_g15_leds_changed_work );
433
677
connect_mask = HID_CONNECT_HIDRAW ;
434
678
gkeys_settings_output_report = 0x02 ;
435
679
gkeys = 6 ;
436
680
break ;
437
681
case LG_G510 :
438
682
case LG_G510_USB_AUDIO :
683
+ INIT_WORK (& g15 -> work , lg_g510_leds_sync_work );
439
684
connect_mask = HID_CONNECT_HIDINPUT | HID_CONNECT_HIDRAW ;
440
685
gkeys_settings_feature_report = 0x01 ;
441
686
gkeys = 18 ;
@@ -476,7 +721,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
476
721
}
477
722
478
723
/* Get initial brightness levels */
479
- ret = lg_g15_update_led_brightness (g15 );
724
+ ret = lg_g15_get_initial_led_brightness (g15 );
480
725
if (ret )
481
726
goto error_hw_stop ;
482
727
@@ -523,9 +768,6 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
523
768
if (ret )
524
769
goto error_hw_stop ;
525
770
526
- if (g15 -> model == LG_G510 || g15 -> model == LG_G510_USB_AUDIO )
527
- return 0 ;
528
-
529
771
/* Register LED devices */
530
772
for (i = 0 ; i < LG_G15_LED_MAX ; i ++ ) {
531
773
ret = lg_g15_register_led (g15 , i );
0 commit comments