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>
master
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 apple_sc *asc = hid_get_drvdata(hdev);
struct hid_report_enum *report_enum; struct hid_report_enum *report_enum;
struct hid_report *report; 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; return -1;
report_enum = &hdev->report_enum[hdev->battery_report_type]; report_enum = &hdev->report_enum[bat->report_type];
report = report_enum->report_id_hash[hdev->battery_report_id]; report = report_enum->report_id_hash[bat->report_id];
if (!report || report->maxfield < 1) if (!report || report->maxfield < 1)
return -1; return -1;
if (hdev->battery_capacity == hdev->battery_max) if (bat->capacity == bat->max)
return -1; return -1;
hid_hw_request(hdev, report, HID_REQ_GET_REPORT); 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) static void hid_test_input_update_battery_charge_status(struct kunit *test)
{ {
struct hid_device *dev; struct hid_battery *bat;
bool handled; bool handled;
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); 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_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_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_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) static void hid_test_input_get_battery_property(struct kunit *test)
{ {
struct power_supply *psy; struct power_supply *psy;
struct hid_battery *bat;
struct hid_device *dev; struct hid_device *dev;
union power_supply_propval val; union power_supply_propval val;
int ret; int ret;
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); 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); psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy);
psy->drv_data = dev; psy->drv_data = bat;
dev->battery_status = HID_BATTERY_UNKNOWN; bat->status = HID_BATTERY_UNKNOWN;
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING; bat->charge_status = POWER_SUPPLY_STATUS_CHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN); KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN);
dev->battery_status = HID_BATTERY_REPORTED; bat->status = HID_BATTERY_REPORTED;
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING; bat->charge_status = POWER_SUPPLY_STATUS_CHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING); KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING);
dev->battery_status = HID_BATTERY_REPORTED; bat->status = HID_BATTERY_REPORTED;
dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING; bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_DISCHARGING); 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; return quirks;
} }
static int hidinput_scale_battery_capacity(struct hid_device *dev, static int hidinput_scale_battery_capacity(struct hid_battery *bat,
int value) int value)
{ {
if (dev->battery_min < dev->battery_max && if (bat->min < bat->max &&
value >= dev->battery_min && value <= dev->battery_max) value >= bat->min && value <= bat->max)
value = ((value - dev->battery_min) * 100) / value = ((value - bat->min) * 100) /
(dev->battery_max - dev->battery_min); (bat->max - bat->min);
return value; return value;
} }
static int hidinput_query_battery_capacity(struct hid_device *dev) static int hidinput_query_battery_capacity(struct hid_battery *bat)
{ {
int ret; int ret;
@ -435,19 +435,20 @@ static int hidinput_query_battery_capacity(struct hid_device *dev)
if (!buf) if (!buf)
return -ENOMEM; return -ENOMEM;
ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 4, ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, 4,
dev->battery_report_type, HID_REQ_GET_REPORT); bat->report_type, HID_REQ_GET_REPORT);
if (ret < 2) if (ret < 2)
return -ENODATA; 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, static int hidinput_get_battery_property(struct power_supply *psy,
enum power_supply_property prop, enum power_supply_property prop,
union power_supply_propval *val) 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 value;
int ret = 0; int ret = 0;
@ -457,17 +458,17 @@ static int hidinput_get_battery_property(struct power_supply *psy,
break; break;
case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_PRESENT:
val->intval = dev->battery_present; val->intval = bat->present;
break; break;
case POWER_SUPPLY_PROP_CAPACITY: case POWER_SUPPLY_PROP_CAPACITY:
if (dev->battery_status != HID_BATTERY_REPORTED && if (bat->status != HID_BATTERY_REPORTED &&
!dev->battery_avoid_query) { !bat->avoid_query) {
value = hidinput_query_battery_capacity(dev); value = hidinput_query_battery_capacity(bat);
if (value < 0) if (value < 0)
return value; return value;
} else { } else {
value = dev->battery_capacity; value = bat->capacity;
} }
val->intval = value; val->intval = value;
@ -478,20 +479,20 @@ 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_status != HID_BATTERY_REPORTED && if (bat->status != HID_BATTERY_REPORTED &&
!dev->battery_avoid_query) { !bat->avoid_query) {
value = hidinput_query_battery_capacity(dev); value = hidinput_query_battery_capacity(bat);
if (value < 0) if (value < 0)
return value; return value;
dev->battery_capacity = value; bat->capacity = value;
dev->battery_status = HID_BATTERY_QUERIED; bat->status = HID_BATTERY_QUERIED;
} }
if (dev->battery_status == HID_BATTERY_UNKNOWN) if (bat->status == HID_BATTERY_UNKNOWN)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN; val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else else
val->intval = dev->battery_charge_status; val->intval = bat->charge_status;
break; break;
case POWER_SUPPLY_PROP_SCOPE: 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, static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field, bool is_percentage) struct hid_field *field, bool is_percentage)
{ {
struct hid_battery *bat;
struct power_supply_desc *psy_desc; struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg = { .drv_data = dev, }; struct power_supply_config psy_cfg = { 0 };
unsigned quirks; unsigned quirks;
s32 min, max; s32 min, max;
int error; int error;
@ -526,16 +528,22 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
if (quirks & HID_BATTERY_QUIRK_IGNORE) if (quirks & HID_BATTERY_QUIRK_IGNORE)
return 0; return 0;
psy_desc = devm_kzalloc(&dev->dev, sizeof(*psy_desc), GFP_KERNEL); bat = devm_kzalloc(&dev->dev, sizeof(*bat), GFP_KERNEL);
if (!psy_desc) if (!bat)
return -ENOMEM; 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", psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL, "hid-%s-battery",
strlen(dev->uniq) ? strlen(dev->uniq) ?
dev->uniq : dev_name(&dev->dev)); dev->uniq : dev_name(&dev->dev));
if (!psy_desc->name) { if (!psy_desc->name) {
error = -ENOMEM; error = -ENOMEM;
goto err_free_mem; goto err_free_desc;
} }
psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; 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) if (quirks & HID_BATTERY_QUIRK_FEATURE)
report_type = HID_FEATURE_REPORT; report_type = HID_FEATURE_REPORT;
dev->battery_min = min; bat->dev = dev;
dev->battery_max = max; bat->min = min;
dev->battery_report_type = report_type; bat->max = max;
dev->battery_report_id = field->report->id; bat->report_type = report_type;
dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING; 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 * Stylus is normally not connected to the device and thus we
* can't query the device and get meaningful battery strength. * can't query the device and get meaningful battery strength.
* We have to wait for the device to report it on its own. * We have to wait for the device to report it on its own.
*/ */
dev->battery_avoid_query = report_type == HID_INPUT_REPORT && bat->avoid_query = report_type == HID_INPUT_REPORT &&
field->physical == HID_DG_STYLUS; field->physical == HID_DG_STYLUS;
if (quirks & HID_BATTERY_QUIRK_AVOID_QUERY) 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); psy_cfg.drv_data = bat;
if (IS_ERR(dev->battery)) { bat->ps = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg);
error = PTR_ERR(dev->battery); if (IS_ERR(bat->ps)) {
error = PTR_ERR(bat->ps);
hid_warn(dev, "can't register power supply: %d\n", error); hid_warn(dev, "can't register power supply: %d\n", error);
goto err_free_name; goto err_free_name;
} }
power_supply_powers(dev->battery, &dev->dev); power_supply_powers(bat->ps, &dev->dev);
dev->battery = bat;
return 0; return 0;
err_free_name: err_free_name:
devm_kfree(&dev->dev, psy_desc->name); devm_kfree(&dev->dev, psy_desc->name);
err_free_mem: err_free_desc:
devm_kfree(&dev->dev, psy_desc); devm_kfree(&dev->dev, psy_desc);
err_free_bat:
devm_kfree(&dev->dev, bat);
dev->battery = NULL; dev->battery = NULL;
return error; 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) unsigned int usage, int value)
{ {
switch (usage) { switch (usage) {
case HID_BAT_CHARGING: case HID_BAT_CHARGING:
dev->battery_charge_status = value ? bat->charge_status = value ?
POWER_SUPPLY_STATUS_CHARGING : POWER_SUPPLY_STATUS_CHARGING :
POWER_SUPPLY_STATUS_DISCHARGING; POWER_SUPPLY_STATUS_DISCHARGING;
return true; 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, static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
int value) int value)
{ {
struct hid_battery *bat = dev->battery;
int capacity; int capacity;
if (!dev->battery) if (!bat)
return; return;
if (hidinput_update_battery_charge_status(dev, usage, value)) { if (hidinput_update_battery_charge_status(bat, usage, value)) {
dev->battery_present = true; bat->present = true;
power_supply_changed(dev->battery); power_supply_changed(bat->ps);
return; return;
} }
if ((usage & HID_USAGE_PAGE) == HID_UP_DIGITIZER && value == 0) if ((usage & HID_USAGE_PAGE) == HID_UP_DIGITIZER && value == 0)
return; return;
if (value < dev->battery_min || value > dev->battery_max) if (value < bat->min || value > bat->max)
return; return;
capacity = hidinput_scale_battery_capacity(dev, value); capacity = hidinput_scale_battery_capacity(bat, value);
if (dev->battery_status != HID_BATTERY_REPORTED || if (bat->status != HID_BATTERY_REPORTED ||
capacity != dev->battery_capacity || capacity != bat->capacity ||
ktime_after(ktime_get_coarse(), dev->battery_ratelimit_time)) { ktime_after(ktime_get_coarse(), bat->ratelimit_time)) {
dev->battery_present = true; bat->present = true;
dev->battery_capacity = capacity; bat->capacity = capacity;
dev->battery_status = HID_BATTERY_REPORTED; bat->status = HID_BATTERY_REPORTED;
dev->battery_ratelimit_time = bat->ratelimit_time =
ktime_add_ms(ktime_get_coarse(), 30 * 1000); ktime_add_ms(ktime_get_coarse(), 30 * 1000);
power_supply_changed(dev->battery); power_supply_changed(bat->ps);
} }
} }
#else /* !CONFIG_HID_BATTERY_STRENGTH */ #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 #ifdef CONFIG_HID_BATTERY_STRENGTH
struct hid_report_enum *report_enum; struct hid_report_enum *report_enum;
struct hid_report *report; 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_magicmouse2(hdev->vendor, hdev->product) &&
!is_usb_magictrackpad2(hdev->vendor, hdev->product))) !is_usb_magictrackpad2(hdev->vendor, hdev->product)))
return -1; return -1;
report_enum = &hdev->report_enum[hdev->battery_report_type]; report_enum = &hdev->report_enum[bat->report_type];
report = report_enum->report_id_hash[hdev->battery_report_id]; report = report_enum->report_id_hash[bat->report_id];
if (!report || report->maxfield < 1) if (!report || report->maxfield < 1)
return -1; return -1;
if (hdev->battery_capacity == hdev->battery_max) if (bat->capacity == bat->max)
return -1; return -1;
hid_hw_request(hdev, report, HID_REQ_GET_REPORT); 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 */ 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_driver;
struct hid_ll_driver; struct hid_ll_driver;
@ -670,20 +700,9 @@ struct hid_device {
#ifdef CONFIG_HID_BATTERY_STRENGTH #ifdef CONFIG_HID_BATTERY_STRENGTH
/* /*
* Power supply information for HID devices which report * Power supply information for HID devices which report
* battery strength. power_supply was successfully registered if * battery strength. battery is non-NULL if successfully registered.
* battery is non-NULL.
*/ */
struct power_supply *battery; struct hid_battery *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;
#endif #endif
unsigned long status; /* see STAT flags above */ 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); 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_GLOBAL_STACK_SIZE 4
#define HID_COLLECTION_STACK_SIZE 4 #define HID_COLLECTION_STACK_SIZE 4