2019-08-28 15:41:25 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* HID driver for gaming keys on Logitech gaming keyboards ( such as the G15 )
*
* Copyright ( c ) 2019 Hans de Goede < hdegoede @ redhat . com >
*/
# include <linux/device.h>
# include <linux/hid.h>
# include <linux/module.h>
# include <linux/random.h>
# include <linux/sched.h>
# include <linux/usb.h>
# include <linux/wait.h>
# include "hid-ids.h"
# define LG_G15_TRANSFER_BUF_SIZE 20
2019-08-28 15:41:26 +03:00
# define LG_G15_FEATURE_REPORT 0x02
2019-08-28 15:41:29 +03:00
# define LG_G510_FEATURE_M_KEYS_LEDS 0x04
# define LG_G510_FEATURE_BACKLIGHT_RGB 0x05
# define LG_G510_FEATURE_POWER_ON_RGB 0x06
2019-08-28 15:41:25 +03:00
enum lg_g15_model {
LG_G15 ,
LG_G15_V2 ,
2019-08-28 15:41:28 +03:00
LG_G510 ,
LG_G510_USB_AUDIO ,
2021-04-16 16:13:22 +03:00
LG_Z10 ,
2019-08-28 15:41:25 +03:00
} ;
2019-08-28 15:41:26 +03:00
enum lg_g15_led_type {
LG_G15_KBD_BRIGHTNESS ,
LG_G15_LCD_BRIGHTNESS ,
LG_G15_BRIGHTNESS_MAX ,
2019-08-28 15:41:27 +03:00
LG_G15_MACRO_PRESET1 = 2 ,
LG_G15_MACRO_PRESET2 ,
LG_G15_MACRO_PRESET3 ,
LG_G15_MACRO_RECORD ,
LG_G15_LED_MAX
2019-08-28 15:41:26 +03:00
} ;
struct lg_g15_led {
struct led_classdev cdev ;
enum led_brightness brightness ;
enum lg_g15_led_type led ;
2019-08-28 15:41:29 +03:00
u8 red , green , blue ;
2019-08-28 15:41:26 +03:00
} ;
2019-08-28 15:41:25 +03:00
struct lg_g15_data {
/* Must be first for proper dma alignment */
u8 transfer_buf [ LG_G15_TRANSFER_BUF_SIZE ] ;
2019-08-28 15:41:26 +03:00
/* Protects the transfer_buf and led brightness */
struct mutex mutex ;
struct work_struct work ;
2019-08-28 15:41:25 +03:00
struct input_dev * input ;
struct hid_device * hdev ;
enum lg_g15_model model ;
2019-08-28 15:41:27 +03:00
struct lg_g15_led leds [ LG_G15_LED_MAX ] ;
2019-08-28 15:41:28 +03:00
bool game_mode_enabled ;
2019-08-28 15:41:25 +03:00
} ;
2019-08-28 15:41:29 +03:00
/******** G15 and G15 v2 LED functions ********/
2019-08-28 15:41:26 +03:00
static int lg_g15_update_led_brightness ( struct lg_g15_data * g15 )
{
int ret ;
ret = hid_hw_raw_request ( g15 - > hdev , LG_G15_FEATURE_REPORT ,
g15 - > transfer_buf , 4 ,
HID_FEATURE_REPORT , HID_REQ_GET_REPORT ) ;
if ( ret ! = 4 ) {
hid_err ( g15 - > hdev , " Error getting LED brightness: %d \n " , ret ) ;
return ( ret < 0 ) ? ret : - EIO ;
}
g15 - > leds [ LG_G15_KBD_BRIGHTNESS ] . brightness = g15 - > transfer_buf [ 1 ] ;
g15 - > leds [ LG_G15_LCD_BRIGHTNESS ] . brightness = g15 - > transfer_buf [ 2 ] ;
2019-08-28 15:41:27 +03:00
g15 - > leds [ LG_G15_MACRO_PRESET1 ] . brightness =
! ( g15 - > transfer_buf [ 3 ] & 0x01 ) ;
g15 - > leds [ LG_G15_MACRO_PRESET2 ] . brightness =
! ( g15 - > transfer_buf [ 3 ] & 0x02 ) ;
g15 - > leds [ LG_G15_MACRO_PRESET3 ] . brightness =
! ( g15 - > transfer_buf [ 3 ] & 0x04 ) ;
g15 - > leds [ LG_G15_MACRO_RECORD ] . brightness =
! ( g15 - > transfer_buf [ 3 ] & 0x08 ) ;
2019-08-28 15:41:26 +03:00
return 0 ;
}
static enum led_brightness lg_g15_led_get ( struct led_classdev * led_cdev )
{
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 ) ;
enum led_brightness brightness ;
mutex_lock ( & g15 - > mutex ) ;
lg_g15_update_led_brightness ( g15 ) ;
brightness = g15 - > leds [ g15_led - > led ] . brightness ;
mutex_unlock ( & g15 - > mutex ) ;
return brightness ;
}
static int lg_g15_led_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 ) ;
2019-08-28 15:41:27 +03:00
u8 val , mask = 0 ;
int i , ret ;
2019-08-28 15:41:26 +03:00
/* Ignore LED off on unregister / keyboard unplug */
if ( led_cdev - > flags & LED_UNREGISTERING )
return 0 ;
mutex_lock ( & g15 - > mutex ) ;
g15 - > transfer_buf [ 0 ] = LG_G15_FEATURE_REPORT ;
g15 - > transfer_buf [ 3 ] = 0 ;
2019-08-28 15:41:27 +03:00
if ( g15_led - > led < LG_G15_BRIGHTNESS_MAX ) {
g15 - > transfer_buf [ 1 ] = g15_led - > led + 1 ;
g15 - > transfer_buf [ 2 ] = brightness < < ( g15_led - > led * 4 ) ;
} else {
for ( i = LG_G15_MACRO_PRESET1 ; i < LG_G15_LED_MAX ; i + + ) {
if ( i = = g15_led - > led )
val = brightness ;
else
val = g15 - > leds [ i ] . brightness ;
if ( val )
mask | = 1 < < ( i - LG_G15_MACRO_PRESET1 ) ;
}
g15 - > transfer_buf [ 1 ] = 0x04 ;
g15 - > transfer_buf [ 2 ] = ~ mask ;
}
2019-08-28 15:41:26 +03:00
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 ) {
/* Success */
g15_led - > brightness = brightness ;
ret = 0 ;
} else {
hid_err ( g15 - > hdev , " Error setting LED brightness: %d \n " , ret ) ;
ret = ( ret < 0 ) ? ret : - EIO ;
}
mutex_unlock ( & g15 - > mutex ) ;
return ret ;
}
static void lg_g15_leds_changed_work ( struct work_struct * work )
{
struct lg_g15_data * g15 = container_of ( work , struct lg_g15_data , work ) ;
enum led_brightness old_brightness [ LG_G15_BRIGHTNESS_MAX ] ;
enum led_brightness brightness [ LG_G15_BRIGHTNESS_MAX ] ;
int i , ret ;
mutex_lock ( & g15 - > mutex ) ;
for ( i = 0 ; i < LG_G15_BRIGHTNESS_MAX ; i + + )
old_brightness [ i ] = g15 - > leds [ i ] . brightness ;
ret = lg_g15_update_led_brightness ( g15 ) ;
for ( i = 0 ; i < LG_G15_BRIGHTNESS_MAX ; i + + )
brightness [ i ] = g15 - > leds [ i ] . brightness ;
mutex_unlock ( & g15 - > mutex ) ;
if ( ret )
return ;
for ( i = 0 ; i < LG_G15_BRIGHTNESS_MAX ; i + + ) {
if ( brightness [ i ] = = old_brightness [ i ] )
continue ;
led_classdev_notify_brightness_hw_changed ( & g15 - > leds [ i ] . cdev ,
brightness [ i ] ) ;
}
}
2019-08-28 15:41:29 +03:00
/******** G510 LED functions ********/
static int lg_g510_get_initial_led_brightness ( struct lg_g15_data * g15 , int i )
{
int ret , high ;
ret = hid_hw_raw_request ( g15 - > hdev , LG_G510_FEATURE_BACKLIGHT_RGB + i ,
g15 - > transfer_buf , 4 ,
HID_FEATURE_REPORT , HID_REQ_GET_REPORT ) ;
if ( ret ! = 4 ) {
hid_err ( g15 - > hdev , " Error getting LED brightness: %d \n " , ret ) ;
return ( ret < 0 ) ? ret : - EIO ;
}
high = max3 ( g15 - > transfer_buf [ 1 ] , g15 - > transfer_buf [ 2 ] ,
g15 - > transfer_buf [ 3 ] ) ;
if ( high ) {
g15 - > leds [ i ] . red =
DIV_ROUND_CLOSEST ( g15 - > transfer_buf [ 1 ] * 255 , high ) ;
g15 - > leds [ i ] . green =
DIV_ROUND_CLOSEST ( g15 - > transfer_buf [ 2 ] * 255 , high ) ;
g15 - > leds [ i ] . blue =
DIV_ROUND_CLOSEST ( g15 - > transfer_buf [ 3 ] * 255 , high ) ;
g15 - > leds [ i ] . brightness = high ;
} else {
g15 - > leds [ i ] . red = 255 ;
g15 - > leds [ i ] . green = 255 ;
g15 - > leds [ i ] . blue = 255 ;
g15 - > leds [ i ] . brightness = 0 ;
}
return 0 ;
}
/* Must be called with g15->mutex locked */
static int lg_g510_kbd_led_write ( struct lg_g15_data * g15 ,
struct lg_g15_led * g15_led ,
enum led_brightness brightness )
{
int ret ;
g15 - > transfer_buf [ 0 ] = 5 + g15_led - > led ;
g15 - > transfer_buf [ 1 ] =
DIV_ROUND_CLOSEST ( g15_led - > red * brightness , 255 ) ;
g15 - > transfer_buf [ 2 ] =
DIV_ROUND_CLOSEST ( g15_led - > green * brightness , 255 ) ;
g15 - > transfer_buf [ 3 ] =
DIV_ROUND_CLOSEST ( g15_led - > blue * brightness , 255 ) ;
ret = hid_hw_raw_request ( g15 - > hdev ,
LG_G510_FEATURE_BACKLIGHT_RGB + g15_led - > led ,
g15 - > transfer_buf , 4 ,
HID_FEATURE_REPORT , HID_REQ_SET_REPORT ) ;
if ( ret = = 4 ) {
/* Success */
g15_led - > brightness = brightness ;
ret = 0 ;
} else {
hid_err ( g15 - > hdev , " Error setting LED brightness: %d \n " , ret ) ;
ret = ( ret < 0 ) ? ret : - EIO ;
}
return ret ;
}
static int lg_g510_kbd_led_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 ) ;
int ret ;
/* Ignore LED off on unregister / keyboard unplug */
if ( led_cdev - > flags & LED_UNREGISTERING )
return 0 ;
mutex_lock ( & g15 - > mutex ) ;
ret = lg_g510_kbd_led_write ( g15 , g15_led , brightness ) ;
mutex_unlock ( & g15 - > mutex ) ;
return ret ;
}
static enum led_brightness lg_g510_kbd_led_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 ssize_t color_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
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 ) ;
unsigned long value ;
int ret ;
if ( count < 7 | | ( count = = 8 & & buf [ 7 ] ! = ' \n ' ) | | count > 8 )
return - EINVAL ;
if ( buf [ 0 ] ! = ' # ' )
return - EINVAL ;
ret = kstrtoul ( buf + 1 , 16 , & value ) ;
if ( ret )
return ret ;
mutex_lock ( & g15 - > mutex ) ;
g15_led - > red = ( value & 0xff0000 ) > > 16 ;
g15_led - > green = ( value & 0x00ff00 ) > > 8 ;
g15_led - > blue = ( value & 0x0000ff ) ;
ret = lg_g510_kbd_led_write ( g15 , g15_led , g15_led - > brightness ) ;
mutex_unlock ( & g15 - > mutex ) ;
return ( ret < 0 ) ? ret : count ;
}
static ssize_t color_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
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 ) ;
ssize_t ret ;
mutex_lock ( & g15 - > mutex ) ;
ret = sprintf ( buf , " #%02x%02x%02x \n " ,
g15_led - > red , g15_led - > green , g15_led - > blue ) ;
mutex_unlock ( & g15 - > mutex ) ;
return ret ;
}
static DEVICE_ATTR_RW ( color ) ;
static struct attribute * lg_g510_kbd_led_attrs [ ] = {
& dev_attr_color . attr ,
NULL ,
} ;
static const struct attribute_group lg_g510_kbd_led_group = {
. attrs = lg_g510_kbd_led_attrs ,
} ;
static const struct attribute_group * lg_g510_kbd_led_groups [ ] = {
& lg_g510_kbd_led_group ,
NULL ,
} ;
static void lg_g510_leds_sync_work ( struct work_struct * work )
{
struct lg_g15_data * g15 = container_of ( work , struct lg_g15_data , work ) ;
mutex_lock ( & g15 - > mutex ) ;
lg_g510_kbd_led_write ( g15 , & g15 - > leds [ LG_G15_KBD_BRIGHTNESS ] ,
g15 - > leds [ LG_G15_KBD_BRIGHTNESS ] . brightness ) ;
mutex_unlock ( & g15 - > mutex ) ;
}
2019-08-28 15:41:30 +03:00
static int lg_g510_update_mkey_led_brightness ( struct lg_g15_data * g15 )
{
int ret ;
ret = hid_hw_raw_request ( g15 - > hdev , LG_G510_FEATURE_M_KEYS_LEDS ,
g15 - > transfer_buf , 2 ,
HID_FEATURE_REPORT , HID_REQ_GET_REPORT ) ;
if ( ret ! = 2 ) {
hid_err ( g15 - > hdev , " Error getting LED brightness: %d \n " , ret ) ;
ret = ( ret < 0 ) ? ret : - EIO ;
}
g15 - > leds [ LG_G15_MACRO_PRESET1 ] . brightness =
! ! ( g15 - > transfer_buf [ 1 ] & 0x80 ) ;
g15 - > leds [ LG_G15_MACRO_PRESET2 ] . brightness =
! ! ( g15 - > transfer_buf [ 1 ] & 0x40 ) ;
g15 - > leds [ LG_G15_MACRO_PRESET3 ] . brightness =
! ! ( g15 - > transfer_buf [ 1 ] & 0x20 ) ;
g15 - > leds [ LG_G15_MACRO_RECORD ] . brightness =
! ! ( g15 - > transfer_buf [ 1 ] & 0x10 ) ;
return 0 ;
}
static enum led_brightness lg_g510_mkey_led_get ( struct led_classdev * led_cdev )
{
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 ) ;
enum led_brightness brightness ;
mutex_lock ( & g15 - > mutex ) ;
lg_g510_update_mkey_led_brightness ( g15 ) ;
brightness = g15 - > leds [ g15_led - > led ] . brightness ;
mutex_unlock ( & g15 - > mutex ) ;
return brightness ;
}
static int lg_g510_mkey_led_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 ) ;
u8 val , mask = 0 ;
int i , ret ;
/* Ignore LED off on unregister / keyboard unplug */
if ( led_cdev - > flags & LED_UNREGISTERING )
return 0 ;
mutex_lock ( & g15 - > mutex ) ;
for ( i = LG_G15_MACRO_PRESET1 ; i < LG_G15_LED_MAX ; i + + ) {
if ( i = = g15_led - > led )
val = brightness ;
else
val = g15 - > leds [ i ] . brightness ;
if ( val )
mask | = 0x80 > > ( i - LG_G15_MACRO_PRESET1 ) ;
}
g15 - > transfer_buf [ 0 ] = LG_G510_FEATURE_M_KEYS_LEDS ;
g15 - > transfer_buf [ 1 ] = mask ;
ret = hid_hw_raw_request ( g15 - > hdev , LG_G510_FEATURE_M_KEYS_LEDS ,
g15 - > transfer_buf , 2 ,
HID_FEATURE_REPORT , HID_REQ_SET_REPORT ) ;
if ( ret = = 2 ) {
/* Success */
g15_led - > brightness = brightness ;
ret = 0 ;
} else {
hid_err ( g15 - > hdev , " Error setting LED brightness: %d \n " , ret ) ;
ret = ( ret < 0 ) ? ret : - EIO ;
}
mutex_unlock ( & g15 - > mutex ) ;
return ret ;
}
2019-08-28 15:41:29 +03:00
/******** Generic LED functions ********/
static int lg_g15_get_initial_led_brightness ( struct lg_g15_data * g15 )
{
int ret ;
switch ( g15 - > model ) {
case LG_G15 :
case LG_G15_V2 :
return lg_g15_update_led_brightness ( g15 ) ;
case LG_G510 :
case LG_G510_USB_AUDIO :
ret = lg_g510_get_initial_led_brightness ( g15 , 0 ) ;
if ( ret )
return ret ;
ret = lg_g510_get_initial_led_brightness ( g15 , 1 ) ;
if ( ret )
return ret ;
2019-08-28 15:41:30 +03:00
return lg_g510_update_mkey_led_brightness ( g15 ) ;
2021-04-16 16:13:22 +03:00
case LG_Z10 :
/*
* Getting the LCD backlight brightness is not supported .
* Reading Feature ( 2 ) fails with - EPIPE and this crashes
* the LCD and touch keys part of the speakers .
*/
return 0 ;
2019-08-28 15:41:29 +03:00
}
return - EINVAL ; /* Never reached */
}
/******** Input functions ********/
2019-08-28 15:41:25 +03:00
/* On the G15 Mark I Logitech has been quite creative with which bit is what */
2021-04-16 16:13:19 +03:00
static void lg_g15_handle_lcd_menu_keys ( struct lg_g15_data * g15 , u8 * data )
{
int i , val ;
/* Most left (round/display) button below the LCD */
input_report_key ( g15 - > input , KEY_KBD_LCD_MENU1 , data [ 8 ] & 0x80 ) ;
/* 4 other buttons below the LCD */
for ( i = 0 ; i < 4 ; i + + ) {
val = data [ i + 2 ] & 0x80 ;
input_report_key ( g15 - > input , KEY_KBD_LCD_MENU2 + i , val ) ;
}
}
2021-04-16 16:13:18 +03:00
static int lg_g15_event ( struct lg_g15_data * g15 , u8 * data )
2019-08-28 15:41:25 +03:00
{
int i , val ;
/* G1 - G6 */
for ( i = 0 ; i < 6 ; i + + ) {
val = data [ i + 1 ] & ( 1 < < i ) ;
input_report_key ( g15 - > input , KEY_MACRO1 + i , val ) ;
}
/* G7 - G12 */
for ( i = 0 ; i < 6 ; i + + ) {
val = data [ i + 2 ] & ( 1 < < i ) ;
input_report_key ( g15 - > input , KEY_MACRO7 + i , val ) ;
}
/* G13 - G17 */
for ( i = 0 ; i < 5 ; i + + ) {
val = data [ i + 1 ] & ( 4 < < i ) ;
input_report_key ( g15 - > input , KEY_MACRO13 + i , val ) ;
}
/* G18 */
input_report_key ( g15 - > input , KEY_MACRO18 , data [ 8 ] & 0x40 ) ;
/* M1 - M3 */
for ( i = 0 ; i < 3 ; i + + ) {
val = data [ i + 6 ] & ( 1 < < i ) ;
input_report_key ( g15 - > input , KEY_MACRO_PRESET1 + i , val ) ;
}
/* MR */
input_report_key ( g15 - > input , KEY_MACRO_RECORD_START , data [ 7 ] & 0x40 ) ;
2021-04-16 16:13:19 +03:00
lg_g15_handle_lcd_menu_keys ( g15 , data ) ;
2019-08-28 15:41:25 +03:00
2019-08-28 15:41:26 +03:00
/* Backlight cycle button pressed? */
if ( data [ 1 ] & 0x80 )
schedule_work ( & g15 - > work ) ;
2019-08-28 15:41:25 +03:00
input_sync ( g15 - > input ) ;
return 0 ;
}
2021-04-16 16:13:18 +03:00
static int lg_g15_v2_event ( struct lg_g15_data * g15 , u8 * data )
2019-08-28 15:41:25 +03:00
{
int i , val ;
/* G1 - G6 */
for ( i = 0 ; i < 6 ; i + + ) {
val = data [ 1 ] & ( 1 < < i ) ;
input_report_key ( g15 - > input , KEY_MACRO1 + i , val ) ;
}
/* M1 - M3 + MR */
input_report_key ( g15 - > input , KEY_MACRO_PRESET1 , data [ 1 ] & 0x40 ) ;
input_report_key ( g15 - > input , KEY_MACRO_PRESET2 , data [ 1 ] & 0x80 ) ;
input_report_key ( g15 - > input , KEY_MACRO_PRESET3 , data [ 2 ] & 0x20 ) ;
input_report_key ( g15 - > input , KEY_MACRO_RECORD_START , data [ 2 ] & 0x40 ) ;
/* Round button to the left of the LCD */
input_report_key ( g15 - > input , KEY_KBD_LCD_MENU1 , data [ 2 ] & 0x80 ) ;
/* 4 buttons below the LCD */
for ( i = 0 ; i < 4 ; i + + ) {
val = data [ 2 ] & ( 2 < < i ) ;
input_report_key ( g15 - > input , KEY_KBD_LCD_MENU2 + i , val ) ;
}
2019-08-28 15:41:26 +03:00
/* Backlight cycle button pressed? */
if ( data [ 2 ] & 0x01 )
schedule_work ( & g15 - > work ) ;
2019-08-28 15:41:25 +03:00
input_sync ( g15 - > input ) ;
return 0 ;
}
2021-04-16 16:13:18 +03:00
static int lg_g510_event ( struct lg_g15_data * g15 , u8 * data )
2019-08-28 15:41:28 +03:00
{
bool game_mode_enabled ;
int i , val ;
/* G1 - G18 */
for ( i = 0 ; i < 18 ; i + + ) {
val = data [ i / 8 + 1 ] & ( 1 < < ( i % 8 ) ) ;
input_report_key ( g15 - > input , KEY_MACRO1 + i , val ) ;
}
/* Game mode on/off slider */
game_mode_enabled = data [ 3 ] & 0x04 ;
if ( game_mode_enabled ! = g15 - > game_mode_enabled ) {
if ( game_mode_enabled )
hid_info ( g15 - > hdev , " Game Mode enabled, Windows (super) key is disabled \n " ) ;
else
hid_info ( g15 - > hdev , " Game Mode disabled \n " ) ;
g15 - > game_mode_enabled = game_mode_enabled ;
}
/* M1 - M3 */
for ( i = 0 ; i < 3 ; i + + ) {
val = data [ 3 ] & ( 0x10 < < i ) ;
input_report_key ( g15 - > input , KEY_MACRO_PRESET1 + i , val ) ;
}
/* MR */
input_report_key ( g15 - > input , KEY_MACRO_RECORD_START , data [ 3 ] & 0x80 ) ;
/* LCD menu keys */
for ( i = 0 ; i < 5 ; i + + ) {
val = data [ 4 ] & ( 1 < < i ) ;
input_report_key ( g15 - > input , KEY_KBD_LCD_MENU1 + i , val ) ;
}
/* Headphone Mute */
input_report_key ( g15 - > input , KEY_MUTE , data [ 4 ] & 0x20 ) ;
/* Microphone Mute */
input_report_key ( g15 - > input , KEY_F20 , data [ 4 ] & 0x40 ) ;
input_sync ( g15 - > input ) ;
return 0 ;
}
2021-04-16 16:13:18 +03:00
static int lg_g510_leds_event ( struct lg_g15_data * g15 , u8 * data )
2019-08-28 15:41:29 +03:00
{
bool backlight_disabled ;
/*
* The G510 ignores backlight updates when the backlight is turned off
* through the light toggle button on the keyboard , to work around this
* we queue a workitem to sync values when the backlight is turned on .
*/
backlight_disabled = data [ 1 ] & 0x04 ;
if ( ! backlight_disabled )
schedule_work ( & g15 - > work ) ;
return 0 ;
}
2019-08-28 15:41:25 +03:00
static int lg_g15_raw_event ( struct hid_device * hdev , struct hid_report * report ,
u8 * data , int size )
{
struct lg_g15_data * g15 = hid_get_drvdata ( hdev ) ;
2019-08-28 15:41:28 +03:00
if ( ! g15 )
return 0 ;
2019-08-28 15:41:25 +03:00
2019-08-28 15:41:28 +03:00
switch ( g15 - > model ) {
case LG_G15 :
if ( data [ 0 ] = = 0x02 & & size = = 9 )
2021-04-16 16:13:18 +03:00
return lg_g15_event ( g15 , data ) ;
2019-08-28 15:41:28 +03:00
break ;
case LG_G15_V2 :
if ( data [ 0 ] = = 0x02 & & size = = 5 )
2021-04-16 16:13:18 +03:00
return lg_g15_v2_event ( g15 , data ) ;
2019-08-28 15:41:28 +03:00
break ;
2021-04-16 16:13:22 +03:00
case LG_Z10 :
if ( data [ 0 ] = = 0x02 & & size = = 9 ) {
lg_g15_handle_lcd_menu_keys ( g15 , data ) ;
input_sync ( g15 - > input ) ;
}
break ;
2019-08-28 15:41:28 +03:00
case LG_G510 :
case LG_G510_USB_AUDIO :
if ( data [ 0 ] = = 0x03 & & size = = 5 )
2021-04-16 16:13:18 +03:00
return lg_g510_event ( g15 , data ) ;
2019-08-28 15:41:29 +03:00
if ( data [ 0 ] = = 0x04 & & size = = 2 )
2021-04-16 16:13:18 +03:00
return lg_g510_leds_event ( g15 , data ) ;
2019-08-28 15:41:28 +03:00
break ;
}
2019-08-28 15:41:25 +03:00
return 0 ;
}
static int lg_g15_input_open ( struct input_dev * dev )
{
struct hid_device * hdev = input_get_drvdata ( dev ) ;
return hid_hw_open ( hdev ) ;
}
static void lg_g15_input_close ( struct input_dev * dev )
{
struct hid_device * hdev = input_get_drvdata ( dev ) ;
hid_hw_close ( hdev ) ;
}
2021-04-16 16:13:21 +03:00
static int lg_g15_register_led ( struct lg_g15_data * g15 , int i , const char * name )
2019-08-28 15:41:26 +03:00
{
g15 - > leds [ i ] . led = i ;
2021-04-16 16:13:21 +03:00
g15 - > leds [ i ] . cdev . name = name ;
2019-08-28 15:41:29 +03:00
switch ( g15 - > model ) {
case LG_G15 :
case LG_G15_V2 :
g15 - > leds [ i ] . cdev . brightness_get = lg_g15_led_get ;
2021-04-16 16:13:22 +03:00
fallthrough ;
case LG_Z10 :
g15 - > leds [ i ] . cdev . brightness_set_blocking = lg_g15_led_set ;
2019-08-28 15:41:29 +03:00
if ( i < LG_G15_BRIGHTNESS_MAX ) {
g15 - > leds [ i ] . cdev . flags = LED_BRIGHT_HW_CHANGED ;
g15 - > leds [ i ] . cdev . max_brightness = 2 ;
} else {
g15 - > leds [ i ] . cdev . max_brightness = 1 ;
}
break ;
case LG_G510 :
case LG_G510_USB_AUDIO :
switch ( i ) {
case LG_G15_LCD_BRIGHTNESS :
/*
* The G510 does not have a separate LCD brightness ,
* but it does have a separate power - on ( reset ) value .
*/
g15 - > leds [ i ] . cdev . name = " g15::power_on_backlight_val " ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2019-08-28 15:41:29 +03:00
case LG_G15_KBD_BRIGHTNESS :
g15 - > leds [ i ] . cdev . brightness_set_blocking =
lg_g510_kbd_led_set ;
g15 - > leds [ i ] . cdev . brightness_get =
lg_g510_kbd_led_get ;
g15 - > leds [ i ] . cdev . max_brightness = 255 ;
g15 - > leds [ i ] . cdev . groups = lg_g510_kbd_led_groups ;
break ;
default :
2019-08-28 15:41:30 +03:00
g15 - > leds [ i ] . cdev . brightness_set_blocking =
lg_g510_mkey_led_set ;
g15 - > leds [ i ] . cdev . brightness_get =
lg_g510_mkey_led_get ;
g15 - > leds [ i ] . cdev . max_brightness = 1 ;
2019-08-28 15:41:29 +03:00
}
break ;
2019-08-28 15:41:27 +03:00
}
2019-08-28 15:41:26 +03:00
return devm_led_classdev_register ( & g15 - > hdev - > dev , & g15 - > leds [ i ] . cdev ) ;
}
2021-04-16 16:13:20 +03:00
/* Common input device init code shared between keyboards and Z-10 speaker handling */
static void lg_g15_init_input_dev ( struct hid_device * hdev , struct input_dev * input ,
const char * name )
{
int i ;
input - > name = name ;
input - > phys = hdev - > phys ;
input - > uniq = hdev - > uniq ;
input - > id . bustype = hdev - > bus ;
input - > id . vendor = hdev - > vendor ;
input - > id . product = hdev - > product ;
input - > id . version = hdev - > version ;
input - > dev . parent = & hdev - > dev ;
input - > open = lg_g15_input_open ;
input - > close = lg_g15_input_close ;
/* Keys below the LCD, intended for controlling a menu on the LCD */
for ( i = 0 ; i < 5 ; i + + )
input_set_capability ( input , EV_KEY , KEY_KBD_LCD_MENU1 + i ) ;
}
2019-08-28 15:41:25 +03:00
static int lg_g15_probe ( struct hid_device * hdev , const struct hid_device_id * id )
{
2021-04-16 16:13:21 +03:00
static const char * const led_names [ ] = {
" g15::kbd_backlight " ,
" g15::lcd_backlight " ,
" g15::macro_preset1 " ,
" g15::macro_preset2 " ,
" g15::macro_preset3 " ,
" g15::macro_record " ,
} ;
2019-08-28 15:41:25 +03:00
u8 gkeys_settings_output_report = 0 ;
2019-08-28 15:41:28 +03:00
u8 gkeys_settings_feature_report = 0 ;
struct hid_report_enum * rep_enum ;
2019-08-28 15:41:25 +03:00
unsigned int connect_mask = 0 ;
2019-08-28 15:41:28 +03:00
bool has_ff000000 = false ;
2019-08-28 15:41:25 +03:00
struct lg_g15_data * g15 ;
struct input_dev * input ;
2019-08-28 15:41:28 +03:00
struct hid_report * rep ;
2019-08-28 15:41:25 +03:00
int ret , i , gkeys = 0 ;
2019-08-28 15:41:28 +03:00
hdev - > quirks | = HID_QUIRK_INPUT_PER_APP ;
2019-08-28 15:41:25 +03:00
ret = hid_parse ( hdev ) ;
if ( ret )
return ret ;
2019-08-28 15:41:28 +03:00
/*
* Some models have multiple interfaces , we want the interface with
* with the f000 .0000 application input report .
*/
rep_enum = & hdev - > report_enum [ HID_INPUT_REPORT ] ;
list_for_each_entry ( rep , & rep_enum - > report_list , list ) {
if ( rep - > application = = 0xff000000 )
has_ff000000 = true ;
}
if ( ! has_ff000000 )
return hid_hw_start ( hdev , HID_CONNECT_DEFAULT ) ;
2019-08-28 15:41:25 +03:00
g15 = devm_kzalloc ( & hdev - > dev , sizeof ( * g15 ) , GFP_KERNEL ) ;
if ( ! g15 )
return - ENOMEM ;
2019-08-28 15:41:26 +03:00
mutex_init ( & g15 - > mutex ) ;
2019-08-28 15:41:25 +03:00
input = devm_input_allocate_device ( & hdev - > dev ) ;
if ( ! input )
return - ENOMEM ;
g15 - > hdev = hdev ;
g15 - > model = id - > driver_data ;
2021-04-16 16:13:20 +03:00
g15 - > input = input ;
input_set_drvdata ( input , hdev ) ;
2019-08-28 15:41:25 +03:00
hid_set_drvdata ( hdev , ( void * ) g15 ) ;
switch ( g15 - > model ) {
case LG_G15 :
2019-08-28 15:41:29 +03:00
INIT_WORK ( & g15 - > work , lg_g15_leds_changed_work ) ;
2019-08-28 15:41:25 +03:00
/*
* The G15 and G15 v2 use a separate usb - device ( on a builtin
* hub ) which emulates a keyboard for the F1 - F12 emulation
* on the G - keys , which we disable , rendering the emulated kbd
* non - functional , so we do not let hid - input connect .
*/
connect_mask = HID_CONNECT_HIDRAW ;
gkeys_settings_output_report = 0x02 ;
gkeys = 18 ;
break ;
case LG_G15_V2 :
2019-08-28 15:41:29 +03:00
INIT_WORK ( & g15 - > work , lg_g15_leds_changed_work ) ;
2019-08-28 15:41:25 +03:00
connect_mask = HID_CONNECT_HIDRAW ;
gkeys_settings_output_report = 0x02 ;
gkeys = 6 ;
break ;
2019-08-28 15:41:28 +03:00
case LG_G510 :
case LG_G510_USB_AUDIO :
2019-08-28 15:41:29 +03:00
INIT_WORK ( & g15 - > work , lg_g510_leds_sync_work ) ;
2019-08-28 15:41:28 +03:00
connect_mask = HID_CONNECT_HIDINPUT | HID_CONNECT_HIDRAW ;
gkeys_settings_feature_report = 0x01 ;
gkeys = 18 ;
break ;
2021-04-16 16:13:22 +03:00
case LG_Z10 :
connect_mask = HID_CONNECT_HIDRAW ;
break ;
2019-08-28 15:41:25 +03:00
}
ret = hid_hw_start ( hdev , connect_mask ) ;
if ( ret )
return ret ;
/* Tell the keyboard to stop sending F1-F12 + 1-6 for G1 - G18 */
if ( gkeys_settings_output_report ) {
g15 - > transfer_buf [ 0 ] = gkeys_settings_output_report ;
memset ( g15 - > transfer_buf + 1 , 0 , gkeys ) ;
/*
* The kbd ignores our output report if we do not queue
* an URB on the USB input endpoint first . . .
*/
ret = hid_hw_open ( hdev ) ;
if ( ret )
goto error_hw_stop ;
ret = hid_hw_output_report ( hdev , g15 - > transfer_buf , gkeys + 1 ) ;
hid_hw_close ( hdev ) ;
}
2019-08-28 15:41:28 +03:00
if ( gkeys_settings_feature_report ) {
g15 - > transfer_buf [ 0 ] = gkeys_settings_feature_report ;
memset ( g15 - > transfer_buf + 1 , 0 , gkeys ) ;
ret = hid_hw_raw_request ( g15 - > hdev ,
gkeys_settings_feature_report ,
g15 - > transfer_buf , gkeys + 1 ,
HID_FEATURE_REPORT , HID_REQ_SET_REPORT ) ;
}
2019-08-28 15:41:25 +03:00
if ( ret < 0 ) {
2020-03-15 20:34:49 +03:00
hid_err ( hdev , " Error %d disabling keyboard emulation for the G-keys, falling back to generic hid-input driver \n " ,
ret ) ;
hid_set_drvdata ( hdev , NULL ) ;
return 0 ;
2019-08-28 15:41:25 +03:00
}
2019-08-28 15:41:26 +03:00
/* Get initial brightness levels */
2019-08-28 15:41:29 +03:00
ret = lg_g15_get_initial_led_brightness ( g15 ) ;
2019-08-28 15:41:26 +03:00
if ( ret )
goto error_hw_stop ;
2021-04-16 16:13:22 +03:00
if ( g15 - > model = = LG_Z10 ) {
lg_g15_init_input_dev ( hdev , g15 - > input , " Logitech Z-10 LCD Menu Keys " ) ;
ret = input_register_device ( g15 - > input ) ;
if ( ret )
goto error_hw_stop ;
ret = lg_g15_register_led ( g15 , 1 , " z-10::lcd_backlight " ) ;
if ( ret )
goto error_hw_stop ;
return 0 ; /* All done */
}
2019-08-28 15:41:26 +03:00
/* Setup and register input device */
2021-04-16 16:13:20 +03:00
lg_g15_init_input_dev ( hdev , input , " Logitech Gaming Keyboard Gaming Keys " ) ;
2019-08-28 15:41:25 +03:00
/* G-keys */
for ( i = 0 ; i < gkeys ; i + + )
input_set_capability ( input , EV_KEY , KEY_MACRO1 + i ) ;
/* M1 - M3 and MR keys */
for ( i = 0 ; i < 3 ; i + + )
input_set_capability ( input , EV_KEY , KEY_MACRO_PRESET1 + i ) ;
input_set_capability ( input , EV_KEY , KEY_MACRO_RECORD_START ) ;
2019-08-28 15:41:28 +03:00
/*
* On the G510 only report headphone and mic mute keys when * not * using
* the builtin USB audio device . When the builtin audio is used these
* keys directly toggle mute ( and the LEDs ) on / off .
*/
if ( g15 - > model = = LG_G510 ) {
input_set_capability ( input , EV_KEY , KEY_MUTE ) ;
/* Userspace expects F20 for micmute */
input_set_capability ( input , EV_KEY , KEY_F20 ) ;
}
2019-08-28 15:41:25 +03:00
ret = input_register_device ( input ) ;
if ( ret )
goto error_hw_stop ;
2019-08-28 15:41:26 +03:00
/* Register LED devices */
2019-08-28 15:41:27 +03:00
for ( i = 0 ; i < LG_G15_LED_MAX ; i + + ) {
2021-04-16 16:13:21 +03:00
ret = lg_g15_register_led ( g15 , i , led_names [ i ] ) ;
2019-08-28 15:41:26 +03:00
if ( ret )
goto error_hw_stop ;
}
2019-08-28 15:41:25 +03:00
return 0 ;
error_hw_stop :
hid_hw_stop ( hdev ) ;
return ret ;
}
static const struct hid_device_id lg_g15_devices [ ] = {
2020-04-13 19:46:28 +03:00
/* The G11 is a G15 without the LCD, treat it as a G15 */
{ HID_USB_DEVICE ( USB_VENDOR_ID_LOGITECH ,
USB_DEVICE_ID_LOGITECH_G11 ) ,
. driver_data = LG_G15 } ,
2019-08-28 15:41:25 +03:00
{ HID_USB_DEVICE ( USB_VENDOR_ID_LOGITECH ,
USB_DEVICE_ID_LOGITECH_G15_LCD ) ,
. driver_data = LG_G15 } ,
{ HID_USB_DEVICE ( USB_VENDOR_ID_LOGITECH ,
USB_DEVICE_ID_LOGITECH_G15_V2_LCD ) ,
. driver_data = LG_G15_V2 } ,
2019-08-28 15:41:28 +03:00
/* G510 without a headset plugged in */
{ HID_USB_DEVICE ( USB_VENDOR_ID_LOGITECH ,
USB_DEVICE_ID_LOGITECH_G510 ) ,
. driver_data = LG_G510 } ,
/* G510 with headset plugged in / with extra USB audio interface */
{ HID_USB_DEVICE ( USB_VENDOR_ID_LOGITECH ,
USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO ) ,
. driver_data = LG_G510_USB_AUDIO } ,
2021-04-16 16:13:22 +03:00
/* Z-10 speakers */
{ HID_USB_DEVICE ( USB_VENDOR_ID_LOGITECH ,
USB_DEVICE_ID_LOGITECH_Z_10_SPK ) ,
. driver_data = LG_Z10 } ,
2019-08-28 15:41:25 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( hid , lg_g15_devices ) ;
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 ,
} ;
module_hid_driver ( lg_g15_driver ) ;
2021-04-16 16:13:23 +03:00
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
2019-08-28 15:41:25 +03:00
MODULE_LICENSE ( " GPL " ) ;