[PATCH] HID: logitech-hidpp: support Color LED feature (8071).

From: Manuel Schönlaub
Date: Tue Mar 08 2022 - 20:15:39 EST


The HID++ protocol allows to set multicolor (RGB) to a static color.
Multiple of such LED zones per device are supported.
This patch exports said LEDs so that they can be set from userspace.

Signed-off-by: Manuel Schönlaub <manuel.schoenlaub@xxxxxxxxx>
---
drivers/hid/hid-logitech-hidpp.c | 188 +++++++++++++++++++++++++++++++
1 file changed, 188 insertions(+)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 81de88ab2..0b6c9c4b8 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -24,6 +24,8 @@
#include <linux/atomic.h>
#include <linux/fixp-arith.h>
#include <asm/unaligned.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
#include "usbhid/usbhid.h"
#include "hid-ids.h"

@@ -96,6 +98,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_CAPABILITY_BATTERY_VOLTAGE BIT(4)
#define HIDPP_CAPABILITY_BATTERY_PERCENTAGE BIT(5)
#define HIDPP_CAPABILITY_UNIFIED_BATTERY BIT(6)
+#define HIDPP_CAPABILITY_HIDPP20_COLORED_LEDS BIT(7)

#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))

@@ -159,6 +162,12 @@ struct hidpp_battery {
u8 supported_levels_1004;
};

+struct hidpp_leds {
+ u8 feature_index;
+ u8 count;
+ struct led_classdev_mc leds[];
+};
+
/**
* struct hidpp_scroll_counter - Utility class for processing high-resolution
* scroll events.
@@ -201,6 +210,7 @@ struct hidpp_device {
u8 supported_reports;

struct hidpp_battery battery;
+ struct hidpp_leds *leds;
struct hidpp_scroll_counter vertical_wheel_counter;

u8 wireless_feature_index;
@@ -1708,6 +1718,134 @@ static int hidpp_battery_get_property(struct power_supply *psy,
return ret;
}

+/* -------------------------------------------------------------------------- */
+/* 0x8070: Color LED effect */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_LED_EFFECTS 0x8070
+
+#define CMD_COLOR_LED_EFFECTS_GET_INFO 0x00
+
+#define CMD_COLOR_LED_EFFECTS_SET_ZONE_STATE 0x31
+
+static int hidpp20_color_led_effect_get_info(struct hidpp_device *hidpp_dev,
+ u8 feature_index, u8 *count)
+{
+ struct hidpp_report response;
+ int ret;
+ u8 *params = (u8 *)response.fap.params;
+
+ ret = hidpp_send_fap_command_sync(hidpp_dev, feature_index,
+ CMD_COLOR_LED_EFFECTS_GET_INFO,
+ NULL, 0, &response);
+
+ if (ret > 0) {
+ hid_err(hidpp_dev->hid_dev,
+ "%s: received protocol error 0x%02x\n",
+ __func__, ret);
+ return -EPROTO;
+ }
+ if (ret)
+ return ret;
+
+ *count = params[0];
+ return 0;
+}
+
+static int hidpp20_color_effect_set(struct hidpp_device *hidpp_dev,
+ u8 zone, bool enabled,
+ u8 r, u8 b, u8 g)
+{
+ int ret;
+ u8 params[5];
+ struct hidpp_report response;
+
+ params[0] = zone;
+ params[1] = enabled ? 1 : 0;
+ params[2] = r;
+ params[3] = g;
+ params[4] = b;
+
+ ret = hidpp_send_fap_command_sync(hidpp_dev,
+ hidpp_dev->leds->feature_index,
+ CMD_COLOR_LED_EFFECTS_SET_ZONE_STATE,
+ params, sizeof(params), &response);
+
+ if (ret)
+ return ret;
+ return 0;
+}
+
+static int hidpp_set_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ int n;
+ struct device *dev = cdev->dev->parent;
+ struct hid_device *hid = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hid);
+
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ u8 red, green, blue;
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+ red = mc_cdev->subled_info[0].brightness;
+ green = mc_cdev->subled_info[1].brightness;
+ blue = mc_cdev->subled_info[2].brightness;
+
+ for (n = 0; n < hidpp->leds->count; n++) {
+ if (cdev == &hidpp->leds->leds[n].led_cdev) {
+ return hidpp20_color_effect_set(hidpp, n,
+ brightness > 0,
+ red, green, blue);
+ }
+ }
+
+ return LED_OFF;
+}
+
+static int hidpp_mc_led_register(struct hidpp_device *hidpp_dev,
+ struct led_classdev_mc *mc_dev,
+ int zone)
+{
+ struct hid_device *hdev = hidpp_dev->hid_dev;
+ struct mc_subled *mc_led_info;
+ struct led_classdev *cdev;
+ int ret;
+
+ mc_led_info = devm_kmalloc_array(&hdev->dev, 3,
+ sizeof(*mc_led_info),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!mc_led_info)
+ return -ENOMEM;
+
+ mc_led_info[0].color_index = LED_COLOR_ID_RED;
+ mc_led_info[1].color_index = LED_COLOR_ID_GREEN;
+ mc_led_info[2].color_index = LED_COLOR_ID_BLUE;
+
+ mc_dev->subled_info = mc_led_info;
+ mc_dev->num_colors = 3;
+
+ cdev = &mc_dev->led_cdev;
+ cdev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "%s:rgb:indicator-%d", hdev->uniq, zone);
+
+ if (!cdev->name)
+ return -ENOMEM;
+
+ cdev->brightness = 0;
+ cdev->max_brightness = 255;
+ cdev->flags |= LED_CORE_SUSPENDRESUME;
+ cdev->brightness_set_blocking = hidpp_set_brightness;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, mc_dev);
+ if (ret < 0) {
+ hid_err(hdev, "Cannot register multicolor LED device: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
/* -------------------------------------------------------------------------- */
/* 0x1d4b: Wireless device status */
/* -------------------------------------------------------------------------- */
@@ -3699,6 +3837,54 @@ static int hidpp_event(struct hid_device *hdev, struct hid_field *field,
return 1;
}

+static int hidpp_initialize_leds(struct hidpp_device *hidpp_dev)
+{
+ u8 count;
+ u8 feature_index;
+ u8 feature_type;
+ int i;
+ int ret;
+ struct hid_device *hdev;
+
+ hdev = hidpp_dev->hid_dev;
+ if (hidpp_dev->leds)
+ return 0;
+ if (hidpp_dev->protocol_major >= 2) {
+ ret = hidpp_root_get_feature(hidpp_dev,
+ HIDPP_PAGE_LED_EFFECTS,
+ &feature_index,
+ &feature_type);
+ if (ret)
+ return ret;
+
+ ret = hidpp20_color_led_effect_get_info(hidpp_dev, feature_index, &count);
+ if (ret)
+ return ret;
+
+ hidpp_dev->capabilities |= HIDPP_CAPABILITY_HIDPP20_COLORED_LEDS;
+ hidpp_dev->leds = devm_kzalloc(&hdev->dev,
+ struct_size(hidpp_dev->leds, leds, count),
+ GFP_KERNEL);
+
+ if (!hidpp_dev->leds)
+ return -ENOMEM;
+
+ hidpp_dev->leds->feature_index = feature_index;
+ hidpp_dev->leds->count = count;
+
+ for (i = 0; i < count; i++) {
+ ret = hidpp_mc_led_register(hidpp_dev, &hidpp_dev->leds->leds[i], i);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+
+ } else {
+ return 0;
+ }
+}
+
static int hidpp_initialize_battery(struct hidpp_device *hidpp)
{
static atomic_t battery_no = ATOMIC_INIT(0);
@@ -3943,6 +4129,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
if (hidpp->battery.ps)
power_supply_changed(hidpp->battery.ps);

+ hidpp_initialize_leds(hidpp);
+
if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL)
hi_res_scroll_enable(hidpp);

--
2.30.2