HID: input: Introduce struct hid_battery and refactor battery code

Introduce struct hid_battery to encapsulate individual battery state,
preparing for future multi-battery support.

The new structure contains all battery-related fields previously stored
directly in hid_device (capacity, min, max, report_type, report_id,
charge_status, etc.). The hid_device->battery pointer type changes from
struct power_supply* to struct hid_battery*, and all battery functions
are refactored accordingly.

A hid_get_battery() helper is added for external drivers, with
hid-apple.c and hid-magicmouse.c updated to use the new API. The
hid-input-test.c KUnit tests are also updated for the new structure.

No functional changes for single-battery devices.

Signed-off-by: Lucas Zampieri <lcasmz54@gmail.com>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
This commit is contained in:
Lucas Zampieri 2026-03-14 01:05:29 +00:00 committed by Benjamin Tissoires
parent 5a9df49858
commit 7a3ac62473
5 changed files with 146 additions and 96 deletions

View File

@ -623,17 +623,19 @@ static int apple_fetch_battery(struct hid_device *hdev)
struct apple_sc *asc = hid_get_drvdata(hdev);
struct hid_report_enum *report_enum;
struct hid_report *report;
struct hid_battery *bat;
if (!(asc->quirks & APPLE_RDESC_BATTERY) || !hdev->battery)
bat = hid_get_battery(hdev);
if (!(asc->quirks & APPLE_RDESC_BATTERY) || !bat)
return -1;
report_enum = &hdev->report_enum[hdev->battery_report_type];
report = report_enum->report_id_hash[hdev->battery_report_id];
report_enum = &hdev->report_enum[bat->report_type];
report = report_enum->report_id_hash[bat->report_id];
if (!report || report->maxfield < 1)
return -1;
if (hdev->battery_capacity == hdev->battery_max)
if (bat->capacity == bat->max)
return -1;
hid_hw_request(hdev, report, HID_REQ_GET_REPORT);

View File

@ -9,54 +9,59 @@
static void hid_test_input_update_battery_charge_status(struct kunit *test)
{
struct hid_device *dev;
struct hid_battery *bat;
bool handled;
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bat);
handled = hidinput_update_battery_charge_status(dev, HID_DG_HEIGHT, 0);
handled = hidinput_update_battery_charge_status(bat, HID_DG_HEIGHT, 0);
KUNIT_EXPECT_FALSE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN);
KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_UNKNOWN);
handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 0);
handled = hidinput_update_battery_charge_status(bat, HID_BAT_CHARGING, 0);
KUNIT_EXPECT_TRUE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING);
KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_DISCHARGING);
handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 1);
handled = hidinput_update_battery_charge_status(bat, HID_BAT_CHARGING, 1);
KUNIT_EXPECT_TRUE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING);
KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_CHARGING);
}
static void hid_test_input_get_battery_property(struct kunit *test)
{
struct power_supply *psy;
struct hid_battery *bat;
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;
bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bat);
bat->dev = dev;
bat->avoid_query = true;
psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy);
psy->drv_data = dev;
psy->drv_data = bat;
dev->battery_status = HID_BATTERY_UNKNOWN;
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
bat->status = HID_BATTERY_UNKNOWN;
bat->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;
bat->status = HID_BATTERY_REPORTED;
bat->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;
bat->status = HID_BATTERY_REPORTED;
bat->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);

View File

@ -416,18 +416,18 @@ static unsigned find_battery_quirk(struct hid_device *hdev)
return quirks;
}
static int hidinput_scale_battery_capacity(struct hid_device *dev,
static int hidinput_scale_battery_capacity(struct hid_battery *bat,
int value)
{
if (dev->battery_min < dev->battery_max &&
value >= dev->battery_min && value <= dev->battery_max)
value = ((value - dev->battery_min) * 100) /
(dev->battery_max - dev->battery_min);
if (bat->min < bat->max &&
value >= bat->min && value <= bat->max)
value = ((value - bat->min) * 100) /
(bat->max - bat->min);
return value;
}
static int hidinput_query_battery_capacity(struct hid_device *dev)
static int hidinput_query_battery_capacity(struct hid_battery *bat)
{
int ret;
@ -435,19 +435,20 @@ static int hidinput_query_battery_capacity(struct hid_device *dev)
if (!buf)
return -ENOMEM;
ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 4,
dev->battery_report_type, HID_REQ_GET_REPORT);
ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, 4,
bat->report_type, HID_REQ_GET_REPORT);
if (ret < 2)
return -ENODATA;
return hidinput_scale_battery_capacity(dev, buf[1]);
return hidinput_scale_battery_capacity(bat, buf[1]);
}
static int hidinput_get_battery_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct hid_device *dev = power_supply_get_drvdata(psy);
struct hid_battery *bat = power_supply_get_drvdata(psy);
struct hid_device *dev = bat->dev;
int value;
int ret = 0;
@ -457,17 +458,17 @@ static int hidinput_get_battery_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = dev->battery_present;
val->intval = bat->present;
break;
case POWER_SUPPLY_PROP_CAPACITY:
if (dev->battery_status != HID_BATTERY_REPORTED &&
!dev->battery_avoid_query) {
value = hidinput_query_battery_capacity(dev);
if (bat->status != HID_BATTERY_REPORTED &&
!bat->avoid_query) {
value = hidinput_query_battery_capacity(bat);
if (value < 0)
return value;
} else {
value = dev->battery_capacity;
value = bat->capacity;
}
val->intval = value;
@ -478,20 +479,20 @@ static int hidinput_get_battery_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_STATUS:
if (dev->battery_status != HID_BATTERY_REPORTED &&
!dev->battery_avoid_query) {
value = hidinput_query_battery_capacity(dev);
if (bat->status != HID_BATTERY_REPORTED &&
!bat->avoid_query) {
value = hidinput_query_battery_capacity(bat);
if (value < 0)
return value;
dev->battery_capacity = value;
dev->battery_status = HID_BATTERY_QUERIED;
bat->capacity = value;
bat->status = HID_BATTERY_QUERIED;
}
if (dev->battery_status == HID_BATTERY_UNKNOWN)
if (bat->status == HID_BATTERY_UNKNOWN)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else
val->intval = dev->battery_charge_status;
val->intval = bat->charge_status;
break;
case POWER_SUPPLY_PROP_SCOPE:
@ -509,8 +510,9 @@ static int hidinput_get_battery_property(struct power_supply *psy,
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field, bool is_percentage)
{
struct hid_battery *bat;
struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg = { .drv_data = dev, };
struct power_supply_config psy_cfg = { 0 };
unsigned quirks;
s32 min, max;
int error;
@ -526,16 +528,22 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
if (quirks & HID_BATTERY_QUIRK_IGNORE)
return 0;
psy_desc = devm_kzalloc(&dev->dev, sizeof(*psy_desc), GFP_KERNEL);
if (!psy_desc)
bat = devm_kzalloc(&dev->dev, sizeof(*bat), GFP_KERNEL);
if (!bat)
return -ENOMEM;
psy_desc = devm_kzalloc(&dev->dev, sizeof(*psy_desc), GFP_KERNEL);
if (!psy_desc) {
error = -ENOMEM;
goto err_free_bat;
}
psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL, "hid-%s-battery",
strlen(dev->uniq) ?
dev->uniq : dev_name(&dev->dev));
if (!psy_desc->name) {
error = -ENOMEM;
goto err_free_mem;
goto err_free_desc;
}
psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
@ -555,51 +563,57 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
if (quirks & HID_BATTERY_QUIRK_FEATURE)
report_type = HID_FEATURE_REPORT;
dev->battery_min = min;
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;
bat->dev = dev;
bat->min = min;
bat->max = max;
bat->report_type = report_type;
bat->report_id = field->report->id;
bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
bat->status = HID_BATTERY_UNKNOWN;
/*
* 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;
bat->avoid_query = report_type == HID_INPUT_REPORT &&
field->physical == HID_DG_STYLUS;
if (quirks & HID_BATTERY_QUIRK_AVOID_QUERY)
dev->battery_avoid_query = true;
bat->avoid_query = true;
dev->battery_present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true;
bat->present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true;
dev->battery = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg);
if (IS_ERR(dev->battery)) {
error = PTR_ERR(dev->battery);
psy_cfg.drv_data = bat;
bat->ps = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg);
if (IS_ERR(bat->ps)) {
error = PTR_ERR(bat->ps);
hid_warn(dev, "can't register power supply: %d\n", error);
goto err_free_name;
}
power_supply_powers(dev->battery, &dev->dev);
power_supply_powers(bat->ps, &dev->dev);
dev->battery = bat;
return 0;
err_free_name:
devm_kfree(&dev->dev, psy_desc->name);
err_free_mem:
err_free_desc:
devm_kfree(&dev->dev, psy_desc);
err_free_bat:
devm_kfree(&dev->dev, bat);
dev->battery = NULL;
return error;
}
static bool hidinput_update_battery_charge_status(struct hid_device *dev,
static bool hidinput_update_battery_charge_status(struct hid_battery *bat,
unsigned int usage, int value)
{
switch (usage) {
case HID_BAT_CHARGING:
dev->battery_charge_status = value ?
POWER_SUPPLY_STATUS_CHARGING :
POWER_SUPPLY_STATUS_DISCHARGING;
bat->charge_status = value ?
POWER_SUPPLY_STATUS_CHARGING :
POWER_SUPPLY_STATUS_DISCHARGING;
return true;
}
@ -609,34 +623,35 @@ static bool hidinput_update_battery_charge_status(struct hid_device *dev,
static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
int value)
{
struct hid_battery *bat = dev->battery;
int capacity;
if (!dev->battery)
if (!bat)
return;
if (hidinput_update_battery_charge_status(dev, usage, value)) {
dev->battery_present = true;
power_supply_changed(dev->battery);
if (hidinput_update_battery_charge_status(bat, usage, value)) {
bat->present = true;
power_supply_changed(bat->ps);
return;
}
if ((usage & HID_USAGE_PAGE) == HID_UP_DIGITIZER && value == 0)
return;
if (value < dev->battery_min || value > dev->battery_max)
if (value < bat->min || value > bat->max)
return;
capacity = hidinput_scale_battery_capacity(dev, value);
capacity = hidinput_scale_battery_capacity(bat, value);
if (dev->battery_status != HID_BATTERY_REPORTED ||
capacity != dev->battery_capacity ||
ktime_after(ktime_get_coarse(), dev->battery_ratelimit_time)) {
dev->battery_present = true;
dev->battery_capacity = capacity;
dev->battery_status = HID_BATTERY_REPORTED;
dev->battery_ratelimit_time =
if (bat->status != HID_BATTERY_REPORTED ||
capacity != bat->capacity ||
ktime_after(ktime_get_coarse(), bat->ratelimit_time)) {
bat->present = true;
bat->capacity = capacity;
bat->status = HID_BATTERY_REPORTED;
bat->ratelimit_time =
ktime_add_ms(ktime_get_coarse(), 30 * 1000);
power_supply_changed(dev->battery);
power_supply_changed(bat->ps);
}
}
#else /* !CONFIG_HID_BATTERY_STRENGTH */

View File

@ -817,19 +817,21 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
#ifdef CONFIG_HID_BATTERY_STRENGTH
struct hid_report_enum *report_enum;
struct hid_report *report;
struct hid_battery *bat;
if (!hdev->battery ||
bat = hid_get_battery(hdev);
if (!bat ||
(!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
!is_usb_magictrackpad2(hdev->vendor, hdev->product)))
return -1;
report_enum = &hdev->report_enum[hdev->battery_report_type];
report = report_enum->report_id_hash[hdev->battery_report_id];
report_enum = &hdev->report_enum[bat->report_type];
report = report_enum->report_id_hash[bat->report_id];
if (!report || report->maxfield < 1)
return -1;
if (hdev->battery_capacity == hdev->battery_max)
if (bat->capacity == bat->max)
return -1;
hid_hw_request(hdev, report, HID_REQ_GET_REPORT);

View File

@ -634,6 +634,36 @@ enum hid_battery_status {
HID_BATTERY_REPORTED, /* Device sent unsolicited battery strength report */
};
/**
* struct hid_battery - represents a single battery power supply
* @dev: pointer to the parent hid_device
* @ps: the power supply instance
* @min: minimum battery value from HID descriptor
* @max: maximum battery value from HID descriptor
* @report_type: HID report type (input/feature)
* @report_id: HID report ID for this battery
* @charge_status: current charging status
* @status: battery reporting status
* @capacity: current battery capacity (0-100)
* @avoid_query: if true, avoid querying battery (e.g., for stylus)
* @present: if true, battery is present (may be dynamic)
* @ratelimit_time: rate limiting for battery reports
*/
struct hid_battery {
struct hid_device *dev;
struct power_supply *ps;
__s32 min;
__s32 max;
__s32 report_type;
__s32 report_id;
__s32 charge_status;
enum hid_battery_status status;
__s32 capacity;
bool avoid_query;
bool present;
ktime_t ratelimit_time;
};
struct hid_driver;
struct hid_ll_driver;
@ -670,20 +700,9 @@ struct hid_device {
#ifdef CONFIG_HID_BATTERY_STRENGTH
/*
* Power supply information for HID devices which report
* battery strength. power_supply was successfully registered if
* battery is non-NULL.
* battery strength. battery is non-NULL if successfully registered.
*/
struct power_supply *battery;
__s32 battery_capacity;
__s32 battery_min;
__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;
bool battery_present;
ktime_t battery_ratelimit_time;
struct hid_battery *battery;
#endif
unsigned long status; /* see STAT flags above */
@ -744,6 +763,13 @@ static inline void hid_set_drvdata(struct hid_device *hdev, void *data)
dev_set_drvdata(&hdev->dev, data);
}
#ifdef CONFIG_HID_BATTERY_STRENGTH
static inline struct hid_battery *hid_get_battery(struct hid_device *hdev)
{
return hdev->battery;
}
#endif
#define HID_GLOBAL_STACK_SIZE 4
#define HID_COLLECTION_STACK_SIZE 4