hidpp20: add support for non-RGB LEDs, feature 0x1300
MODIFY
src/driver-hidpp20.c
src/driver-hidpp20.c
#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)

MODIFY
src/hidpp20.c
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 */
/* -------------------------------------------------------------------------- */

MODIFY
src/hidpp20.h
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
-
-

hidpp20: add support for non-RGB LEDs, feature 0x1300

Signed-off-by: Ogier Bouvier <obouvier@logitech.com>