@@ -857,6 +857,7 @@
#define USB_DEVICE_ID_MX5500_RECEIVER_MOUSE_DEV 0xc71c
#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_KBD_DEV 0xc71e
#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_MOUSE_DEV 0xc71f
+#define USB_DEVICE_ID_LOGITECH_LITRA_GLOW 0xc900
#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2 0xca03
#define USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL 0xca04
b/drivers/hid/hid-logitech-hidpp.c
@@ -11,6 +11,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/usb.h>
#include <linux/hid.h>
@@ -77,6 +78,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_QUIRK_HIDPP_WHEELS BIT(26)
#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(27)
#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(28)
+#define HIDPP_QUIRK_CLASS_SIMPLE_START BIT(29)
/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -99,6 +101,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL BIT(7)
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL BIT(8)
#define HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL BIT(9)
+#define HIDPP_CAPABILITY_ILLUMINATION_LIGHT BIT(10)
#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max,
EV_KEY, (c))
@@ -207,6 +210,7 @@ struct hidpp_device {
struct hidpp_scroll_counter vertical_wheel_counter;
u8 wireless_feature_index;
+ u8 illumination_feature_index;
};
/* HID++ 1.0 error codes */
@@ -228,6 +232,7 @@ struct hidpp_device {
#define HIDPP20_ERROR 0xff
static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
+static void hidpp_illumination_defered_event(struct hidpp_device *hidpp);
static int __hidpp_send_report(struct hid_device *hdev,
struct hidpp_report *hidpp_report)
@@ -402,6 +407,9 @@ static void delayed_work_cb(struct work_struct *work)
struct hidpp_device *hidpp = container_of(work, struct
hidpp_device,
work);
hidpp_connect_event(hidpp);
+ if (hidpp->capabilities & HIDPP_CAPABILITY_ILLUMINATION_LIGHT) {
+ hidpp_illumination_defered_event(hidpp);
+ }
}
static inline bool hidpp_match_answer(struct hidpp_report *question,
@@ -857,6 +865,8 @@ static int hidpp_unifying_init(struct hidpp_device
*hidpp)
#define CMD_ROOT_GET_FEATURE 0x00
#define CMD_ROOT_GET_PROTOCOL_VERSION 0x10
+#define HIDPP_FEATURE_TYPE_HIDDEN 0x70
+
static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16
feature,
u8 *feature_index, u8 *feature_type)
{
@@ -1723,6 +1733,392 @@ static int hidpp_set_wireless_feature_index(struct
hidpp_device *hidpp)
return ret;
}
+/*
--------------------------------------------------------------------------
*/
+/* 0x1990: Illumination Light
*/
+/*
--------------------------------------------------------------------------
*/
+
+#define HIDPP_PAGE_ILLUMINATION_LIGHT 0x1990
+
+#define HIDPP_ILLUMINATION_FUNC_GET 0x00
+#define HIDPP_ILLUMINATION_FUNC_SET 0x10
+#define HIDPP_ILLUMINATION_FUNC_GET_BRIGHTNESS_INFO 0x20
+#define HIDPP_ILLUMINATION_FUNC_GET_BRIGHTNESS 0x30
+#define HIDPP_ILLUMINATION_FUNC_SET_BRIGHTNESS 0x40
+
+/* Not yet supported
+ * #define HIDPP_ILLUMINATION_FUNC_GET_BRIGHTNESS_LEVELS 0x50
+ * #define HIDPP_ILLUMINATION_FUNC_SET_BRIGHTNESS_LEVELS 0x60
+ */
+
+#define HIDPP_ILLUMINATION_FUNC_GET_COLOR_TEMPERATURE_INFO 0x70
+#define HIDPP_ILLUMINATION_FUNC_GET_COLOR_TEMPERATURE 0x80
+#define HIDPP_ILLUMINATION_FUNC_SET_COLOR_TEMPERATURE 0x90
+
+/* Not yet supported
+ * #define HIDPP_ILLUMINATION_FUNC_GET_COLOR_TEMPERATURE_LEVELS 0xA0
+ * #define HIDPP_ILLUMINATION_FUNC_SET_COLOR_TEMPERATURE_LEVELS 0xB0
+ */
+
+#define HIDPP_ILLUMINATION_EVENT_CHANGE 0x00
+#define HIDPP_ILLUMINATION_EVENT_BRIGHTNESS_CHANGE 0x10
+#define HIDPP_ILLUMINATION_EVENT_COLOR_TEMPERATURE_CHANGE 0x20
+
+#define HIDPP_ILLUMINATION_CAP_EVENTS BIT(0)
+#define HIDPP_ILLUMINATION_CAP_LINEAR_LEVELS BIT(1)
+#define HIDPP_ILLUMINATION_CAP_NON_LINEAR_LEVELS BIT(2)
+
+struct control_info {
+ u16 min;
+ u16 max;
+ u16 res;
+ u8 capabilities;
+ u8 max_levels;
+};
+
+struct led_data {
+ struct led_classdev cdev;
+ struct hidpp_device *drv_data;
+ struct hid_device *hdev;
+ u16 feature_index;
+ struct control_info brightness_info;
+ struct control_info color_temperature_info;
+ struct {
+ struct mutex mutex;
+ int on;
+ int brightness;
+ } hw_change;
+ char dirname[256];
+};
+
+/* kernel led interface designates 0 as off. To not lose the ability to
chose
+ * minimal brightness, we thus need to increase the reported range by 1
+ */
+static unsigned int device_to_led_brightness(struct led_data *led, u16
device_brightness)
+{
+ u16 relative = device_brightness - led->brightness_info.min;
+ u16 step = relative / led->brightness_info.res;
+
+ return step + 1;
+}
+
+static u16 led_to_device_brightness(struct led_data *led, unsigned int
led_brightness)
+{
+ unsigned int step = led_brightness - 1;
+ unsigned int relative = step * led->brightness_info.res;
+
+ return led->brightness_info.min + relative;
+}
+
+static int request_led_on(struct hidpp_device *hidpp, struct led_data
*led, bool *on)
+{
+
+ struct hidpp_report report;
+
+ int ret = hidpp_send_fap_command_sync(hidpp,
+ hidpp->illumination_feature_index,
+ HIDPP_ILLUMINATION_FUNC_GET, NULL,
+ 0, &report);
+ if (ret) {
+ hid_err(hidpp->hid_dev, "Getting Illumination failed\n");
+ return ret;
+ }
+
+ *on = report.fap.params[0] & BIT(0);
+ return 0;
+}
+
+static int request_led_brightness(struct hidpp_device *hidpp, struct
led_data *led,
+ unsigned int *led_brightness)
+{
+ u16 device_brightness;
+ struct hidpp_report report;
+
+ int ret = hidpp_send_fap_command_sync(
+ hidpp, hidpp->illumination_feature_index,
+ HIDPP_ILLUMINATION_FUNC_GET_BRIGHTNESS, NULL, 0, &report);
+ if (ret) {
+ hid_err(hidpp->hid_dev,
+ "Getting Illumination Brightness failed\n");
+ return ret;
+ }
+
+ device_brightness = get_unaligned_be16(&report.fap.params[0]);
+ *led_brightness = device_to_led_brightness(led,
device_brightness);
+ return 0;
+}
+
+static enum led_brightness led_brightness_get(struct led_classdev
*led_cdev)
+{
+ bool on;
+ unsigned int brightness;
+ struct led_data *led = container_of(led_cdev, struct led_data,
cdev);
+ struct hidpp_device *hidpp = led->drv_data;
+
+ int ret = request_led_on(hidpp, led, &on);
+
+ if (ret || !on)
+ return LED_OFF;
+
+ ret = request_led_brightness(hidpp, led, &brightness);
+ if (ret)
+ return LED_OFF;
+
+ return brightness;
+}
+
+
+static void led_brightness_set_dummy(struct led_classdev *led_cdev,
+ enum led_brightness led_brightness)
+{
+}
+
+static int led_brightness_set_sync(struct led_classdev *led_cdev,
+ enum led_brightness led_brightness)
+{
+ struct hidpp_report report;
+ struct led_data *led = container_of(led_cdev, struct led_data,
cdev);
+ struct hidpp_device *hidpp = led->drv_data;
+ u16 device_brightness = led_to_device_brightness(led,
led_brightness);
+
+ bool on = led_brightness != 0;
+ u8 params[2] = { on ? BIT(0) : 0, 0 };
+
+ int ret = hidpp_send_fap_command_sync(hidpp,
+
hidpp->illumination_feature_index,
+ HIDPP_ILLUMINATION_FUNC_SET,
params,
+ 1, &report);
+ if (ret) {
+ hid_err(hidpp->hid_dev, "Setting Illumination failed\n");
+ return ret;
+ }
+
+ if (!on)
+ return 0;
+
+ put_unaligned_be16(device_brightness, params);
+ ret = hidpp_send_fap_command_sync(
+ hidpp, hidpp->illumination_feature_index,
+ HIDPP_ILLUMINATION_FUNC_SET_BRIGHTNESS, params, 2,
+ &report);
+ if (ret) {
+ hid_err(hidpp->hid_dev,
+ "Setting Illumination Brightness failed\n");
+ }
+ return ret;
+}
+
+static int get_brightness_info_sync(struct hidpp_device *hidpp,
+ struct control_info *info)
+{
+ struct hidpp_report resp;
+ int ret = hidpp_send_fap_command_sync(
+ hidpp, hidpp->illumination_feature_index,
+ HIDPP_ILLUMINATION_FUNC_GET_BRIGHTNESS_INFO, NULL, 0,
&resp);
+ if (ret) {
+ hid_err(hidpp->hid_dev,
+ "%s: failed with %d\n", __func__, ret);
+ return ret;
+ }
+
+ info->capabilities = resp.fap.params[0];
+ info->min = get_unaligned_be16(&resp.fap.params[1]);
+ info->max = get_unaligned_be16(&resp.fap.params[3]);
+ info->res = get_unaligned_be16(&resp.fap.params[5]);
+ info->max_levels = resp.fap.params[7] & 0x0F;
+ return 0;
+}
+
+static int get_color_temperature_info_sync(struct hidpp_device *hidpp,
+ struct control_info *info)
+{
+ struct hidpp_report resp;
+ int ret = hidpp_send_fap_command_sync(
+ hidpp, hidpp->illumination_feature_index,
+ HIDPP_ILLUMINATION_FUNC_GET_COLOR_TEMPERATURE_INFO, NULL,
0,
+ &resp);
+ if (ret) {
+ hid_err(hidpp->hid_dev,
+ "%s: failed with %d\n", __func__, ret);
+ return ret;
+ }
+
+ info->capabilities = resp.fap.params[0];
+ info->min = get_unaligned_be16(&resp.fap.params[1]);
+ info->max = get_unaligned_be16(&resp.fap.params[3]);
+ info->res = get_unaligned_be16(&resp.fap.params[5]);
+ info->max_levels = resp.fap.params[7];
+ return 0;
+}
+
+static int register_led(struct hidpp_device *hidpp)
+{
+ int ret;
+ unsigned int brightness_range;
+ struct led_data *led = devm_kzalloc(&hidpp->hid_dev->dev,
sizeof(struct led_data),
+ GFP_KERNEL);
+
+ if (!led)
+ return -ENOMEM;
+
+ ret = get_brightness_info_sync(hidpp, &led->brightness_info);
+ if (ret)
+ goto cleanup;
+
+ ret = get_color_temperature_info_sync(hidpp,
+
&led->color_temperature_info);
+ if (ret)
+ goto cleanup;
+
+ led->drv_data = hidpp;
+ mutex_init(&led->hw_change.mutex);
+ led->hw_change.on = -1;
+ led->hw_change.brightness = -1;
+ led->cdev.name = "logitech::illumination";
+ led->cdev.flags = LED_BRIGHT_HW_CHANGED | LED_HW_PLUGGABLE;
+ led->cdev.max_brightness = device_to_led_brightness(led,
led->brightness_info.max);
+ brightness_range = led->brightness_info.max -
led->brightness_info.min;
+ if (brightness_range == 0) {
+ /* According to docs set value is not supported under
these
+ * conditions.
+ * LED interface enforces a set function.
+ */
+ led->cdev.brightness_set = led_brightness_set_dummy;
+ } else {
+ led->cdev.brightness_set_blocking =
led_brightness_set_sync;
+ }
+ led->cdev.brightness_get = led_brightness_get;
+
+ ret = devm_led_classdev_register(&hidpp->hid_dev->dev,
&led->cdev);
+ if (ret < 0)
+ goto register_fail;
+
+ hidpp->private_data = led;
+ return 0;
+register_fail:
+ mutex_destroy(&led->hw_change.mutex);
+cleanup:
+ devm_kfree(&hidpp->hid_dev->dev, led);
+ return ret;
+}
+
+static int hidpp_initialize_illumination(struct hidpp_device *hidpp)
+{
+ int ret;
+ unsigned long capabilities = hidpp->capabilities;
+
+ if (hidpp->protocol_major >= 2) {
+ u8 feature_index;
+ u8 feature_type;
+
+ ret = hidpp_root_get_feature(hidpp,
+
HIDPP_PAGE_ILLUMINATION_LIGHT,
+ &feature_index,
&feature_type);
+ if (!ret && !(feature_type & HIDPP_FEATURE_TYPE_HIDDEN)) {
+ hidpp->capabilities |=
+ HIDPP_CAPABILITY_ILLUMINATION_LIGHT;
+ hidpp->illumination_feature_index = feature_index;
+ hid_dbg(hidpp->hid_dev,
+ "Detected HID++ 2.0 Illumination
Light\n");
+ return 0;
+ }
+ }
+
+ if (hidpp->capabilities == capabilities)
+ hid_dbg(hidpp->hid_dev,
+ "Did not detect HID++ Illumination Light hardware
support\n");
+ return 0;
+}
+
+static void hidpp_remove_illumination(struct hidpp_device *hidpp)
+{
+ struct led_data *led = (struct led_data *)hidpp->private_data;
+
+ mutex_destroy(&led->hw_change.mutex);
+}
+
+static void hidpp_illumination_defered_event(struct hidpp_device *hidpp)
+{
+ bool has_hw_change, on;
+ int ret;
+ unsigned int led_brightness;
+ struct led_data *led = (struct led_data *)hidpp->private_data;
+
+ mutex_lock(&led->hw_change.mutex);
+
+ has_hw_change = led->hw_change.on == -1 &&
led->hw_change.brightness == -1;
+
+ if (!has_hw_change)
+ goto unlock;
+
+ if (led->hw_change.on == -1) {
+ ret = request_led_on(hidpp, led, &on);
+ if (ret)
+ goto reset_hw_change;
+ } else {
+ on = led->hw_change.on;
+ }
+
+ if (!on) {
+ led_brightness = LED_OFF;
+ goto notify_changed;
+ }
+
+ if (led->hw_change.brightness == -1) {
+ ret = request_led_brightness(hidpp, led, &led_brightness);
+ if (ret)
+ goto reset_hw_change;
+ } else {
+ led_brightness = device_to_led_brightness(led,
led->hw_change.brightness);
+ }
+
+notify_changed:
+ led_classdev_notify_brightness_hw_changed(
+ &led->cdev, led_brightness);
+reset_hw_change:
+ led->hw_change.on = -1;
+ led->hw_change.brightness = -1;
+unlock:
+ mutex_unlock(&led->hw_change.mutex);
+}
+
+static int hidpp20_illumination_raw_event(struct hidpp_device *hidpp, u8
*data,
+ int size)
+{
+ struct led_data *led = (struct led_data *)hidpp->private_data;
+ struct hidpp_report *report = (struct hidpp_report *)data;
+
+ switch (report->report_id) {
+ case REPORT_ID_HIDPP_LONG:
+ /* size is already checked in hidpp_raw_event.
+ * only leave long through
+ */
+ break;
+ default:
+ hid_err(hidpp->hid_dev, "%s:Unhandled report_id %u\n",
__func__, report->report_id);
+ return 0;
+ }
+
+ if (report->fap.feature_index !=
hidpp->illumination_feature_index)
+ return 0;
+
+ if (report->fap.funcindex_clientid ==
HIDPP_ILLUMINATION_EVENT_CHANGE) {
+ mutex_lock(&led->hw_change.mutex);
+ led->hw_change.on = report->fap.params[0] & BIT(0);
+ mutex_unlock(&led->hw_change.mutex);
+ return 0;
+ }
+
+ if (report->fap.funcindex_clientid ==
+ HIDPP_ILLUMINATION_EVENT_BRIGHTNESS_CHANGE) {
+ mutex_lock(&led->hw_change.mutex);
+ led->hw_change.brightness =
get_unaligned_be16(&report->fap.params[0]);
+ mutex_unlock(&led->hw_change.mutex);
+ return 0;
+ }
+
+ return 0;
+}
+
/*
--------------------------------------------------------------------------
*/
/* 0x2120: Hi-resolution scrolling
*/
/*
--------------------------------------------------------------------------
*/
@@ -3640,6 +4036,12 @@ static int hidpp_raw_hidpp_event(struct
hidpp_device *hidpp, u8 *data,
return ret;
}
+ if (hidpp->capabilities & HIDPP_CAPABILITY_ILLUMINATION_LIGHT) {
+ ret = hidpp20_illumination_raw_event(hidpp, data, size);
+ if (ret != 0)
+ return ret;
+ }
+
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) {
ret = hidpp10_wheel_raw_event(hidpp, data, size);
if (ret != 0)
@@ -3964,6 +4366,7 @@ static void hidpp_connect_event(struct hidpp_device
*hidpp)
hidpp_initialize_battery(hidpp);
hidpp_initialize_hires_scroll(hidpp);
+ hidpp_initialize_illumination(hidpp);
/* forward current battery state */
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
@@ -3986,6 +4389,14 @@ static void hidpp_connect_event(struct hidpp_device
*hidpp)
if (hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL)
hi_res_scroll_enable(hidpp);
+ if (hidpp->capabilities & HIDPP_CAPABILITY_ILLUMINATION_LIGHT) {
+ ret = register_led(hidpp);
+ if (ret) {
+ hid_err(hdev, "Registering leds failed.\n");
+ return;
+ }
+ }
+
if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) ||
hidpp->delayed_input)
/* if the input nodes are already created, we can stop now
*/
return;
@@ -4156,11 +4567,15 @@ static int hidpp_probe(struct hid_device *hdev,
const struct hid_device_id *id)
hid_warn(hdev, "Cannot allocate sysfs group for %s\n",
hdev->name);
- /*
- * Plain USB connections need to actually call start and open
- * on the transport driver to allow incoming data.
- */
- ret = hid_hw_start(hdev, 0);
+ if (hidpp->quirks & HIDPP_QUIRK_CLASS_SIMPLE_START) {
+ ret = hid_hw_start(hdev, HID_CLAIMED_DRIVER);
+ } else {
+ /*
+ * Plain USB connections need to actually call start and
open
+ * on the transport driver to allow incoming data.
+ */
+ ret = hid_hw_start(hdev, 0);
+ }
if (ret) {
hid_err(hdev, "hw start failed\n");
goto hid_hw_start_fail;
@@ -4211,6 +4626,10 @@ static int hidpp_probe(struct hid_device *hdev,
const struct hid_device_id *id)
hidpp_connect_event(hidpp);
+ /* Resetting HID node made communication with Glow break down*/
+ if (hidpp->quirks & HIDPP_QUIRK_CLASS_SIMPLE_START)
+ return 0;
+
/* Reset the HID node state */
hid_device_io_stop(hdev);
hid_hw_close(hdev);
@@ -4254,6 +4673,9 @@ static void hidpp_remove(struct hid_device *hdev)
if (!hidpp)
return hid_hw_stop(hdev);
+ if (hidpp->capabilities & HIDPP_CAPABILITY_ILLUMINATION_LIGHT)
+ hidpp_remove_illumination(hidpp);
+
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
hid_hw_stop(hdev);
@@ -4334,6 +4756,9 @@ static const struct hid_device_id hidpp_devices[] =
{
.driver_data = HIDPP_QUIRK_CLASS_G920 |
HIDPP_QUIRK_FORCE_OUTPUT_REPORTS},
{ /* Logitech G Pro Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
+ { /* Logitech Litra Glow over USB*/
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_LITRA_GLOW),
+ .driver_data = HIDPP_QUIRK_CLASS_SIMPLE_START |
HIDPP_QUIRK_FORCE_OUTPUT_REPORTS },
{ /* MX5000 keyboard over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305),
@@ -494,6 +494,7 @@ static const struct hid_device_id
hid_have_special_driver[] = {
#endif
#if IS_ENABLED(CONFIG_HID_LOGITECH_HIDPP)
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_G920_WHEEL) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_LITRA_GLOW) },
Tries to implement as general support for Illumination Light as possible. Note that it is singular, which means by Logitech spec we are fine off with just handling one capability/device. Implementation currently only exposes Brightness and On/Off controls. Does currently not expose Color Temperature because LEDs does not support it. Introduces HIDPP_QUIRK_CLASS_SIMPLE_START to prevent reconnect on startup. Could not get Glow to work with that. Signed-off-by: Andreas Bergmeier <abergmeier@gmx.net> --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-logitech-hidpp.c | 435 ++++++++++++++++++++++++++++++- drivers/hid/hid-quirks.c | 1 + 3 files changed, 432 insertions(+), 5 deletions(-) #endif #if IS_ENABLED(CONFIG_HID_MAGICMOUSE) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE) }, -- 2.34.1