Commit 27840bc4 authored by Dmitry Torokhov's avatar Dmitry Torokhov Committed by Greg Kroah-Hartman

HID: input: fix battery level reporting on BT mice

commit 2e210bbb upstream.

The commit 581c4484 ("HID: input: map digitizer battery usage")
assumed that devices having input (qas opposed to feature) report for
battery strength would report the data on their own, without the need to
be polled by the kernel; unfortunately it is not so. Many wireless mice
do not send unsolicited reports with battery strength data and have to
be polled explicitly. As a complication, stylus devices on digitizers
are not normally connected to the base and thus can not be polled - the
base can only determine battery strength in the stylus when it is in
proximity.

To solve this issue, we add a special flag that tells the kernel
to avoid polling the device (and expect unsolicited reports) and set it
when report field with physical usage of digitizer stylus (HID_DG_STYLUS).
Unless this flag is set, and we have not seen the unsolicited reports,
the kernel will attempt to poll the device when userspace attempts to
read "capacity" and "state" attributes of power_supply object
corresponding to the devices battery.

Fixes: 581c4484 ("HID: input: map digitizer battery usage")
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=198095
Cc: stable@vger.kernel.org
Reported-and-tested-by: default avatarMartin van Es <martin@mrvanes.com>
Signed-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 90936d90
...@@ -387,7 +387,8 @@ static int hidinput_get_battery_property(struct power_supply *psy, ...@@ -387,7 +387,8 @@ static int hidinput_get_battery_property(struct power_supply *psy,
break; break;
case POWER_SUPPLY_PROP_CAPACITY: case POWER_SUPPLY_PROP_CAPACITY:
if (dev->battery_report_type == HID_FEATURE_REPORT) { if (dev->battery_status != HID_BATTERY_REPORTED &&
!dev->battery_avoid_query) {
value = hidinput_query_battery_capacity(dev); value = hidinput_query_battery_capacity(dev);
if (value < 0) if (value < 0)
return value; return value;
...@@ -403,17 +404,17 @@ static int hidinput_get_battery_property(struct power_supply *psy, ...@@ -403,17 +404,17 @@ static int hidinput_get_battery_property(struct power_supply *psy,
break; break;
case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_STATUS:
if (!dev->battery_reported && if (dev->battery_status != HID_BATTERY_REPORTED &&
dev->battery_report_type == HID_FEATURE_REPORT) { !dev->battery_avoid_query) {
value = hidinput_query_battery_capacity(dev); value = hidinput_query_battery_capacity(dev);
if (value < 0) if (value < 0)
return value; return value;
dev->battery_capacity = value; dev->battery_capacity = value;
dev->battery_reported = true; dev->battery_status = HID_BATTERY_QUERIED;
} }
if (!dev->battery_reported) if (dev->battery_status == HID_BATTERY_UNKNOWN)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN; val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else if (dev->battery_capacity == 100) else if (dev->battery_capacity == 100)
val->intval = POWER_SUPPLY_STATUS_FULL; val->intval = POWER_SUPPLY_STATUS_FULL;
...@@ -486,6 +487,14 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, ...@@ -486,6 +487,14 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
dev->battery_report_type = report_type; dev->battery_report_type = report_type;
dev->battery_report_id = field->report->id; dev->battery_report_id = field->report->id;
/*
* Stylus is normally not connected to the device and thus we
* can't query the device and get meaningful battery strength.
* We have to wait for the device to report it on its own.
*/
dev->battery_avoid_query = report_type == HID_INPUT_REPORT &&
field->physical == HID_DG_STYLUS;
dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg); dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg);
if (IS_ERR(dev->battery)) { if (IS_ERR(dev->battery)) {
error = PTR_ERR(dev->battery); error = PTR_ERR(dev->battery);
...@@ -530,9 +539,10 @@ static void hidinput_update_battery(struct hid_device *dev, int value) ...@@ -530,9 +539,10 @@ static void hidinput_update_battery(struct hid_device *dev, int value)
capacity = hidinput_scale_battery_capacity(dev, value); capacity = hidinput_scale_battery_capacity(dev, value);
if (!dev->battery_reported || capacity != dev->battery_capacity) { if (dev->battery_status != HID_BATTERY_REPORTED ||
capacity != dev->battery_capacity) {
dev->battery_capacity = capacity; dev->battery_capacity = capacity;
dev->battery_reported = true; dev->battery_status = HID_BATTERY_REPORTED;
power_supply_changed(dev->battery); power_supply_changed(dev->battery);
} }
} }
......
...@@ -512,6 +512,12 @@ enum hid_type { ...@@ -512,6 +512,12 @@ enum hid_type {
HID_TYPE_USBNONE HID_TYPE_USBNONE
}; };
enum hid_battery_status {
HID_BATTERY_UNKNOWN = 0,
HID_BATTERY_QUERIED, /* Kernel explicitly queried battery strength */
HID_BATTERY_REPORTED, /* Device sent unsolicited battery strength report */
};
struct hid_driver; struct hid_driver;
struct hid_ll_driver; struct hid_ll_driver;
...@@ -554,7 +560,8 @@ struct hid_device { /* device report descriptor */ ...@@ -554,7 +560,8 @@ struct hid_device { /* device report descriptor */
__s32 battery_max; __s32 battery_max;
__s32 battery_report_type; __s32 battery_report_type;
__s32 battery_report_id; __s32 battery_report_id;
bool battery_reported; enum hid_battery_status battery_status;
bool battery_avoid_query;
#endif #endif
unsigned int status; /* see STAT flags above */ unsigned int status; /* see STAT flags above */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment