Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

HID: input: map battery system charging

HID descriptors with Battery System (0x85) Charging (0x44) usage are
ignored and POWER_SUPPLY_STATUS_DISCHARGING is always reported to user
space, even when the device is charging.

Map this usage and when it is reported set the right charging status.

In addition, add KUnit tests to make sure that the charging status is
correctly set and reported. They can be run with the usual command:

$ ./tools/testing/kunit/kunit.py run --kunitconfig=drivers/hid

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>

authored by

José Expósito and committed by
Jiri Kosina
a608dc1c 2043f9a3

+118 -2
+1
drivers/hid/.kunitconfig
··· 1 1 CONFIG_KUNIT=y 2 2 CONFIG_USB=y 3 3 CONFIG_USB_HID=y 4 + CONFIG_HID_BATTERY_STRENGTH=y 4 5 CONFIG_HID_UCLOGIC=y 5 6 CONFIG_HID_KUNIT_TEST=y
+1
drivers/hid/Kconfig
··· 1264 1264 config HID_KUNIT_TEST 1265 1265 tristate "KUnit tests for HID" if !KUNIT_ALL_TESTS 1266 1266 depends on KUNIT=y 1267 + depends on HID_BATTERY_STRENGTH 1267 1268 depends on HID_UCLOGIC 1268 1269 default KUNIT_ALL_TESTS 1269 1270 help
+80
drivers/hid/hid-input-test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * HID to Linux Input mapping 4 + * 5 + * Copyright (c) 2022 José Expósito <jose.exposito89@gmail.com> 6 + */ 7 + 8 + #include <kunit/test.h> 9 + 10 + static void hid_test_input_set_battery_charge_status(struct kunit *test) 11 + { 12 + struct hid_device *dev; 13 + bool handled; 14 + 15 + dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); 16 + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); 17 + 18 + handled = hidinput_set_battery_charge_status(dev, HID_DG_HEIGHT, 0); 19 + KUNIT_EXPECT_FALSE(test, handled); 20 + KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN); 21 + 22 + handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 0); 23 + KUNIT_EXPECT_TRUE(test, handled); 24 + KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING); 25 + 26 + handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 1); 27 + KUNIT_EXPECT_TRUE(test, handled); 28 + KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING); 29 + } 30 + 31 + static void hid_test_input_get_battery_property(struct kunit *test) 32 + { 33 + struct power_supply *psy; 34 + struct hid_device *dev; 35 + union power_supply_propval val; 36 + int ret; 37 + 38 + dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); 39 + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); 40 + dev->battery_avoid_query = true; 41 + 42 + psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL); 43 + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy); 44 + psy->drv_data = dev; 45 + 46 + dev->battery_status = HID_BATTERY_UNKNOWN; 47 + dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING; 48 + ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); 49 + KUNIT_EXPECT_EQ(test, ret, 0); 50 + KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN); 51 + 52 + dev->battery_status = HID_BATTERY_REPORTED; 53 + dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING; 54 + ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); 55 + KUNIT_EXPECT_EQ(test, ret, 0); 56 + KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING); 57 + 58 + dev->battery_status = HID_BATTERY_REPORTED; 59 + dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING; 60 + ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); 61 + KUNIT_EXPECT_EQ(test, ret, 0); 62 + KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_DISCHARGING); 63 + } 64 + 65 + static struct kunit_case hid_input_tests[] = { 66 + KUNIT_CASE(hid_test_input_set_battery_charge_status), 67 + KUNIT_CASE(hid_test_input_get_battery_property), 68 + { } 69 + }; 70 + 71 + static struct kunit_suite hid_input_test_suite = { 72 + .name = "hid_input", 73 + .test_cases = hid_input_tests, 74 + }; 75 + 76 + kunit_test_suite(hid_input_test_suite); 77 + 78 + MODULE_DESCRIPTION("HID input KUnit tests"); 79 + MODULE_LICENSE("GPL"); 80 + MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>");
+34 -2
drivers/hid/hid-input.c
··· 480 480 if (dev->battery_status == HID_BATTERY_UNKNOWN) 481 481 val->intval = POWER_SUPPLY_STATUS_UNKNOWN; 482 482 else 483 - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 483 + val->intval = dev->battery_charge_status; 484 484 break; 485 485 486 486 case POWER_SUPPLY_PROP_SCOPE: ··· 548 548 dev->battery_max = max; 549 549 dev->battery_report_type = report_type; 550 550 dev->battery_report_id = field->report->id; 551 + dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING; 551 552 552 553 /* 553 554 * Stylus is normally not connected to the device and thus we ··· 615 614 power_supply_changed(dev->battery); 616 615 } 617 616 } 617 + 618 + static bool hidinput_set_battery_charge_status(struct hid_device *dev, 619 + unsigned int usage, int value) 620 + { 621 + switch (usage) { 622 + case HID_BAT_CHARGING: 623 + dev->battery_charge_status = value ? 624 + POWER_SUPPLY_STATUS_CHARGING : 625 + POWER_SUPPLY_STATUS_DISCHARGING; 626 + return true; 627 + } 628 + 629 + return false; 630 + } 618 631 #else /* !CONFIG_HID_BATTERY_STRENGTH */ 619 632 static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, 620 633 struct hid_field *field, bool is_percentage) ··· 642 627 643 628 static void hidinput_update_battery(struct hid_device *dev, int value) 644 629 { 630 + } 631 + 632 + static bool hidinput_set_battery_charge_status(struct hid_device *dev, 633 + unsigned int usage, int value) 634 + { 635 + return false; 645 636 } 646 637 #endif /* CONFIG_HID_BATTERY_STRENGTH */ 647 638 ··· 1238 1217 hidinput_setup_battery(device, HID_INPUT_REPORT, field, true); 1239 1218 usage->type = EV_PWR; 1240 1219 return; 1220 + case HID_BAT_CHARGING: 1221 + usage->type = EV_PWR; 1222 + return; 1241 1223 } 1242 1224 goto unknown; 1243 1225 ··· 1483 1459 return; 1484 1460 1485 1461 if (usage->type == EV_PWR) { 1486 - hidinput_update_battery(hid, value); 1462 + bool handled = hidinput_set_battery_charge_status(hid, usage->hid, value); 1463 + 1464 + if (!handled) 1465 + hidinput_update_battery(hid, value); 1466 + 1487 1467 return; 1488 1468 } 1489 1469 ··· 2343 2315 cancel_work_sync(&hid->led_work); 2344 2316 } 2345 2317 EXPORT_SYMBOL_GPL(hidinput_disconnect); 2318 + 2319 + #ifdef CONFIG_HID_KUNIT_TEST 2320 + #include "hid-input-test.c" 2321 + #endif
+2
include/linux/hid.h
··· 312 312 #define HID_DG_LATENCYMODE 0x000d0060 313 313 314 314 #define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065 315 + #define HID_BAT_CHARGING 0x00850044 315 316 316 317 #define HID_VD_ASUS_CUSTOM_MEDIA_KEYS 0xff310076 317 318 ··· 612 611 __s32 battery_max; 613 612 __s32 battery_report_type; 614 613 __s32 battery_report_id; 614 + __s32 battery_charge_status; 615 615 enum hid_battery_status battery_status; 616 616 bool battery_avoid_query; 617 617 ktime_t battery_ratelimit_time;