Skip to content

Commit e037acf

Browse files
FFY00Jiri Kosina
authored andcommitted
HID: logitech-hidpp: add support for Unified Battery (1004) feature
This new feature present in new devices replaces the old Battery Level Status (0x1000) feature. It keeps essentially the same information for levels (reporting critical, low, good and full) but makes these levels optional, the device exports a capability setting which describes which levels it supports. In addition to this, there is an optional state_of_charge paramenter that exports the battery percentage. This patch adds support for this new feature. There were some implementation choices, as described below and in the code. If the device supports the state_of_charge parameter, we will just export the battery percentage and not the levels, which the device might still support. Since this feature can co-exist with the Battery Voltage (0x1001) feature and we currently only support one battery feature, I changed the battery feature discovery to try to use 0x1000 and 0x1004 first and only then 0x1001, the battery voltage feature. In the future we could uncouple this and make the battery feature co-exists with 0x1000 and 0x1004, allowing the device to export voltage information in addition to the battery percentage or level. I tested this patch with a MX Anywhere 3, which supports the new feature. Since I don't have any device that doesn't support the state_of_charge parameter of this feature, I forced the MX Anywhere 3 to use the level information, instead of battery percentage, to test that part of the implementation. I also tested with a MX Master 3, which supports the Battery Level Status (0x1000) feature, and a G703 Hero, which supports the Battery Voltage (0x1001) feature, to make sure nothing broke there. [jkosina@suse.cz: fix comment] Signed-off-by: Filipe Laíns <lains@archlinux.org> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
1 parent 2bbe17a commit e037acf

1 file changed

Lines changed: 239 additions & 7 deletions

File tree

drivers/hid/hid-logitech-hidpp.c

Lines changed: 239 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ MODULE_PARM_DESC(disable_tap_to_click,
9292
#define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2)
9393
#define HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS BIT(3)
9494
#define HIDPP_CAPABILITY_BATTERY_VOLTAGE BIT(4)
95+
#define HIDPP_CAPABILITY_BATTERY_PERCENTAGE BIT(5)
96+
#define HIDPP_CAPABILITY_UNIFIED_BATTERY BIT(6)
9597

9698
#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
9799

@@ -152,6 +154,7 @@ struct hidpp_battery {
152154
int voltage;
153155
int charge_type;
154156
bool online;
157+
u8 supported_levels_1004;
155158
};
156159

157160
/**
@@ -1171,7 +1174,7 @@ static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp,
11711174
return 0;
11721175
}
11731176

1174-
static int hidpp20_query_battery_info(struct hidpp_device *hidpp)
1177+
static int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp)
11751178
{
11761179
u8 feature_type;
11771180
int ret;
@@ -1208,7 +1211,7 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp)
12081211
return 0;
12091212
}
12101213

1211-
static int hidpp20_battery_event(struct hidpp_device *hidpp,
1214+
static int hidpp20_battery_event_1000(struct hidpp_device *hidpp,
12121215
u8 *data, int size)
12131216
{
12141217
struct hidpp_report *report = (struct hidpp_report *)data;
@@ -1380,6 +1383,224 @@ static int hidpp20_battery_voltage_event(struct hidpp_device *hidpp,
13801383
return 0;
13811384
}
13821385

1386+
/* -------------------------------------------------------------------------- */
1387+
/* 0x1004: Unified battery */
1388+
/* -------------------------------------------------------------------------- */
1389+
1390+
#define HIDPP_PAGE_UNIFIED_BATTERY 0x1004
1391+
1392+
#define CMD_UNIFIED_BATTERY_GET_CAPABILITIES 0x00
1393+
#define CMD_UNIFIED_BATTERY_GET_STATUS 0x10
1394+
1395+
#define EVENT_UNIFIED_BATTERY_STATUS_EVENT 0x00
1396+
1397+
#define FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL BIT(0)
1398+
#define FLAG_UNIFIED_BATTERY_LEVEL_LOW BIT(1)
1399+
#define FLAG_UNIFIED_BATTERY_LEVEL_GOOD BIT(2)
1400+
#define FLAG_UNIFIED_BATTERY_LEVEL_FULL BIT(3)
1401+
1402+
#define FLAG_UNIFIED_BATTERY_FLAGS_RECHARGEABLE BIT(0)
1403+
#define FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE BIT(1)
1404+
1405+
static int hidpp20_unifiedbattery_get_capabilities(struct hidpp_device *hidpp,
1406+
u8 feature_index)
1407+
{
1408+
struct hidpp_report response;
1409+
int ret;
1410+
u8 *params = (u8 *)response.fap.params;
1411+
1412+
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS ||
1413+
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) {
1414+
/* we have already set the device capabilities, so let's skip */
1415+
return 0;
1416+
}
1417+
1418+
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
1419+
CMD_UNIFIED_BATTERY_GET_CAPABILITIES,
1420+
NULL, 0, &response);
1421+
/* Ignore these intermittent errors */
1422+
if (ret == HIDPP_ERROR_RESOURCE_ERROR)
1423+
return -EIO;
1424+
if (ret > 0) {
1425+
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
1426+
__func__, ret);
1427+
return -EPROTO;
1428+
}
1429+
if (ret)
1430+
return ret;
1431+
1432+
/*
1433+
* If the device supports state of charge (battery percentage) we won't
1434+
* export the battery level information. there are 4 possible battery
1435+
* levels and they all are optional, this means that the device might
1436+
* not support any of them, we are just better off with the battery
1437+
* percentage.
1438+
*/
1439+
if (params[1] & FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE) {
1440+
hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_PERCENTAGE;
1441+
hidpp->battery.supported_levels_1004 = 0;
1442+
} else {
1443+
hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
1444+
hidpp->battery.supported_levels_1004 = params[0];
1445+
}
1446+
1447+
return 0;
1448+
}
1449+
1450+
static int hidpp20_unifiedbattery_map_status(struct hidpp_device *hidpp,
1451+
u8 charging_status,
1452+
u8 external_power_status)
1453+
{
1454+
int status;
1455+
1456+
switch (charging_status) {
1457+
case 0: /* discharging */
1458+
status = POWER_SUPPLY_STATUS_DISCHARGING;
1459+
break;
1460+
case 1: /* charging */
1461+
case 2: /* charging slow */
1462+
status = POWER_SUPPLY_STATUS_CHARGING;
1463+
break;
1464+
case 3: /* complete */
1465+
status = POWER_SUPPLY_STATUS_FULL;
1466+
break;
1467+
case 4: /* error */
1468+
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
1469+
hid_info(hidpp->hid_dev, "%s: charging error",
1470+
hidpp->name);
1471+
break;
1472+
default:
1473+
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
1474+
break;
1475+
}
1476+
1477+
return status;
1478+
}
1479+
1480+
static int hidpp20_unifiedbattery_map_level(struct hidpp_device *hidpp,
1481+
u8 battery_level)
1482+
{
1483+
/* cler unsupported level bits */
1484+
battery_level &= hidpp->battery.supported_levels_1004;
1485+
1486+
if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_FULL)
1487+
return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
1488+
else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_GOOD)
1489+
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
1490+
else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_LOW)
1491+
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
1492+
else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL)
1493+
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
1494+
1495+
return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
1496+
}
1497+
1498+
static int hidpp20_unifiedbattery_get_status(struct hidpp_device *hidpp,
1499+
u8 feature_index,
1500+
u8 *state_of_charge,
1501+
int *status,
1502+
int *level)
1503+
{
1504+
struct hidpp_report response;
1505+
int ret;
1506+
u8 *params = (u8 *)response.fap.params;
1507+
1508+
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
1509+
CMD_UNIFIED_BATTERY_GET_STATUS,
1510+
NULL, 0, &response);
1511+
/* Ignore these intermittent errors */
1512+
if (ret == HIDPP_ERROR_RESOURCE_ERROR)
1513+
return -EIO;
1514+
if (ret > 0) {
1515+
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
1516+
__func__, ret);
1517+
return -EPROTO;
1518+
}
1519+
if (ret)
1520+
return ret;
1521+
1522+
*state_of_charge = params[0];
1523+
*status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]);
1524+
*level = hidpp20_unifiedbattery_map_level(hidpp, params[1]);
1525+
1526+
return 0;
1527+
}
1528+
1529+
static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
1530+
{
1531+
u8 feature_type;
1532+
int ret;
1533+
u8 state_of_charge;
1534+
int status, level;
1535+
1536+
if (hidpp->battery.feature_index == 0xff) {
1537+
ret = hidpp_root_get_feature(hidpp,
1538+
HIDPP_PAGE_UNIFIED_BATTERY,
1539+
&hidpp->battery.feature_index,
1540+
&feature_type);
1541+
if (ret)
1542+
return ret;
1543+
}
1544+
1545+
ret = hidpp20_unifiedbattery_get_capabilities(hidpp,
1546+
hidpp->battery.feature_index);
1547+
if (ret)
1548+
return ret;
1549+
1550+
ret = hidpp20_unifiedbattery_get_status(hidpp,
1551+
hidpp->battery.feature_index,
1552+
&state_of_charge,
1553+
&status,
1554+
&level);
1555+
if (ret)
1556+
return ret;
1557+
1558+
hidpp->capabilities |= HIDPP_CAPABILITY_UNIFIED_BATTERY;
1559+
hidpp->battery.capacity = state_of_charge;
1560+
hidpp->battery.status = status;
1561+
hidpp->battery.level = level;
1562+
hidpp->battery.online = true;
1563+
1564+
return 0;
1565+
}
1566+
1567+
static int hidpp20_battery_event_1004(struct hidpp_device *hidpp,
1568+
u8 *data, int size)
1569+
{
1570+
struct hidpp_report *report = (struct hidpp_report *)data;
1571+
u8 *params = (u8 *)report->fap.params;
1572+
int state_of_charge, status, level;
1573+
bool changed;
1574+
1575+
if (report->fap.feature_index != hidpp->battery.feature_index ||
1576+
report->fap.funcindex_clientid != EVENT_UNIFIED_BATTERY_STATUS_EVENT)
1577+
return 0;
1578+
1579+
state_of_charge = params[0];
1580+
status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]);
1581+
level = hidpp20_unifiedbattery_map_level(hidpp, params[1]);
1582+
1583+
changed = status != hidpp->battery.status ||
1584+
(state_of_charge != hidpp->battery.capacity &&
1585+
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) ||
1586+
(level != hidpp->battery.level &&
1587+
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS);
1588+
1589+
if (changed) {
1590+
hidpp->battery.capacity = state_of_charge;
1591+
hidpp->battery.status = status;
1592+
hidpp->battery.level = level;
1593+
if (hidpp->battery.ps)
1594+
power_supply_changed(hidpp->battery.ps);
1595+
}
1596+
1597+
return 0;
1598+
}
1599+
1600+
/* -------------------------------------------------------------------------- */
1601+
/* Battery feature helpers */
1602+
/* -------------------------------------------------------------------------- */
1603+
13831604
static enum power_supply_property hidpp_battery_props[] = {
13841605
POWER_SUPPLY_PROP_ONLINE,
13851606
POWER_SUPPLY_PROP_STATUS,
@@ -3307,7 +3528,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
33073528
}
33083529

33093530
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
3310-
ret = hidpp20_battery_event(hidpp, data, size);
3531+
ret = hidpp20_battery_event_1000(hidpp, data, size);
3532+
if (ret != 0)
3533+
return ret;
3534+
ret = hidpp20_battery_event_1004(hidpp, data, size);
33113535
if (ret != 0)
33123536
return ret;
33133537
ret = hidpp_solar_battery_event(hidpp, data, size);
@@ -3443,9 +3667,14 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
34433667
if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
34443668
ret = hidpp_solar_request_battery_event(hidpp);
34453669
else {
3446-
ret = hidpp20_query_battery_voltage_info(hidpp);
3670+
/* we only support one battery feature right now, so let's
3671+
first check the ones that support battery level first
3672+
and leave voltage for last */
3673+
ret = hidpp20_query_battery_info_1000(hidpp);
3674+
if (ret)
3675+
ret = hidpp20_query_battery_info_1004(hidpp);
34473676
if (ret)
3448-
ret = hidpp20_query_battery_info(hidpp);
3677+
ret = hidpp20_query_battery_voltage_info(hidpp);
34493678
}
34503679

34513680
if (ret)
@@ -3473,7 +3702,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
34733702

34743703
num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 3;
34753704

3476-
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE)
3705+
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE ||
3706+
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE)
34773707
battery_props[num_battery_props++] =
34783708
POWER_SUPPLY_PROP_CAPACITY;
34793709

@@ -3650,8 +3880,10 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
36503880
} else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
36513881
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
36523882
hidpp20_query_battery_voltage_info(hidpp);
3883+
else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY)
3884+
hidpp20_query_battery_info_1004(hidpp);
36533885
else
3654-
hidpp20_query_battery_info(hidpp);
3886+
hidpp20_query_battery_info_1000(hidpp);
36553887
}
36563888
if (hidpp->battery.ps)
36573889
power_supply_changed(hidpp->battery.ps);

0 commit comments

Comments
 (0)