HID: input: map battery system charging
authorJosé Expósito <jose.exposito89@gmail.com>
Thu, 24 Nov 2022 17:59:37 +0000 (18:59 +0100)
committerJiri Kosina <jkosina@suse.cz>
Tue, 20 Dec 2022 14:30:35 +0000 (15:30 +0100)
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>
drivers/hid/.kunitconfig
drivers/hid/Kconfig
drivers/hid/hid-input-test.c [new file with mode: 0644]
drivers/hid/hid-input.c
include/linux/hid.h

index 04daeff..675a820 100644 (file)
@@ -1,5 +1,6 @@
 CONFIG_KUNIT=y
 CONFIG_USB=y
 CONFIG_USB_HID=y
+CONFIG_HID_BATTERY_STRENGTH=y
 CONFIG_HID_UCLOGIC=y
 CONFIG_HID_KUNIT_TEST=y
index 17cce4c..1aa7b66 100644 (file)
@@ -1264,6 +1264,7 @@ config HID_MCP2221
 config HID_KUNIT_TEST
        tristate "KUnit tests for HID" if !KUNIT_ALL_TESTS
        depends on KUNIT=y
+       depends on HID_BATTERY_STRENGTH
        depends on HID_UCLOGIC
        default KUNIT_ALL_TESTS
        help
diff --git a/drivers/hid/hid-input-test.c b/drivers/hid/hid-input-test.c
new file mode 100644 (file)
index 0000000..77c2d45
--- /dev/null
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  HID to Linux Input mapping
+ *
+ *  Copyright (c) 2022 José Expósito <jose.exposito89@gmail.com>
+ */
+
+#include <kunit/test.h>
+
+static void hid_test_input_set_battery_charge_status(struct kunit *test)
+{
+       struct hid_device *dev;
+       bool handled;
+
+       dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+       handled = hidinput_set_battery_charge_status(dev, HID_DG_HEIGHT, 0);
+       KUNIT_EXPECT_FALSE(test, handled);
+       KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN);
+
+       handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 0);
+       KUNIT_EXPECT_TRUE(test, handled);
+       KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING);
+
+       handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 1);
+       KUNIT_EXPECT_TRUE(test, handled);
+       KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING);
+}
+
+static void hid_test_input_get_battery_property(struct kunit *test)
+{
+       struct power_supply *psy;
+       struct hid_device *dev;
+       union power_supply_propval val;
+       int ret;
+
+       dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+       dev->battery_avoid_query = true;
+
+       psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy);
+       psy->drv_data = dev;
+
+       dev->battery_status = HID_BATTERY_UNKNOWN;
+       dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
+       ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
+       KUNIT_EXPECT_EQ(test, ret, 0);
+       KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN);
+
+       dev->battery_status = HID_BATTERY_REPORTED;
+       dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
+       ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
+       KUNIT_EXPECT_EQ(test, ret, 0);
+       KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING);
+
+       dev->battery_status = HID_BATTERY_REPORTED;
+       dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
+       ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
+       KUNIT_EXPECT_EQ(test, ret, 0);
+       KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_DISCHARGING);
+}
+
+static struct kunit_case hid_input_tests[] = {
+       KUNIT_CASE(hid_test_input_set_battery_charge_status),
+       KUNIT_CASE(hid_test_input_get_battery_property),
+       { }
+};
+
+static struct kunit_suite hid_input_test_suite = {
+       .name = "hid_input",
+       .test_cases = hid_input_tests,
+};
+
+kunit_test_suite(hid_input_test_suite);
+
+MODULE_DESCRIPTION("HID input KUnit tests");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>");
index 3a93cf0..796930a 100644 (file)
@@ -480,7 +480,7 @@ static int hidinput_get_battery_property(struct power_supply *psy,
                if (dev->battery_status == HID_BATTERY_UNKNOWN)
                        val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
                else
-                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+                       val->intval = dev->battery_charge_status;
                break;
 
        case POWER_SUPPLY_PROP_SCOPE:
@@ -548,6 +548,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
        dev->battery_max = max;
        dev->battery_report_type = report_type;
        dev->battery_report_id = field->report->id;
+       dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
 
        /*
         * Stylus is normally not connected to the device and thus we
@@ -614,6 +615,20 @@ static void hidinput_update_battery(struct hid_device *dev, int value)
                power_supply_changed(dev->battery);
        }
 }
+
+static bool hidinput_set_battery_charge_status(struct hid_device *dev,
+                                              unsigned int usage, int value)
+{
+       switch (usage) {
+       case HID_BAT_CHARGING:
+               dev->battery_charge_status = value ?
+                                            POWER_SUPPLY_STATUS_CHARGING :
+                                            POWER_SUPPLY_STATUS_DISCHARGING;
+               return true;
+       }
+
+       return false;
+}
 #else  /* !CONFIG_HID_BATTERY_STRENGTH */
 static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
                                  struct hid_field *field, bool is_percentage)
@@ -628,6 +643,12 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
 static void hidinput_update_battery(struct hid_device *dev, int value)
 {
 }
+
+static bool hidinput_set_battery_charge_status(struct hid_device *dev,
+                                              unsigned int usage, int value)
+{
+       return false;
+}
 #endif /* CONFIG_HID_BATTERY_STRENGTH */
 
 static bool hidinput_field_in_collection(struct hid_device *device, struct hid_field *field,
@@ -1217,6 +1238,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
                        hidinput_setup_battery(device, HID_INPUT_REPORT, field, true);
                        usage->type = EV_PWR;
                        return;
+               case HID_BAT_CHARGING:
+                       usage->type = EV_PWR;
+                       return;
                }
                goto unknown;
 
@@ -1459,7 +1483,11 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
                return;
 
        if (usage->type == EV_PWR) {
-               hidinput_update_battery(hid, value);
+               bool handled = hidinput_set_battery_charge_status(hid, usage->hid, value);
+
+               if (!handled)
+                       hidinput_update_battery(hid, value);
+
                return;
        }
 
@@ -2315,3 +2343,7 @@ void hidinput_disconnect(struct hid_device *hid)
        cancel_work_sync(&hid->led_work);
 }
 EXPORT_SYMBOL_GPL(hidinput_disconnect);
+
+#ifdef CONFIG_HID_KUNIT_TEST
+#include "hid-input-test.c"
+#endif
index 8677ae3..1eb5408 100644 (file)
@@ -312,6 +312,7 @@ struct hid_item {
 #define HID_DG_LATENCYMODE     0x000d0060
 
 #define HID_BAT_ABSOLUTESTATEOFCHARGE  0x00850065
+#define HID_BAT_CHARGING               0x00850044
 
 #define HID_VD_ASUS_CUSTOM_MEDIA_KEYS  0xff310076
 
@@ -611,6 +612,7 @@ struct hid_device {                                                 /* device report descriptor */
        __s32 battery_max;
        __s32 battery_report_type;
        __s32 battery_report_id;
+       __s32 battery_charge_status;
        enum hid_battery_status battery_status;
        bool battery_avoid_query;
        ktime_t battery_ratelimit_time;