linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: "Manuel Schönlaub" <manuel.schoenlaub@gmail.com>
To: manuel.schoenlaub@gmail.com
Cc: lains@riseup.net, jikos@kernel.org,
	benjamin.tissoires@redhat.com, linux-input@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH] HID: logitech-hidpp: support Color LED feature (8071).
Date: Tue, 8 Mar 2022 16:50:57 -0700	[thread overview]
Message-ID: <Yifr4etBFPu1a2Ct@hermes> (raw)

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@gmail.com>
---
 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


             reply	other threads:[~2022-03-09  1:15 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-08 23:50 Manuel Schönlaub [this message]
2022-03-23 21:04 ` [PATCH] HID: logitech-hidpp: support Color LED feature (8071) Pavel Machek
2022-03-24  2:21   ` Manuel Schönlaub
2022-05-05  8:25     ` Pavel Machek
2022-03-23 21:22 ` Filipe Laíns
2022-03-23 22:24   ` Bastien Nocera
2022-03-24  3:28     ` Manuel Schönlaub
2022-03-24  9:32       ` Bastien Nocera
2022-03-24 16:10         ` Manuel Schönlaub
2022-05-05  8:29           ` Pavel Machek
2022-03-24  3:34   ` Manuel Schönlaub
2022-03-24 19:54     ` Benjamin Tissoires
2022-03-25  1:29       ` Manuel Schönlaub
2022-05-05  8:32         ` Pavel Machek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=Yifr4etBFPu1a2Ct@hermes \
    --to=manuel.schoenlaub@gmail.com \
    --cc=benjamin.tissoires@redhat.com \
    --cc=jikos@kernel.org \
    --cc=lains@riseup.net \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).