Skip to content

Instantly share code, notes, and snippets.

@tolga9009
Created June 18, 2016 22:30
Show Gist options
  • Save tolga9009/540c5b4f599dbe7c10bd46ed78d38879 to your computer and use it in GitHub Desktop.
Save tolga9009/540c5b4f599dbe7c10bd46ed78d38879 to your computer and use it in GitHub Desktop.
Logitech G710+ Kernel Module using LED API
/*
* 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