Created
June 18, 2016 22:30
-
-
Save tolga9009/540c5b4f599dbe7c10bd46ed78d38879 to your computer and use it in GitHub Desktop.
Logitech G710+ Kernel Module using LED API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* HID driver for Logitech gaming keyboards | |
* | |
* This driver is based on logitech-g710-linux-driver by Filip Wieladke aka | |
* Wattos and hid-microsoft by Jiri Slaby et al. It provides a proof-of-concept | |
* implementation for LEDs using Linux Kernel's LED API. | |
* | |
* Copyright (c) 2015 Tolga Cakir <tolga@cevel.net> | |
*/ | |
/* | |
* This program is free software; you can redistribute it and/or modify it | |
* under the terms of the GNU General Public License as published by the Free | |
* Software Foundation; either version 2 of the License, or (at your option) | |
* any later version. | |
*/ | |
#include <linux/device.h> | |
#include <linux/hid.h> | |
#include <linux/leds.h> | |
#include <linux/module.h> | |
#include "hid-ids.h" | |
#define LOGITECH_G710_PROFILE_LEDS 3 /* M[1-3] */ | |
#define LOGITECH_G710_RECORD_LED_BIT 3 | |
struct logitech_kbd_drvdata { | |
struct hid_device *hdev; | |
struct hid_report *extra_keys_report; /* G[1-6], M[1-3] and MR keys */ | |
struct hid_report *macro_leds_report; /* backlight of M[1-3] and MR */ | |
struct led_classdev profile_led[LOGITECH_G710_PROFILE_LEDS]; | |
struct led_classdev record_led; | |
struct work_struct work; | |
__u8 profile_led_state[LOGITECH_G710_PROFILE_LEDS]; | |
__u8 record_led_state; | |
}; | |
static void logitech_kbd_brightness_set(struct led_classdev *led_cdev, | |
enum led_brightness brightness) | |
{ | |
struct device *dev = led_cdev->dev->parent; | |
struct hid_device *hdev = container_of(dev, struct hid_device, dev); | |
struct logitech_kbd_drvdata *drvdata = hid_get_drvdata(hdev); | |
int i; | |
for (i = 0; i < LOGITECH_G710_PROFILE_LEDS; i++) { | |
if (led_cdev == &drvdata->profile_led[i]) { | |
drvdata->profile_led_state[i] = brightness; | |
schedule_work(&drvdata->work); | |
} | |
} | |
if (led_cdev == &drvdata->record_led) { | |
drvdata->record_led_state = brightness; | |
schedule_work(&drvdata->work); | |
} | |
} | |
static void logitech_kbd_leds_work(struct work_struct *work) | |
{ | |
struct logitech_kbd_drvdata *drvdata = | |
container_of(work, struct logitech_kbd_drvdata, work); | |
struct hid_device *hdev = drvdata->hdev; | |
struct hid_report *report = drvdata->macro_leds_report; | |
__u8 leds = 0; | |
int i; | |
for (i = 0; i < LOGITECH_G710_PROFILE_LEDS; i++) { | |
if (drvdata->profile_led_state[i]) | |
leds |= BIT(i); | |
} | |
if (drvdata->record_led_state) | |
leds |= BIT(LOGITECH_G710_RECORD_LED_BIT); | |
report->field[0]->value[0] = leds << 4; | |
hid_hw_request(hdev, report, HID_REQ_SET_REPORT); | |
} | |
static void logitech_kbd_leds_init(struct hid_device *hdev) | |
{ | |
struct logitech_kbd_drvdata *drvdata = hid_get_drvdata(hdev); | |
size_t name_sz; | |
char *name; | |
int i, ret = 0; | |
const char *name_fmt, *name_fmt_r; | |
INIT_WORK(&drvdata->work, logitech_kbd_leds_work); | |
name_sz = strlen(dev_name(&hdev->dev)) + strlen("::profile#") + 1; | |
name_fmt = "%s::profile%d"; | |
/* Register M1 - M3 LEDs */ | |
for (i = 0; i < LOGITECH_G710_PROFILE_LEDS; i++) { | |
name = kzalloc(name_sz, GFP_KERNEL); | |
snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), i + 1); | |
drvdata->profile_led[i].name = name; | |
drvdata->profile_led[i].brightness = drvdata->profile_led_state[i]; | |
drvdata->profile_led[i].max_brightness = 1; | |
drvdata->profile_led[i].brightness_set = | |
logitech_kbd_brightness_set; | |
ret = led_classdev_register(&hdev->dev, &drvdata->profile_led[i]); | |
if (ret) { | |
hid_err(hdev, "could not register led device\n"); | |
} | |
} | |
/* Register MR LED */ | |
name_sz = strlen(dev_name(&hdev->dev)) + strlen("::record") + 1; | |
name_fmt_r = "%s::record"; | |
name = kzalloc(name_sz, GFP_KERNEL); | |
snprintf(name, name_sz, name_fmt_r, dev_name(&hdev->dev)); | |
drvdata->record_led.name = name; | |
drvdata->record_led.brightness = drvdata->record_led_state; | |
drvdata->record_led.max_brightness = 1; | |
drvdata->record_led.brightness_set = logitech_kbd_brightness_set; | |
ret = led_classdev_register(&hdev->dev, &drvdata->profile_led[i]); | |
if (ret) { | |
hid_err(hdev, "could not register led device\n"); | |
} | |
} | |
static void logitech_kbd_leds_remove(struct hid_device *hdev) | |
{ | |
struct logitech_kbd_drvdata *drvdata = hid_get_drvdata(hdev); | |
int i; | |
for (i = 0; i < LOGITECH_G710_PROFILE_LEDS; i++) { | |
led_classdev_unregister(&drvdata->profile_led[i]); | |
kfree(drvdata->profile_led[i].name); | |
} | |
led_classdev_unregister(&drvdata->record_led); | |
kfree(drvdata->record_led.name); | |
cancel_work_sync(&drvdata->work); | |
} | |
static int logitech_kbd_init(struct hid_device *hdev) | |
{ | |
struct logitech_kbd_drvdata *drvdata; | |
struct list_head *report_list; | |
struct hid_report *report; | |
report_list = &hdev->report_enum[HID_FEATURE_REPORT].report_list; | |
if (list_empty(report_list)) { | |
return 0; /* handles composite devices */ | |
} | |
drvdata = hid_get_drvdata(hdev); | |
list_for_each_entry(report, report_list, list) { | |
switch (report->id) { | |
case 6: drvdata->macro_leds_report = report; break; | |
//case 9: drvdata->extra_keys_report = report; break; | |
} | |
} | |
logitech_kbd_leds_init(hdev); | |
return 0; | |
} | |
static int logitech_kbd_probe(struct hid_device *hdev, | |
const struct hid_device_id *id) | |
{ | |
int ret; | |
struct logitech_kbd_drvdata *drvdata; | |
drvdata = devm_kzalloc(&hdev->dev, sizeof(struct logitech_kbd_drvdata), | |
GFP_KERNEL); | |
if (drvdata == NULL) { | |
hid_err(hdev, "can't alloc logitech-kbd data"); | |
return -ENOMEM; | |
} | |
hdev->quirks = id->driver_data; | |
hid_set_drvdata(hdev, drvdata); | |
drvdata->hdev = hdev; | |
ret = hid_parse(hdev); | |
if (ret) { | |
hid_err(hdev, "parse failed\n"); | |
goto err_free; | |
} | |
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | |
if (ret) { | |
hid_err(hdev, "hw start failed\n"); | |
goto err_free; | |
} | |
logitech_kbd_init(hdev); | |
return 0; | |
err_free: | |
return ret; | |
} | |
static void logitech_kbd_remove(struct hid_device *hdev) | |
{ | |
logitech_kbd_leds_remove(hdev); | |
hid_hw_stop(hdev); | |
} | |
static int logitech_kbd_event(struct hid_device *hdev, struct hid_field *field, | |
struct hid_usage *usage, __s32 value) | |
{ | |
switch (usage->hid & HID_USAGE) { | |
default: break; | |
} | |
return 0; | |
} | |
static int logitech_kbd_input_mapping(struct hid_device *dev, | |
struct hid_input *input, | |
struct hid_field *field, | |
struct hid_usage *usage, | |
unsigned long **bit, int *max) | |
{ | |
return 0; | |
} | |
static const struct hid_device_id logitech_kbd_devices[] = { | |
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, \ | |
USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS), | |
.driver_data = HID_QUIRK_NOGET }, | |
{ } | |
}; | |
MODULE_DEVICE_TABLE(hid, logitech_kbd_devices); | |
static struct hid_driver logitech_kbd_driver = { | |
.name = "logitech-kbd", | |
.id_table = logitech_kbd_devices, | |
.probe = logitech_kbd_probe, | |
.event = logitech_kbd_event, | |
.remove = logitech_kbd_remove, | |
.input_mapping = logitech_kbd_input_mapping, | |
}; | |
module_hid_driver(logitech_kbd_driver); | |
MODULE_LICENSE("GPL"); | |
MODULE_AUTHOR("Tolga Cakir"); | |
MODULE_DESCRIPTION("HID driver for Logitech gaming keyboards"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment