diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index df37a0fd4efa..75d8cf7c29bc 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -749,6 +749,8 @@ #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219 #define USB_DEVICE_ID_LOGITECH_G15_LCD 0xc222 #define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227 +#define USB_DEVICE_ID_LOGITECH_G510 0xc22d +#define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e #define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f #define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262 #define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283 diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index ef06a85de60b..b0891503fbcf 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -22,6 +22,8 @@ enum lg_g15_model { LG_G15, LG_G15_V2, + LG_G510, + LG_G510_USB_AUDIO, }; enum lg_g15_led_type { @@ -51,12 +53,16 @@ struct lg_g15_data { struct hid_device *hdev; enum lg_g15_model model; struct lg_g15_led leds[LG_G15_LED_MAX]; + bool game_mode_enabled; }; static int lg_g15_update_led_brightness(struct lg_g15_data *g15) { int ret; + if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO) + return 0; + ret = hid_hw_raw_request(g15->hdev, LG_G15_FEATURE_REPORT, g15->transfer_buf, 4, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); @@ -256,16 +262,73 @@ static int lg_g15_v2_event(struct lg_g15_data *g15, u8 *data, int size) return 0; } +static int lg_g510_event(struct lg_g15_data *g15, u8 *data, int size) +{ + 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; +} + 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); - if (g15->model == LG_G15 && data[0] == 0x02 && size == 9) - return lg_g15_event(g15, data, size); + if (!g15) + return 0; - if (g15->model == LG_G15_V2 && data[0] == 0x02 && size == 5) - return lg_g15_v2_event(g15, data, size); + switch (g15->model) { + case LG_G15: + if (data[0] == 0x02 && size == 9) + return lg_g15_event(g15, data, size); + break; + case LG_G15_V2: + if (data[0] == 0x02 && size == 5) + return lg_g15_v2_event(g15, data, size); + break; + case LG_G510: + case LG_G510_USB_AUDIO: + if (data[0] == 0x03 && size == 5) + return lg_g510_event(g15, data, size); + break; + } return 0; } @@ -312,15 +375,33 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i) static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) { u8 gkeys_settings_output_report = 0; + u8 gkeys_settings_feature_report = 0; + struct hid_report_enum *rep_enum; unsigned int connect_mask = 0; + bool has_ff000000 = false; struct lg_g15_data *g15; struct input_dev *input; + struct hid_report *rep; int ret, i, gkeys = 0; + hdev->quirks |= HID_QUIRK_INPUT_PER_APP; + ret = hid_parse(hdev); if (ret) return ret; + /* + * 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); + g15 = devm_kzalloc(&hdev->dev, sizeof(*g15), GFP_KERNEL); if (!g15) return -ENOMEM; @@ -353,6 +434,12 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) gkeys_settings_output_report = 0x02; gkeys = 6; break; + case LG_G510: + case LG_G510_USB_AUDIO: + connect_mask = HID_CONNECT_HIDINPUT | HID_CONNECT_HIDRAW; + gkeys_settings_feature_report = 0x01; + gkeys = 18; + break; } ret = hid_hw_start(hdev, connect_mask); @@ -374,6 +461,15 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_hw_close(hdev); } + 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); + } + if (ret < 0) { hid_err(hdev, "Error disabling keyboard emulation for the G-keys\n"); goto error_hw_stop; @@ -409,6 +505,17 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) for (i = 0; i < 5; i++) input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i); + /* + * 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); + } + g15->input = input; input_set_drvdata(input, hdev); @@ -416,6 +523,9 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto error_hw_stop; + if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO) + return 0; + /* Register LED devices */ for (i = 0; i < LG_G15_LED_MAX; i++) { ret = lg_g15_register_led(g15, i); @@ -437,6 +547,14 @@ static const struct hid_device_id lg_g15_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G15_V2_LCD), .driver_data = LG_G15_V2 }, + /* 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 }, { } }; MODULE_DEVICE_TABLE(hid, lg_g15_devices);