diff options
-rw-r--r-- | drivers/hid/hid-lg-g15.c | 2 | ||||
-rw-r--r-- | drivers/hid/hid-logitech-hidpp.c | 246 |
2 files changed, 240 insertions, 8 deletions
diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index fcaf8466e627..bfbba0d41933 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -647,7 +647,7 @@ static void lg_g15_input_close(struct input_dev *dev) static int lg_g15_register_led(struct lg_g15_data *g15, int i) { - const char * const led_names[] = { + static const char * const led_names[] = { "g15::kbd_backlight", "g15::lcd_backlight", "g15::macro_preset1", diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 7eb9a6ddb46a..d459e2dbe647 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -92,6 +92,8 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2) #define HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS BIT(3) #define HIDPP_CAPABILITY_BATTERY_VOLTAGE BIT(4) +#define HIDPP_CAPABILITY_BATTERY_PERCENTAGE BIT(5) +#define HIDPP_CAPABILITY_UNIFIED_BATTERY BIT(6) #define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) @@ -152,6 +154,7 @@ struct hidpp_battery { int voltage; int charge_type; bool online; + u8 supported_levels_1004; }; /** @@ -1171,7 +1174,7 @@ static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp, return 0; } -static int hidpp20_query_battery_info(struct hidpp_device *hidpp) +static int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp) { u8 feature_type; int ret; @@ -1208,7 +1211,7 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp) return 0; } -static int hidpp20_battery_event(struct hidpp_device *hidpp, +static int hidpp20_battery_event_1000(struct hidpp_device *hidpp, u8 *data, int size) { struct hidpp_report *report = (struct hidpp_report *)data; @@ -1380,6 +1383,224 @@ static int hidpp20_battery_voltage_event(struct hidpp_device *hidpp, return 0; } +/* -------------------------------------------------------------------------- */ +/* 0x1004: Unified battery */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_UNIFIED_BATTERY 0x1004 + +#define CMD_UNIFIED_BATTERY_GET_CAPABILITIES 0x00 +#define CMD_UNIFIED_BATTERY_GET_STATUS 0x10 + +#define EVENT_UNIFIED_BATTERY_STATUS_EVENT 0x00 + +#define FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL BIT(0) +#define FLAG_UNIFIED_BATTERY_LEVEL_LOW BIT(1) +#define FLAG_UNIFIED_BATTERY_LEVEL_GOOD BIT(2) +#define FLAG_UNIFIED_BATTERY_LEVEL_FULL BIT(3) + +#define FLAG_UNIFIED_BATTERY_FLAGS_RECHARGEABLE BIT(0) +#define FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE BIT(1) + +static int hidpp20_unifiedbattery_get_capabilities(struct hidpp_device *hidpp, + u8 feature_index) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS || + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) { + /* we have already set the device capabilities, so let's skip */ + return 0; + } + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_UNIFIED_BATTERY_GET_CAPABILITIES, + NULL, 0, &response); + /* Ignore these intermittent errors */ + if (ret == HIDPP_ERROR_RESOURCE_ERROR) + return -EIO; + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } + if (ret) + return ret; + + /* + * If the device supports state of charge (battery percentage) we won't + * export the battery level information. there are 4 possible battery + * levels and they all are optional, this means that the device might + * not support any of them, we are just better off with the battery + * percentage. + */ + if (params[1] & FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE) { + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_PERCENTAGE; + hidpp->battery.supported_levels_1004 = 0; + } else { + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS; + hidpp->battery.supported_levels_1004 = params[0]; + } + + return 0; +} + +static int hidpp20_unifiedbattery_map_status(struct hidpp_device *hidpp, + u8 charging_status, + u8 external_power_status) +{ + int status; + + switch (charging_status) { + case 0: /* discharging */ + status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 1: /* charging */ + case 2: /* charging slow */ + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 3: /* complete */ + status = POWER_SUPPLY_STATUS_FULL; + break; + case 4: /* error */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + hid_info(hidpp->hid_dev, "%s: charging error", + hidpp->name); + break; + default: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + return status; +} + +static int hidpp20_unifiedbattery_map_level(struct hidpp_device *hidpp, + u8 battery_level) +{ + /* cler unsupported level bits */ + battery_level &= hidpp->battery.supported_levels_1004; + + if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_FULL) + return POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_GOOD) + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_LOW) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + + return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; +} + +static int hidpp20_unifiedbattery_get_status(struct hidpp_device *hidpp, + u8 feature_index, + u8 *state_of_charge, + int *status, + int *level) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_UNIFIED_BATTERY_GET_STATUS, + NULL, 0, &response); + /* Ignore these intermittent errors */ + if (ret == HIDPP_ERROR_RESOURCE_ERROR) + return -EIO; + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } + if (ret) + return ret; + + *state_of_charge = params[0]; + *status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]); + *level = hidpp20_unifiedbattery_map_level(hidpp, params[1]); + + return 0; +} + +static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp) +{ + u8 feature_type; + int ret; + u8 state_of_charge; + int status, level; + + if (hidpp->battery.feature_index == 0xff) { + ret = hidpp_root_get_feature(hidpp, + HIDPP_PAGE_UNIFIED_BATTERY, + &hidpp->battery.feature_index, + &feature_type); + if (ret) + return ret; + } + + ret = hidpp20_unifiedbattery_get_capabilities(hidpp, + hidpp->battery.feature_index); + if (ret) + return ret; + + ret = hidpp20_unifiedbattery_get_status(hidpp, + hidpp->battery.feature_index, + &state_of_charge, + &status, + &level); + if (ret) + return ret; + + hidpp->capabilities |= HIDPP_CAPABILITY_UNIFIED_BATTERY; + hidpp->battery.capacity = state_of_charge; + hidpp->battery.status = status; + hidpp->battery.level = level; + hidpp->battery.online = true; + + return 0; +} + +static int hidpp20_battery_event_1004(struct hidpp_device *hidpp, + u8 *data, int size) +{ + struct hidpp_report *report = (struct hidpp_report *)data; + u8 *params = (u8 *)report->fap.params; + int state_of_charge, status, level; + bool changed; + + if (report->fap.feature_index != hidpp->battery.feature_index || + report->fap.funcindex_clientid != EVENT_UNIFIED_BATTERY_STATUS_EVENT) + return 0; + + state_of_charge = params[0]; + status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]); + level = hidpp20_unifiedbattery_map_level(hidpp, params[1]); + + changed = status != hidpp->battery.status || + (state_of_charge != hidpp->battery.capacity && + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) || + (level != hidpp->battery.level && + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS); + + if (changed) { + hidpp->battery.capacity = state_of_charge; + hidpp->battery.status = status; + hidpp->battery.level = level; + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* Battery feature helpers */ +/* -------------------------------------------------------------------------- */ + static enum power_supply_property hidpp_battery_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, @@ -3307,7 +3528,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, } if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { - ret = hidpp20_battery_event(hidpp, data, size); + ret = hidpp20_battery_event_1000(hidpp, data, size); + if (ret != 0) + return ret; + ret = hidpp20_battery_event_1004(hidpp, data, size); if (ret != 0) return ret; ret = hidpp_solar_battery_event(hidpp, data, size); @@ -3443,9 +3667,14 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750) ret = hidpp_solar_request_battery_event(hidpp); else { - ret = hidpp20_query_battery_voltage_info(hidpp); + /* we only support one battery feature right now, so let's + first check the ones that support battery level first + and leave voltage for last */ + ret = hidpp20_query_battery_info_1000(hidpp); + if (ret) + ret = hidpp20_query_battery_info_1004(hidpp); if (ret) - ret = hidpp20_query_battery_info(hidpp); + ret = hidpp20_query_battery_voltage_info(hidpp); } if (ret) @@ -3473,7 +3702,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 3; - if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE) + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE || + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) battery_props[num_battery_props++] = POWER_SUPPLY_PROP_CAPACITY; @@ -3650,8 +3880,10 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) } else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE) hidpp20_query_battery_voltage_info(hidpp); + else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY) + hidpp20_query_battery_info_1004(hidpp); else - hidpp20_query_battery_info(hidpp); + hidpp20_query_battery_info_1000(hidpp); } if (hidpp->battery.ps) power_supply_changed(hidpp->battery.ps); |