Skip to content

Commit be28136

Browse files
pedrovanzellabentiss
authored andcommitted
hid-logitech-hidpp: read battery voltage from newer devices
Newer Logitech mice report their battery voltage through feature 0x1001 instead of the battery levels through feature 0x1000. When the device is brought up and we try to query the battery, figure out if it supports the old or the new feature. If it supports the new feature, record the feature index and read the battery voltage and its charge status. The device will respond with three bytes: the first two are its voltage, and the last one is a bitset, reporting if the battery is charging, fast or slow, in critical level or discharging. If everything went correctly, record the fact that we're capable of querying battery voltage. Note that the protocol only gives us the current voltage in mV. We store that as-is, but when queried, we report it in uV as expected by sysfs. Like we do for other ways of interacting with the battery for other devices, refresh the battery status and notify the power supply subsystem of the changes in voltage and status. Since there are no known devices which implement both the old and the new battery feature, we make sure to try the newer feature first and only fall back to the old one if that fails. Signed-off-by: Pedro Vanzella <[email protected]> Signed-off-by: Benjamin Tissoires <[email protected]>
1 parent 04bd681 commit be28136

File tree

1 file changed

+168
-4
lines changed

1 file changed

+168
-4
lines changed

drivers/hid/hid-logitech-hidpp.c

Lines changed: 168 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
9191
#define HIDPP_CAPABILITY_HIDPP20_BATTERY BIT(1)
9292
#define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2)
9393
#define HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS BIT(3)
94+
#define HIDPP_CAPABILITY_BATTERY_VOLTAGE BIT(4)
9495

9596
/*
9697
* There are two hidpp protocols in use, the first version hidpp10 is known
@@ -139,12 +140,15 @@ struct hidpp_report {
139140
struct hidpp_battery {
140141
u8 feature_index;
141142
u8 solar_feature_index;
143+
u8 voltage_feature_index;
142144
struct power_supply_desc desc;
143145
struct power_supply *ps;
144146
char name[64];
145147
int status;
146148
int capacity;
147149
int level;
150+
int voltage;
151+
int charge_type;
148152
bool online;
149153
};
150154

@@ -1237,6 +1241,144 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp,
12371241
return 0;
12381242
}
12391243

1244+
/* -------------------------------------------------------------------------- */
1245+
/* 0x1001: Battery voltage */
1246+
/* -------------------------------------------------------------------------- */
1247+
1248+
#define HIDPP_PAGE_BATTERY_VOLTAGE 0x1001
1249+
1250+
#define CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE 0x00
1251+
1252+
#define EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST 0x00
1253+
1254+
static int hidpp20_battery_map_status_voltage(u8 data[3], int *voltage,
1255+
int *level, int *charge_type)
1256+
{
1257+
int status;
1258+
1259+
long charge_sts = (long)data[2];
1260+
1261+
*level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
1262+
switch (data[2] & 0xe0) {
1263+
case 0x00:
1264+
status = POWER_SUPPLY_STATUS_CHARGING;
1265+
break;
1266+
case 0x20:
1267+
status = POWER_SUPPLY_STATUS_FULL;
1268+
*level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
1269+
break;
1270+
case 0x40:
1271+
status = POWER_SUPPLY_STATUS_DISCHARGING;
1272+
break;
1273+
case 0xe0:
1274+
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
1275+
break;
1276+
default:
1277+
status = POWER_SUPPLY_STATUS_UNKNOWN;
1278+
}
1279+
1280+
*charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
1281+
if (test_bit(3, &charge_sts)) {
1282+
*charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
1283+
}
1284+
if (test_bit(4, &charge_sts)) {
1285+
*charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
1286+
}
1287+
1288+
if (test_bit(5, &charge_sts)) {
1289+
*level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
1290+
}
1291+
1292+
*voltage = get_unaligned_be16(data);
1293+
1294+
return status;
1295+
}
1296+
1297+
static int hidpp20_battery_get_battery_voltage(struct hidpp_device *hidpp,
1298+
u8 feature_index,
1299+
int *status, int *voltage,
1300+
int *level, int *charge_type)
1301+
{
1302+
struct hidpp_report response;
1303+
int ret;
1304+
u8 *params = (u8 *)response.fap.params;
1305+
1306+
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
1307+
CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE,
1308+
NULL, 0, &response);
1309+
1310+
if (ret > 0) {
1311+
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
1312+
__func__, ret);
1313+
return -EPROTO;
1314+
}
1315+
if (ret)
1316+
return ret;
1317+
1318+
hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_VOLTAGE;
1319+
1320+
*status = hidpp20_battery_map_status_voltage(params, voltage,
1321+
level, charge_type);
1322+
1323+
return 0;
1324+
}
1325+
1326+
static int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp)
1327+
{
1328+
u8 feature_type;
1329+
int ret;
1330+
int status, voltage, level, charge_type;
1331+
1332+
if (hidpp->battery.voltage_feature_index == 0xff) {
1333+
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
1334+
&hidpp->battery.voltage_feature_index,
1335+
&feature_type);
1336+
if (ret)
1337+
return ret;
1338+
}
1339+
1340+
ret = hidpp20_battery_get_battery_voltage(hidpp,
1341+
hidpp->battery.voltage_feature_index,
1342+
&status, &voltage, &level, &charge_type);
1343+
1344+
if (ret)
1345+
return ret;
1346+
1347+
hidpp->battery.status = status;
1348+
hidpp->battery.voltage = voltage;
1349+
hidpp->battery.level = level;
1350+
hidpp->battery.charge_type = charge_type;
1351+
hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING;
1352+
1353+
return 0;
1354+
}
1355+
1356+
static int hidpp20_battery_voltage_event(struct hidpp_device *hidpp,
1357+
u8 *data, int size)
1358+
{
1359+
struct hidpp_report *report = (struct hidpp_report *)data;
1360+
int status, voltage, level, charge_type;
1361+
1362+
if (report->fap.feature_index != hidpp->battery.voltage_feature_index ||
1363+
report->fap.funcindex_clientid != EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST)
1364+
return 0;
1365+
1366+
status = hidpp20_battery_map_status_voltage(report->fap.params, &voltage,
1367+
&level, &charge_type);
1368+
1369+
hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING;
1370+
1371+
if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) {
1372+
hidpp->battery.voltage = voltage;
1373+
hidpp->battery.status = status;
1374+
hidpp->battery.level = level;
1375+
hidpp->battery.charge_type = charge_type;
1376+
if (hidpp->battery.ps)
1377+
power_supply_changed(hidpp->battery.ps);
1378+
}
1379+
return 0;
1380+
}
1381+
12401382
static enum power_supply_property hidpp_battery_props[] = {
12411383
POWER_SUPPLY_PROP_ONLINE,
12421384
POWER_SUPPLY_PROP_STATUS,
@@ -1246,6 +1388,7 @@ static enum power_supply_property hidpp_battery_props[] = {
12461388
POWER_SUPPLY_PROP_SERIAL_NUMBER,
12471389
0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY, */
12481390
0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY_LEVEL, */
1391+
0, /* placeholder for POWER_SUPPLY_PROP_VOLTAGE_NOW, */
12491392
};
12501393

12511394
static int hidpp_battery_get_property(struct power_supply *psy,
@@ -1283,6 +1426,13 @@ static int hidpp_battery_get_property(struct power_supply *psy,
12831426
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
12841427
val->strval = hidpp->hid_dev->uniq;
12851428
break;
1429+
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
1430+
/* hardware reports voltage in in mV. sysfs expects uV */
1431+
val->intval = hidpp->battery.voltage * 1000;
1432+
break;
1433+
case POWER_SUPPLY_PROP_CHARGE_TYPE:
1434+
val->intval = hidpp->battery.charge_type;
1435+
break;
12861436
default:
12871437
ret = -EINVAL;
12881438
break;
@@ -3139,6 +3289,9 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
31393289
ret = hidpp_solar_battery_event(hidpp, data, size);
31403290
if (ret != 0)
31413291
return ret;
3292+
ret = hidpp20_battery_voltage_event(hidpp, data, size);
3293+
if (ret != 0)
3294+
return ret;
31423295
}
31433296

31443297
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
@@ -3260,12 +3413,16 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
32603413

32613414
hidpp->battery.feature_index = 0xff;
32623415
hidpp->battery.solar_feature_index = 0xff;
3416+
hidpp->battery.voltage_feature_index = 0xff;
32633417

32643418
if (hidpp->protocol_major >= 2) {
32653419
if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
32663420
ret = hidpp_solar_request_battery_event(hidpp);
3267-
else
3268-
ret = hidpp20_query_battery_info(hidpp);
3421+
else {
3422+
ret = hidpp20_query_battery_voltage_info(hidpp);
3423+
if (ret)
3424+
ret = hidpp20_query_battery_info(hidpp);
3425+
}
32693426

32703427
if (ret)
32713428
return ret;
@@ -3290,7 +3447,7 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
32903447
if (!battery_props)
32913448
return -ENOMEM;
32923449

3293-
num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 2;
3450+
num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 3;
32943451

32953452
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE)
32963453
battery_props[num_battery_props++] =
@@ -3300,6 +3457,10 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
33003457
battery_props[num_battery_props++] =
33013458
POWER_SUPPLY_PROP_CAPACITY_LEVEL;
33023459

3460+
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
3461+
battery_props[num_battery_props++] =
3462+
POWER_SUPPLY_PROP_VOLTAGE_NOW;
3463+
33033464
battery = &hidpp->battery;
33043465

33053466
n = atomic_inc_return(&battery_no) - 1;
@@ -3463,7 +3624,10 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
34633624
else
34643625
hidpp10_query_battery_status(hidpp);
34653626
} else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
3466-
hidpp20_query_battery_info(hidpp);
3627+
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
3628+
hidpp20_query_battery_voltage_info(hidpp);
3629+
else
3630+
hidpp20_query_battery_info(hidpp);
34673631
}
34683632
if (hidpp->battery.ps)
34693633
power_supply_changed(hidpp->battery.ps);

0 commit comments

Comments
 (0)