enum lg_g15_model {
LG_G15,
LG_G15_V2,
+ LG_G510,
+ LG_G510_USB_AUDIO,
};
enum lg_g15_led_type {
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);
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;
}
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;
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);
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;
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);
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);
{ 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);