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