From a47492e62c9a5e4e1b364b86a83aa13f62d7a067 Mon Sep 17 00:00:00 2001 From: Ogier Bouvier Date: Fri, 15 Sep 2017 16:10:20 +0200 Subject: [PATCH] hidpp20: add support for non-RGB LEDs, feature 0x1300 Signed-off-by: Ogier Bouvier --- src/driver-hidpp20.c | 13 +- src/hidpp20.c | 296 +++++++++++++++++++++++++++++++++++++++++++ src/hidpp20.h | 140 +++++++++++++++++++- 3 files changed, 440 insertions(+), 9 deletions(-) diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c index 840de13d..1d7175b2 100644 --- a/src/driver-hidpp20.c +++ b/src/driver-hidpp20.c @@ -54,6 +54,7 @@ #define HIDPP_CAP_KBD_REPROGRAMMABLE_KEYS_1b00 (1 << 4) #define HIDPP_CAP_COLOR_LED_EFFECTS_8070 (1 << 5) #define HIDPP_CAP_ONBOARD_PROFILES_8100 (1 << 6) +#define HIDPP_CAP_LED_SW_CONTROL (1 << 7) struct hidpp20drv_data { struct hidpp20_device *dev; @@ -63,7 +64,7 @@ struct hidpp20drv_data { unsigned num_controls; struct hidpp20_control_id *controls; struct hidpp20_profiles *profiles; - struct hidpp20_color_led_zone_info *led_infos; + union hidpp20_generic_led_zone_info led_infos; unsigned int num_profiles; unsigned int num_resolutions; @@ -257,7 +258,7 @@ hidpp20drv_read_led(struct ratbag_led *led) if (!(drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070)) return; - led_info = &drv_data->led_infos[led->index]; + led_info = &drv_data->led_infos.color_leds[led->index]; profile = &drv_data->profiles->profiles[led->profile->index]; h_led = &profile->leds[led->index]; @@ -677,11 +678,11 @@ hidpp20drv_read_color_leds(struct ratbag_device *device) if (!(drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070)) return 0; - free(drv_data->led_infos); - drv_data->led_infos = NULL; + free(drv_data->led_infos.color_leds); + drv_data->led_infos.color_leds = NULL; drv_data->num_leds = 0; - rc = hidpp20_color_led_effects_get_zone_infos(drv_data->dev, &drv_data->led_infos); + rc = hidpp20_color_led_effects_get_zone_infos(drv_data->dev, &drv_data->led_infos.color_leds); if (rc > 0) { drv_data->num_leds = rc; @@ -999,7 +1000,7 @@ hidpp20drv_remove(struct ratbag_device *device) if (drv_data->profiles) hidpp20_onboard_profiles_destroy(drv_data->profiles); - free(drv_data->led_infos); + free(drv_data->led_infos.color_leds); free(drv_data->controls); free(drv_data->sensors); if (drv_data->dev) diff --git a/src/hidpp20.c b/src/hidpp20.c index 70b43860..fe9d8a32 100644 --- a/src/hidpp20.c +++ b/src/hidpp20.c @@ -70,7 +70,32 @@ hidpp20_feature_get_name(uint16_t feature) break; } + return str; +} + +const char* +hidpp20_sw_led_control_get_mode_string(const enum hidpp20_led_sw_ctrl_led_mode mode) +{ + static char numeric[8]; + const char* str; + + switch (mode) + { + CASE_RETURN_STRING(HIDPP20_LED_MODE_OFF); + CASE_RETURN_STRING(HIDPP20_LED_MODE_ON); + CASE_RETURN_STRING(HIDPP20_LED_MODE_BLINK); + CASE_RETURN_STRING(HIDPP20_LED_MODE_RAMP_UP); + CASE_RETURN_STRING(HIDPP20_LED_MODE_RAMP_DOWN); + CASE_RETURN_STRING(HIDPP20_LED_MODE_BREATHING); + CASE_RETURN_STRING(HIDPP20_LED_MODE_HEARTBEAT); + CASE_RETURN_STRING(HIDPP20_LED_MODE_TRAVEL); + default: + sprintf_safe(numeric, "%#4x", mode); + str = numeric; + break; + } #undef CASE_RETURN_STRING + return str; } @@ -400,6 +425,277 @@ hidpp20_batterylevel_get_battery_level(struct hidpp20_device *device, return msg.msg.parameters[2]; } +/* -------------------------------------------------------------------------- */ +/* 0x1300: Non-RGB led support */ +/* -------------------------------------------------------------------------- */ + +#define CMD_LED_SW_CONTROL_GET_LED_COUNT 0x00 +#define CMD_LED_SW_CONTROL_GET_LED_INFO 0x10 +#define CMD_LED_SW_CONTROL_GET_SW_CTRL 0x20 +#define CMD_LED_SW_CONTROL_SET_SW_CTRL 0x30 +#define CMD_LED_SW_CONTROL_GET_LED_STATE 0x40 +#define CMD_LED_SW_CONTROL_SET_LED_STATE 0x50 +#define CMD_LED_SW_CONTROL_GET_NV_CONFIG 0x60 + +static bool hidpp20_led_sw_control_check_state(uint16_t state) +{ + switch (state) + { + case HIDPP20_LED_MODE_ON: + case HIDPP20_LED_MODE_OFF: + case HIDPP20_LED_MODE_BLINK: + case HIDPP20_LED_MODE_TRAVEL: + case HIDPP20_LED_MODE_RAMP_UP: + case HIDPP20_LED_MODE_RAMP_DOWN: + case HIDPP20_LED_MODE_HEARTBEAT: + case HIDPP20_LED_MODE_BREATHING: + return true; + } + + return false; +} + +int hidpp20_led_sw_control_read_leds(struct hidpp20_device* device, + struct hidpp20_led_sw_ctrl_led_info** info_list) +{ + int rc; + struct hidpp20_led_sw_ctrl_led_info *i_list, *info; + unsigned i; + uint8_t num_infos; + + rc = hidpp20_led_sw_control_get_led_count(device); + + if (rc < 0) + return rc; + + num_infos = rc; + + if(rc == 0) { + *info_list = NULL; + return 0; + } + + i_list = zalloc(rc * sizeof(struct hidpp20_led_sw_ctrl_led_info)); + + for (i = 0; i < num_infos; i++) { + info = &i_list[i]; + info->index = i; + + rc = hidpp20_led_sw_control_get_led_info(device, i, info); + + if (rc != 0) + goto err; + + hidpp_log_raw(&device->base, "non-color led %d: type: %d supports: %d\n", + info->index, + info->type, + info->caps); + } + + *info_list = i_list; + return num_infos; + +err: + free(i_list); + return rc; +} + +int hidpp20_led_sw_control_get_led_count(struct hidpp20_device* device) +{ + union hidpp20_message msg = { + .msg = { + .report_id = REPORT_ID_LONG, + .address = CMD_LED_SW_CONTROL_GET_LED_COUNT, + .device_idx = device->index, + }, + }; + + uint8_t feature_idx; + + feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); + if (feature_idx == 0) { + return -ENOTSUP; + } + + msg.msg.sub_id = feature_idx; + + if (hidpp20_request_command(device, &msg)) { + return -ENOTSUP; + } + + return msg.msg.parameters[0]; +} + +int hidpp20_led_sw_control_get_led_info(struct hidpp20_device* device, + uint8_t led_idx, + struct hidpp20_led_sw_ctrl_led_info *info) +{ + union hidpp20_message msg = { + .msg = { + .report_id = REPORT_ID_LONG, + .address = CMD_LED_SW_CONTROL_GET_LED_INFO, + .device_idx = device->index, + }, + }; + struct hidpp20_led_sw_ctrl_led_info *params; + uint8_t feature_idx; + + feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); + if (feature_idx == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_idx; + msg.msg.parameters[0] = led_idx; + + if (hidpp20_request_command(device, &msg)) + // Only error possible is an invalid index, which means the led doesn't exist + return -ENOENT; + + params = (struct hidpp20_led_sw_ctrl_led_info*) msg.msg.parameters; + params->type = hidpp_be_u16_to_cpu(params->type); + params->caps = hidpp_be_u16_to_cpu(params->caps); + + *info = *params; + + return 0; +} + +bool hidpp20_led_sw_control_get_sw_ctrl(struct hidpp20_device* device) +{ + uint8_t feature_idx; + int rc; + union hidpp20_message msg = { + .msg = { + .report_id = REPORT_ID_SHORT, + .address = CMD_LED_SW_CONTROL_GET_SW_CTRL, + .device_idx = device->index, + }, + }; + + feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); + + if (feature_idx == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_idx; + + rc = hidpp20_request_command(device, &msg); + + if (rc) { + return -ENOTSUP; + } + + return msg.msg.parameters[0]; +} + +int hidpp20_led_sw_control_set_sw_ctrl(struct hidpp20_device* device, bool ctrl) +{ + uint8_t feature_idx; + union hidpp20_message msg = { + .msg = { + .report_id = REPORT_ID_SHORT, + .address = CMD_LED_SW_CONTROL_SET_SW_CTRL, + .device_idx = device->index, + }, + }; + + feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); + + if (feature_idx == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_idx; + msg.msg.parameters[0] = ctrl; + + if (hidpp20_request_command(device, &msg)) + return -EINVAL; + + return 0; +} + +int hidpp20_led_sw_control_get_led_state(struct hidpp20_device* device, + uint8_t led_idx, + struct hidpp20_led_sw_ctrl_led_state *out) +{ + int rc; + uint8_t feature_idx; + union hidpp20_message msg = { + .msg = { + .report_id = REPORT_ID_LONG, + .address = CMD_LED_SW_CONTROL_GET_LED_STATE, + .device_idx = device->index, + } + }; + struct hidpp20_led_sw_ctrl_led_state *state; + + feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); + + if (feature_idx == 0) { + return -ENOTSUP; + } + + msg.msg.sub_id = feature_idx; + + msg.msg.parameters[0] = led_idx; + + rc = hidpp20_request_command(device, &msg); + + if (rc) + return -ENOENT; + + state = (struct hidpp20_led_sw_ctrl_led_state*) msg.msg.parameters; + + // This field has to be stored in little-endian + state->mode = state->mode; + + if (state->mode == HIDPP20_LED_MODE_BREATHING) { + // Only parameters that is reported by these LEDs is brightness when breathing + state->breathing.brightness = hidpp_be_u16_to_cpu(state->breathing.brightness); + } + + *out = *state; + + return 0; +} + +int hidpp20_led_sw_control_set_led_state(struct hidpp20_device* device, + const struct hidpp20_led_sw_ctrl_led_state *state) +{ + int rc; + uint8_t feature_idx; + union hidpp20_message msg = { + .msg = { + .report_id = REPORT_ID_LONG, + .address = CMD_LED_SW_CONTROL_SET_LED_STATE, + .device_idx = device->index, + } + }; + + feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); + + if (feature_idx == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_idx; + + if (!hidpp20_led_sw_control_check_state(state->mode)) + return -EINVAL; + + msg.msg.parameters[0] = state->index; + hidpp_set_unaligned_be_u16(&msg.msg.parameters[1], state->mode); + hidpp_set_unaligned_be_u16(&msg.msg.parameters[3], state->blink.index); + hidpp_set_unaligned_be_u16(&msg.msg.parameters[5], state->blink.on_time); + hidpp_set_unaligned_be_u16(&msg.msg.parameters[7], state->blink.off_time); + + rc = hidpp20_request_command(device, &msg); + + if (rc) + return -EINVAL; + + return 0; +} + + /* -------------------------------------------------------------------------- */ /* 0x1b00: KBD reprogrammable keys and mouse buttons */ /* -------------------------------------------------------------------------- */ diff --git a/src/hidpp20.h b/src/hidpp20.h index 8f2ae794..d2e9134b 100644 --- a/src/hidpp20.h +++ b/src/hidpp20.h @@ -126,6 +126,130 @@ int hidpp20_batterylevel_get_battery_level(struct hidpp20_device *device, uint16_t *level, uint16_t *next_level); +/* -------------------------------------------------------------------------- */ +/* 0x1300: LED software control */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_LED_SW_CONTROL 0x1300 + +enum hidpp20_led_sw_ctrl_led_type { + HIDPP20_LED_TYPE_BATTERY = 0x01, + HIDPP20_LED_TYPE_DPI, + HIDPP20_LED_TYPE_PROFILE, + HIDPP20_LED_TYPE_LOGO, + HIDPP20_LED_TYPE_COSMETIC +}; + +enum hidpp20_led_sw_ctrl_led_mode { + HIDPP20_LED_MODE_OFF = 0x01, + HIDPP20_LED_MODE_ON = 0x02, + HIDPP20_LED_MODE_BLINK = 0x04, + HIDPP20_LED_MODE_TRAVEL = 0x08, + HIDPP20_LED_MODE_RAMP_UP = 0x10, + HIDPP20_LED_MODE_RAMP_DOWN = 0x20, + HIDPP20_LED_MODE_HEARTBEAT = 0x40, + HIDPP20_LED_MODE_BREATHING = 0x80 +}; + +#define HIDPP20_LED_SW_CONTROL_LED_INDEX_ALL 0xff + +struct hidpp20_led_sw_ctrl_led_info { + uint8_t index; + uint8_t type; + uint8_t physical_count; + uint16_t caps; + uint8_t nvconfig_caps; +} __attribute__((packed)); +_Static_assert(sizeof(struct hidpp20_led_sw_ctrl_led_info) == 6, "Invalid size"); + +struct hidpp20_led_sw_ctrl_led_state { + uint8_t index; + uint16_t mode; + union { + struct { + uint16_t brightness; + uint16_t period; + uint16_t timeout; + } breathing; + struct { + uint16_t unused; + uint16_t delay; + } traveling; + struct { + uint16_t index; + uint16_t on_time; + uint16_t off_time; + } blink; + struct { + /* Logical information to display on the LED + * Meaning and value range depend on the LED + */ + uint16_t index; + } on; + }; +} __attribute__((packed)); +_Static_assert(sizeof(struct hidpp20_led_sw_ctrl_led_state) == 9, "Invalid size"); + +int hidpp20_led_sw_control_read_leds(struct hidpp20_device* device, + struct hidpp20_led_sw_ctrl_led_info** info_list); + +const char* +hidpp20_sw_led_control_get_mode_string(const enum hidpp20_led_sw_ctrl_led_mode mode); + +int hidpp20_led_sw_control_read_leds(struct hidpp20_device* device, + struct hidpp20_led_sw_ctrl_led_info** info_list); + +/** + * Retrieves the number of non-RGB logical leds from the mouse + * @return the led count or a negative errno + */ +int hidpp20_led_sw_control_get_led_count(struct hidpp20_device* device); + +/** + * Retrieves the info for a given logical led + * @return 0 or a negative errno in case of error + */ +int hidpp20_led_sw_control_get_led_info(struct hidpp20_device* device, + uint8_t led_idx, + struct hidpp20_led_sw_ctrl_led_info* info); + +/** + * Reads all LEDs on the device for their current state + * @return 0 on success or a negative errno + */ +int hidpp20_led_sw_control_read_leds(struct hidpp20_device* device, + struct hidpp20_led_sw_ctrl_led_info** info_list); + +/** + * Get the current sw control value + * @return 0 if the leds are sofware-controlled 1 if they are firmware controlled + */ +bool hidpp20_led_sw_control_get_sw_ctrl(struct hidpp20_device* device); + +/** + * Sets who controls the LEDs + * ctrl = 0, the software controls the LEDs + * ctrl = 1, the firmware controls the LEDs + * @return 0 on success or a negative errno + */ +int hidpp20_led_sw_control_set_sw_ctrl(struct hidpp20_device* device, bool ctrl); + +/** + * Gets the state of a LED + * @return 0 on success or a negative errno + */ +int hidpp20_led_sw_control_get_led_state(struct hidpp20_device* device, + uint8_t led_idx, + struct hidpp20_led_sw_ctrl_led_state* state); + +/** + * Sets the state of a LED + * The LED index should be passed in the state structure + * @return 0 on success or a negative errno + */ +int hidpp20_led_sw_control_set_led_state(struct hidpp20_device* device, + const struct hidpp20_led_sw_ctrl_led_state* state); + /* -------------------------------------------------------------------------- */ /* 0x1b00: KBD reprogrammable keys and mouse buttons */ /* -------------------------------------------------------------------------- */ @@ -258,7 +382,6 @@ int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device, #define HIDPP_PAGE_COLOR_LED_EFFECTS 0x8070 - struct hidpp20_color_led_info; struct hidpp20_color_led_zone_info; @@ -362,6 +485,14 @@ struct hidpp20_color_led_info { } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_info) == 5, "Invalid size"); +struct hidpp20_non_color_led_zone_info { + uint8_t index; + uint8_t type; + uint16_t capabilities; + uint8_t nv_config; +} __attribute__((packed)); +_Static_assert(sizeof(struct hidpp20_non_color_led_zone_info) == 5, "Invalid size"); + struct hidpp20_color_led_zone_info { uint8_t index; uint16_t location; @@ -370,6 +501,11 @@ struct hidpp20_color_led_zone_info { } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_zone_info) == 5, "Invalid size"); +union hidpp20_generic_led_zone_info { + struct hidpp20_led_sw_ctrl_led_info* leds; + struct hidpp20_color_led_zone_info* color_leds; +}; + struct hidpp20_color { uint8_t red; uint8_t green; @@ -610,5 +746,3 @@ hidpp20_onboard_profiles_allocate_sector(struct hidpp20_profiles *profiles) /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_MOUSE_BUTTON_SPY 0x8110 - -