linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/1] HID: Add support for multiple batteries per device
@ 2025-11-19 14:30 Lucas Zampieri
  2025-11-19 14:30 ` [PATCH v3 1/1] HID: input: " Lucas Zampieri
  0 siblings, 1 reply; 4+ messages in thread
From: Lucas Zampieri @ 2025-11-19 14:30 UTC (permalink / raw)
  To: linux-input
  Cc: Lucas Zampieri, linux-kernel, Jiri Kosina, Benjamin Tissoires,
	Sebastian Reichel, Bastien Nocera, linux-pm

This series adds support for HID devices with multiple batteries.

Currently, the HID battery reporting subsystem only supports one battery per
device. There are several devices with multiple batteries that would benefit
from this support:
- Gaming headsets with batteries in both the headset and charging dock
- Wireless earbuds with per-earbud batteries plus charging case
- Split keyboards with per-side batteries

## Proposed Solution

This series introduces struct hid_battery to encapsulate individual battery
state, replaces the old battery fields with a list-based approach, and adds
support for multiple batteries tracked within struct hid_device. Batteries
are identified by report ID. The implementation is fully backwards compatible
with single-battery devices through a helper function.

## Testing

Tested with split keyboard hardware (Dactyl 5x6) using custom ZMK firmware
that implements per-side HID battery reporting. Each battery (left and right
keyboard halves) reports independently through the power supply interface with
distinct report IDs (0x05 and 0x06).

Test firmware available on my personal fork at:
https://github.com/zampierilucas/zmk/tree/feat/individual-hid-battery-reporting
If this series gets merged, these changes will be proposed to upstream ZMK.

HID descriptor and recording captured with hid-recorder:

D: 0
R: 162 05 01 09 06 a1 01 85 01 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 05 07 75 08 95 01 81 03 05 07 15 00 25 01 19 00 29 67 75 01 95 68 81 02 c0 05 0c 09 01 a1 01 85 02 05 0c 15 00 26 ff 0f 19 00 2a ff 0f 75 10 95 06 81 00 c0 05 84 09 05 a1 01 05 85 85 05 09 44 15 00 25 01 35 00 45 01 75 08 95 01 81 02 09 65 15 00 25 64 35 00 45 64 75 08 95 01 81 02 c0 05 84 09 05 a1 01 05 85 85 06 09 44 15 00 25 01 35 00 45 01 75 08 95 01 81 02 09 65 15 00 25 64 35 00 45 64 75 08 95 01 81 02 c0
N: ZMK Project Dactyl 5x6
P: usb-0000:2d:00.3-4.2/input2
I: 3 1d50 615e
D: 0
E: 0.000000 3 05 00 56
E: 0.000977 3 05 00 56
E: 1.490974 3 06 00 52
E: 1.491958 3 06 00 52
E: 6.492979 3 06 00 53
E: 6.493962 3 06 00 53

The recording shows both batteries reporting with different charge levels
(Report ID 05: 86%, Report ID 06: 82%-83%), demonstrating the multi-battery
functionality. This can be used to verify UPower compatibility.

## Future Work: Userspace Integration

As suggested by Bastien, semantic battery differentiation (e.g., "left
earbud" vs "right earbud") requires userspace coordination, as HID
reports typically lack role metadata.

This will require:
1. systemd/hwdb entries for device-specific battery role mappings
2. UPower updates to enumerate and group multi-battery devices
3. Desktop environment changes to display batteries with meaningful labels

This kernel infrastructure is a prerequisite for that userspace work.

Lucas Zampieri (1):
  HID: input: Add support for multiple batteries per device

Signed-off-by: Lucas Zampieri <lzampier@redhat.com>

Changes in v3:
- Squashed the three v2 patches into a single patch as suggested by
  Benjamin
- Removed all legacy dev->battery_* fields, using list-based storage only
- Changed battery naming to include report ID: hid-{uniq}-battery-{report_id}
- Converted battery memory management to devm_* for automatic cleanup
- Updated hidinput_update_battery() to take struct hid_battery directly
- Added hid_get_first_battery() helper for external driver compatibility
- Updated hid-apple.c and hid-magicmouse.c to use new battery API
- Simplified cover letter based on feedback

Changes in v2:
- Split the monolithic v1 patch into three logical patches for easier review:
  1. Introduce struct hid_battery (pure structure addition)
  2. Refactor existing code to use the new structure (internal changes)
  3. Add multi-battery support (new functionality)
- Added detailed testing section with hardware specifics
- Added hid-recorder output (dactyl-hid-recording.txt) demonstrating two-battery
  HID descriptor for UPower validation
- Added "Future Work: Userspace Integration" section addressing Bastien's feedback
  about semantic battery differentiation
- Added hardware examples with product links to commit messages (per Bastien's
  suggestion)
- No functional changes from v1, only improved patch organization and documentation

 drivers/hid/hid-apple.c      |  10 +-
 drivers/hid/hid-core.c       |   4 +
 drivers/hid/hid-input-test.c |  39 +++----
 drivers/hid/hid-input.c      | 191 +++++++++++++++++++----------------
 drivers/hid/hid-magicmouse.c |  10 +-
 include/linux/hid.h          |  54 +++++++---
 6 files changed, 182 insertions(+), 126 deletions(-)

--
2.51.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v3 1/1] HID: input: Add support for multiple batteries per device
  2025-11-19 14:30 [PATCH v3 0/1] HID: Add support for multiple batteries per device Lucas Zampieri
@ 2025-11-19 14:30 ` Lucas Zampieri
  2025-11-20  8:17   ` kernel test robot
  2025-11-20  8:40   ` kernel test robot
  0 siblings, 2 replies; 4+ messages in thread
From: Lucas Zampieri @ 2025-11-19 14:30 UTC (permalink / raw)
  To: linux-input
  Cc: Lucas Zampieri, linux-kernel, Jiri Kosina, Benjamin Tissoires,
	Sebastian Reichel, Bastien Nocera, linux-pm

Introduce struct hid_battery to encapsulate individual battery state and
enable HID devices to register multiple batteries, each identified by
its report ID.

This change adds struct hid_battery with all battery-related fields and
replaces the legacy dev->battery_* fields with a batteries list. All
memory management is converted to use devm_* for simpler cleanup.
Batteries are named using their report ID with the pattern
hid-{uniq}-battery-{report_id}. External drivers hid-apple and
hid-magicmouse are updated to use the new battery API via the
hid_get_first_battery() helper, and hid-input-test is updated for the
new battery structure.

This enables proper battery reporting for devices with multiple
batteries such as split keyboards, gaming headsets with charging docks,
and wireless earbuds with per-earbud batteries.

Signed-off-by: Lucas Zampieri <lzampier@redhat.com>
---
 drivers/hid/hid-apple.c      |  10 +-
 drivers/hid/hid-core.c       |   4 +
 drivers/hid/hid-input-test.c |  39 +++----
 drivers/hid/hid-input.c      | 191 +++++++++++++++++++----------------
 drivers/hid/hid-magicmouse.c |  10 +-
 include/linux/hid.h          |  54 +++++++---
 6 files changed, 182 insertions(+), 126 deletions(-)

diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 61404d7a43ee..fb09b616f8cc 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -618,17 +618,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_first_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);
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index a5b3a8ca2fcb..76d628547e9a 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2990,6 +2990,10 @@ struct hid_device *hid_allocate_device(void)
 	mutex_init(&hdev->ll_open_lock);
 	kref_init(&hdev->ref);
 
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+	INIT_LIST_HEAD(&hdev->batteries);
+#endif
+
 	ret = hid_bpf_device_init(hdev);
 	if (ret)
 		goto out_err;
diff --git a/drivers/hid/hid-input-test.c b/drivers/hid/hid-input-test.c
index 6f5c71660d82..c92008dafddf 100644
--- a/drivers/hid/hid-input-test.c
+++ b/drivers/hid/hid-input-test.c
@@ -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);
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index e56e7de53279..08b889ecaf7f 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -418,18 +418,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)
 {
 	u8 *buf;
 	int ret;
@@ -438,14 +438,14 @@ 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) {
 		kfree(buf);
 		return -ENODATA;
 	}
 
-	ret = hidinput_scale_battery_capacity(dev, buf[1]);
+	ret = hidinput_scale_battery_capacity(bat, buf[1]);
 	kfree(buf);
 	return ret;
 }
@@ -454,7 +454,8 @@ 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;
 
@@ -465,13 +466,13 @@ static int hidinput_get_battery_property(struct power_supply *psy,
 		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;
@@ -482,20 +483,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:
@@ -510,37 +511,54 @@ static int hidinput_get_battery_property(struct power_supply *psy,
 	return ret;
 }
 
+static struct hid_battery *hidinput_find_battery(struct hid_device *dev,
+						 int report_id)
+{
+	struct hid_battery *bat;
+
+	list_for_each_entry(bat, &dev->batteries, list) {
+		if (bat->report_id == report_id)
+			return bat;
+	}
+	return NULL;
+}
+
 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;
 
-	if (dev->battery)
-		return 0;	/* already initialized? */
+	if (hidinput_find_battery(dev, field->report->id))
+		return 0;	/* already initialized */
 
 	quirks = find_battery_quirk(dev);
 
-	hid_dbg(dev, "device %x:%x:%x %d quirks %d\n",
-		dev->bus, dev->vendor, dev->product, dev->version, quirks);
+	hid_dbg(dev, "device %x:%x:%x %d quirks %d report_id %d\n",
+		dev->bus, dev->vendor, dev->product, dev->version, quirks,
+		field->report->id);
 
 	if (quirks & HID_BATTERY_QUIRK_IGNORE)
 		return 0;
 
-	psy_desc = kzalloc(sizeof(*psy_desc), GFP_KERNEL);
+	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)
 		return -ENOMEM;
 
-	psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery",
-				   strlen(dev->uniq) ?
-					dev->uniq : dev_name(&dev->dev));
-	if (!psy_desc->name) {
-		error = -ENOMEM;
-		goto err_free_mem;
-	}
+	psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL,
+					"hid-%s-battery-%d",
+					strlen(dev->uniq) ?
+						dev->uniq : dev_name(&dev->dev),
+					field->report->id);
+	if (!psy_desc->name)
+		return -ENOMEM;
 
 	psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
 	psy_desc->properties = hidinput_battery_props;
@@ -559,98 +577,89 @@ 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;
-
-	dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg);
-	if (IS_ERR(dev->battery)) {
-		error = PTR_ERR(dev->battery);
-		hid_warn(dev, "can't register power supply: %d\n", error);
-		goto err_free_name;
+		bat->avoid_query = true;
+
+	psy_cfg.drv_data = bat;
+	bat->ps = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg);
+	if (IS_ERR(bat->ps)) {
+		hid_warn(dev, "can't register power supply: %ld\n",
+			 PTR_ERR(bat->ps));
+		return PTR_ERR(bat->ps);
 	}
 
-	power_supply_powers(dev->battery, &dev->dev);
-	return 0;
+	power_supply_powers(bat->ps, &dev->dev);
+
+	list_add_tail(&bat->list, &dev->batteries);
 
-err_free_name:
-	kfree(psy_desc->name);
-err_free_mem:
-	kfree(psy_desc);
-	dev->battery = NULL;
-	return error;
+	return 0;
 }
 
 static void hidinput_cleanup_battery(struct hid_device *dev)
 {
-	const struct power_supply_desc *psy_desc;
-
-	if (!dev->battery)
-		return;
+	struct hid_battery *bat, *next;
 
-	psy_desc = dev->battery->desc;
-	power_supply_unregister(dev->battery);
-	kfree(psy_desc->name);
-	kfree(psy_desc);
-	dev->battery = NULL;
+	list_for_each_entry_safe(bat, next, &dev->batteries, list) {
+		list_del(&bat->list);
+	}
 }
 
-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;
 	}
 
 	return false;
 }
 
-static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
-				    int value)
+static void hidinput_update_battery(struct hid_battery *bat,
+				    unsigned int usage, int value)
 {
 	int capacity;
 
-	if (!dev->battery)
-		return;
-
-	if (hidinput_update_battery_charge_status(dev, usage, value)) {
-		power_supply_changed(dev->battery);
+	if (hidinput_update_battery_charge_status(bat, usage, value)) {
+		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_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->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 */
@@ -664,9 +673,10 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
 {
 }
 
-static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
-				    int value)
+static struct hid_battery *hidinput_find_battery(struct hid_device *dev,
+						 int report_id)
 {
+	return NULL;
 }
 #endif	/* CONFIG_HID_BATTERY_STRENGTH */
 
@@ -1533,7 +1543,10 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
 		return;
 
 	if (usage->type == EV_PWR) {
-		hidinput_update_battery(hid, usage->hid, value);
+		struct hid_battery *bat = hidinput_find_battery(hid, report->id);
+
+		if (bat)
+			hidinput_update_battery(bat, usage->hid, value);
 		return;
 	}
 
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 7d4a25c6de0e..b495f7a4bc6c 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -812,19 +812,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_first_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);
diff --git a/include/linux/hid.h b/include/linux/hid.h
index a4ddb94e3ee5..3e33ef74c3c1 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -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)
+ * @ratelimit_time: rate limiting for battery reports
+ * @list: list node for linking into hid_device's battery list
+ */
+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;
+	ktime_t ratelimit_time;
+	struct list_head list;
+};
+
 struct hid_driver;
 struct hid_ll_driver;
 
@@ -670,19 +700,10 @@ 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. Each battery is tracked separately in the
+	 * batteries list.
 	 */
-	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;
-	ktime_t battery_ratelimit_time;
+	struct list_head batteries;
 #endif
 
 	unsigned long status;						/* see STAT flags above */
@@ -743,6 +764,15 @@ 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_first_battery(struct hid_device *hdev)
+{
+	if (list_empty(&hdev->batteries))
+		return NULL;
+	return list_first_entry(&hdev->batteries, struct hid_battery, list);
+}
+#endif
+
 #define HID_GLOBAL_STACK_SIZE 4
 #define HID_COLLECTION_STACK_SIZE 4
 
-- 
2.51.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v3 1/1] HID: input: Add support for multiple batteries per device
  2025-11-19 14:30 ` [PATCH v3 1/1] HID: input: " Lucas Zampieri
@ 2025-11-20  8:17   ` kernel test robot
  2025-11-20  8:40   ` kernel test robot
  1 sibling, 0 replies; 4+ messages in thread
From: kernel test robot @ 2025-11-20  8:17 UTC (permalink / raw)
  To: Lucas Zampieri, linux-input
  Cc: oe-kbuild-all, Lucas Zampieri, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Sebastian Reichel, Bastien Nocera, linux-pm

Hi Lucas,

kernel test robot noticed the following build errors:

[auto build test ERROR on hid/for-next]
[also build test ERROR on dtor-input/for-linus linus/master v6.18-rc6 next-20251119]
[cannot apply to dtor-input/next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Lucas-Zampieri/HID-input-Add-support-for-multiple-batteries-per-device/20251119-223834
base:   https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
patch link:    https://lore.kernel.org/r/20251119143005.1513531-2-lzampier%40redhat.com
patch subject: [PATCH v3 1/1] HID: input: Add support for multiple batteries per device
config: sh-defconfig (https://download.01.org/0day-ci/archive/20251120/202511201624.yUv4VtBv-lkp@intel.com/config)
compiler: sh4-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251120/202511201624.yUv4VtBv-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202511201624.yUv4VtBv-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/hid/hid-input.c: In function 'hidinput_hid_event':
>> drivers/hid/hid-input.c:1550:25: error: implicit declaration of function 'hidinput_update_battery'; did you mean 'hidinput_find_battery'? [-Wimplicit-function-declaration]
    1550 |                         hidinput_update_battery(bat, usage->hid, value);
         |                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                         hidinput_find_battery


vim +1550 drivers/hid/hid-input.c

  1536	
  1537	void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
  1538	{
  1539		struct input_dev *input;
  1540		struct hid_report *report = field->report;
  1541		unsigned *quirks = &hid->quirks;
  1542	
  1543		if (!usage->type)
  1544			return;
  1545	
  1546		if (usage->type == EV_PWR) {
  1547			struct hid_battery *bat = hidinput_find_battery(hid, report->id);
  1548	
  1549			if (bat)
> 1550				hidinput_update_battery(bat, usage->hid, value);
  1551			return;
  1552		}
  1553	
  1554		if (!field->hidinput)
  1555			return;
  1556	
  1557		input = field->hidinput->input;
  1558	
  1559		if (usage->hat_min < usage->hat_max || usage->hat_dir) {
  1560			int hat_dir = usage->hat_dir;
  1561			if (!hat_dir)
  1562				hat_dir = (value - usage->hat_min) * 8 / (usage->hat_max - usage->hat_min + 1) + 1;
  1563			if (hat_dir < 0 || hat_dir > 8) hat_dir = 0;
  1564			input_event(input, usage->type, usage->code    , hid_hat_to_axis[hat_dir].x);
  1565			input_event(input, usage->type, usage->code + 1, hid_hat_to_axis[hat_dir].y);
  1566			return;
  1567		}
  1568	
  1569		/*
  1570		 * Ignore out-of-range values as per HID specification,
  1571		 * section 5.10 and 6.2.25, when NULL state bit is present.
  1572		 * When it's not, clamp the value to match Microsoft's input
  1573		 * driver as mentioned in "Required HID usages for digitizers":
  1574		 * https://msdn.microsoft.com/en-us/library/windows/hardware/dn672278(v=vs.85).asp
  1575		 *
  1576		 * The logical_minimum < logical_maximum check is done so that we
  1577		 * don't unintentionally discard values sent by devices which
  1578		 * don't specify logical min and max.
  1579		 */
  1580		if ((field->flags & HID_MAIN_ITEM_VARIABLE) &&
  1581		    field->logical_minimum < field->logical_maximum) {
  1582			if (field->flags & HID_MAIN_ITEM_NULL_STATE &&
  1583			    (value < field->logical_minimum ||
  1584			     value > field->logical_maximum)) {
  1585				dbg_hid("Ignoring out-of-range value %x\n", value);
  1586				return;
  1587			}
  1588			value = clamp(value,
  1589				      field->logical_minimum,
  1590				      field->logical_maximum);
  1591		}
  1592	
  1593		switch (usage->hid) {
  1594		case HID_DG_ERASER:
  1595			report->tool_active |= !!value;
  1596	
  1597			/*
  1598			 * if eraser is set, we must enforce BTN_TOOL_RUBBER
  1599			 * to accommodate for devices not following the spec.
  1600			 */
  1601			if (value)
  1602				hid_report_set_tool(report, input, BTN_TOOL_RUBBER);
  1603			else if (report->tool != BTN_TOOL_RUBBER)
  1604				/* value is off, tool is not rubber, ignore */
  1605				return;
  1606			else if (*quirks & HID_QUIRK_NOINVERT &&
  1607				 !test_bit(BTN_TOUCH, input->key)) {
  1608				/*
  1609				 * There is no invert to release the tool, let hid_input
  1610				 * send BTN_TOUCH with scancode and release the tool after.
  1611				 */
  1612				hid_report_release_tool(report, input, BTN_TOOL_RUBBER);
  1613				return;
  1614			}
  1615	
  1616			/* let hid-input set BTN_TOUCH */
  1617			break;
  1618	
  1619		case HID_DG_INVERT:
  1620			report->tool_active |= !!value;
  1621	
  1622			/*
  1623			 * If invert is set, we store BTN_TOOL_RUBBER.
  1624			 */
  1625			if (value)
  1626				hid_report_set_tool(report, input, BTN_TOOL_RUBBER);
  1627			else if (!report->tool_active)
  1628				/* tool_active not set means Invert and Eraser are not set */
  1629				hid_report_release_tool(report, input, BTN_TOOL_RUBBER);
  1630	
  1631			/* no further processing */
  1632			return;
  1633	
  1634		case HID_DG_INRANGE:
  1635			report->tool_active |= !!value;
  1636	
  1637			if (report->tool_active) {
  1638				/*
  1639				 * if tool is not set but is marked as active,
  1640				 * assume ours
  1641				 */
  1642				if (!report->tool)
  1643					report->tool = usage->code;
  1644	
  1645				/* drivers may have changed the value behind our back, resend it */
  1646				hid_report_set_tool(report, input, report->tool);
  1647			} else {
  1648				hid_report_release_tool(report, input, usage->code);
  1649			}
  1650	
  1651			/* reset tool_active for the next event */
  1652			report->tool_active = false;
  1653	
  1654			/* no further processing */
  1655			return;
  1656	
  1657		case HID_DG_TIPSWITCH:
  1658			report->tool_active |= !!value;
  1659	
  1660			/* if tool is set to RUBBER we should ignore the current value */
  1661			if (report->tool == BTN_TOOL_RUBBER)
  1662				return;
  1663	
  1664			break;
  1665	
  1666		case HID_DG_TIPPRESSURE:
  1667			if (*quirks & HID_QUIRK_NOTOUCH) {
  1668				int a = field->logical_minimum;
  1669				int b = field->logical_maximum;
  1670	
  1671				if (value > a + ((b - a) >> 3)) {
  1672					input_event(input, EV_KEY, BTN_TOUCH, 1);
  1673					report->tool_active = true;
  1674				}
  1675			}
  1676			break;
  1677	
  1678		case HID_UP_PID | 0x83UL: /* Simultaneous Effects Max */
  1679			dbg_hid("Maximum Effects - %d\n",value);
  1680			return;
  1681	
  1682		case HID_UP_PID | 0x7fUL:
  1683			dbg_hid("PID Pool Report\n");
  1684			return;
  1685		}
  1686	
  1687		switch (usage->type) {
  1688		case EV_KEY:
  1689			if (usage->code == 0) /* Key 0 is "unassigned", not KEY_UNKNOWN */
  1690				return;
  1691			break;
  1692	
  1693		case EV_REL:
  1694			if (usage->code == REL_WHEEL_HI_RES ||
  1695			    usage->code == REL_HWHEEL_HI_RES) {
  1696				hidinput_handle_scroll(usage, input, value);
  1697				return;
  1698			}
  1699			break;
  1700	
  1701		case EV_ABS:
  1702			if ((field->flags & HID_MAIN_ITEM_RELATIVE) &&
  1703			    usage->code == ABS_VOLUME) {
  1704				int count = abs(value);
  1705				int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN;
  1706				int i;
  1707	
  1708				for (i = 0; i < count; i++) {
  1709					input_event(input, EV_KEY, direction, 1);
  1710					input_sync(input);
  1711					input_event(input, EV_KEY, direction, 0);
  1712					input_sync(input);
  1713				}
  1714				return;
  1715	
  1716			} else if (((*quirks & HID_QUIRK_X_INVERT) && usage->code == ABS_X) ||
  1717				   ((*quirks & HID_QUIRK_Y_INVERT) && usage->code == ABS_Y))
  1718				value = field->logical_maximum - value;
  1719			break;
  1720		}
  1721	
  1722		/*
  1723		 * Ignore reports for absolute data if the data didn't change. This is
  1724		 * not only an optimization but also fixes 'dead' key reports. Some
  1725		 * RollOver implementations for localized keys (like BACKSLASH/PIPE; HID
  1726		 * 0x31 and 0x32) report multiple keys, even though a localized keyboard
  1727		 * can only have one of them physically available. The 'dead' keys
  1728		 * report constant 0. As all map to the same keycode, they'd confuse
  1729		 * the input layer. If we filter the 'dead' keys on the HID level, we
  1730		 * skip the keycode translation and only forward real events.
  1731		 */
  1732		if (!(field->flags & (HID_MAIN_ITEM_RELATIVE |
  1733		                      HID_MAIN_ITEM_BUFFERED_BYTE)) &&
  1734				      (field->flags & HID_MAIN_ITEM_VARIABLE) &&
  1735		    usage->usage_index < field->maxusage &&
  1736		    value == field->value[usage->usage_index])
  1737			return;
  1738	
  1739		/* report the usage code as scancode if the key status has changed */
  1740		if (usage->type == EV_KEY &&
  1741		    (!test_bit(usage->code, input->key)) == value)
  1742			input_event(input, EV_MSC, MSC_SCAN, usage->hid);
  1743	
  1744		input_event(input, usage->type, usage->code, value);
  1745	
  1746		if ((field->flags & HID_MAIN_ITEM_RELATIVE) &&
  1747		    usage->type == EV_KEY && value) {
  1748			input_sync(input);
  1749			input_event(input, usage->type, usage->code, 0);
  1750		}
  1751	}
  1752	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v3 1/1] HID: input: Add support for multiple batteries per device
  2025-11-19 14:30 ` [PATCH v3 1/1] HID: input: " Lucas Zampieri
  2025-11-20  8:17   ` kernel test robot
@ 2025-11-20  8:40   ` kernel test robot
  1 sibling, 0 replies; 4+ messages in thread
From: kernel test robot @ 2025-11-20  8:40 UTC (permalink / raw)
  To: Lucas Zampieri, linux-input
  Cc: llvm, oe-kbuild-all, Lucas Zampieri, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Sebastian Reichel, Bastien Nocera, linux-pm

Hi Lucas,

kernel test robot noticed the following build errors:

[auto build test ERROR on hid/for-next]
[also build test ERROR on dtor-input/for-linus linus/master v6.18-rc6 next-20251119]
[cannot apply to dtor-input/next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Lucas-Zampieri/HID-input-Add-support-for-multiple-batteries-per-device/20251119-223834
base:   https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
patch link:    https://lore.kernel.org/r/20251119143005.1513531-2-lzampier%40redhat.com
patch subject: [PATCH v3 1/1] HID: input: Add support for multiple batteries per device
config: um-defconfig (https://download.01.org/0day-ci/archive/20251120/202511201651.tkKTEKpn-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 9e9fe08b16ea2c4d9867fb4974edf2a3776d6ece)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251120/202511201651.tkKTEKpn-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202511201651.tkKTEKpn-lkp@intel.com/

All errors (new ones prefixed by >>):

>> drivers/hid/hid-input.c:1550:4: error: call to undeclared function 'hidinput_update_battery'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
    1550 |                         hidinput_update_battery(bat, usage->hid, value);
         |                         ^
   1 error generated.


vim +/hidinput_update_battery +1550 drivers/hid/hid-input.c

  1536	
  1537	void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
  1538	{
  1539		struct input_dev *input;
  1540		struct hid_report *report = field->report;
  1541		unsigned *quirks = &hid->quirks;
  1542	
  1543		if (!usage->type)
  1544			return;
  1545	
  1546		if (usage->type == EV_PWR) {
  1547			struct hid_battery *bat = hidinput_find_battery(hid, report->id);
  1548	
  1549			if (bat)
> 1550				hidinput_update_battery(bat, usage->hid, value);
  1551			return;
  1552		}
  1553	
  1554		if (!field->hidinput)
  1555			return;
  1556	
  1557		input = field->hidinput->input;
  1558	
  1559		if (usage->hat_min < usage->hat_max || usage->hat_dir) {
  1560			int hat_dir = usage->hat_dir;
  1561			if (!hat_dir)
  1562				hat_dir = (value - usage->hat_min) * 8 / (usage->hat_max - usage->hat_min + 1) + 1;
  1563			if (hat_dir < 0 || hat_dir > 8) hat_dir = 0;
  1564			input_event(input, usage->type, usage->code    , hid_hat_to_axis[hat_dir].x);
  1565			input_event(input, usage->type, usage->code + 1, hid_hat_to_axis[hat_dir].y);
  1566			return;
  1567		}
  1568	
  1569		/*
  1570		 * Ignore out-of-range values as per HID specification,
  1571		 * section 5.10 and 6.2.25, when NULL state bit is present.
  1572		 * When it's not, clamp the value to match Microsoft's input
  1573		 * driver as mentioned in "Required HID usages for digitizers":
  1574		 * https://msdn.microsoft.com/en-us/library/windows/hardware/dn672278(v=vs.85).asp
  1575		 *
  1576		 * The logical_minimum < logical_maximum check is done so that we
  1577		 * don't unintentionally discard values sent by devices which
  1578		 * don't specify logical min and max.
  1579		 */
  1580		if ((field->flags & HID_MAIN_ITEM_VARIABLE) &&
  1581		    field->logical_minimum < field->logical_maximum) {
  1582			if (field->flags & HID_MAIN_ITEM_NULL_STATE &&
  1583			    (value < field->logical_minimum ||
  1584			     value > field->logical_maximum)) {
  1585				dbg_hid("Ignoring out-of-range value %x\n", value);
  1586				return;
  1587			}
  1588			value = clamp(value,
  1589				      field->logical_minimum,
  1590				      field->logical_maximum);
  1591		}
  1592	
  1593		switch (usage->hid) {
  1594		case HID_DG_ERASER:
  1595			report->tool_active |= !!value;
  1596	
  1597			/*
  1598			 * if eraser is set, we must enforce BTN_TOOL_RUBBER
  1599			 * to accommodate for devices not following the spec.
  1600			 */
  1601			if (value)
  1602				hid_report_set_tool(report, input, BTN_TOOL_RUBBER);
  1603			else if (report->tool != BTN_TOOL_RUBBER)
  1604				/* value is off, tool is not rubber, ignore */
  1605				return;
  1606			else if (*quirks & HID_QUIRK_NOINVERT &&
  1607				 !test_bit(BTN_TOUCH, input->key)) {
  1608				/*
  1609				 * There is no invert to release the tool, let hid_input
  1610				 * send BTN_TOUCH with scancode and release the tool after.
  1611				 */
  1612				hid_report_release_tool(report, input, BTN_TOOL_RUBBER);
  1613				return;
  1614			}
  1615	
  1616			/* let hid-input set BTN_TOUCH */
  1617			break;
  1618	
  1619		case HID_DG_INVERT:
  1620			report->tool_active |= !!value;
  1621	
  1622			/*
  1623			 * If invert is set, we store BTN_TOOL_RUBBER.
  1624			 */
  1625			if (value)
  1626				hid_report_set_tool(report, input, BTN_TOOL_RUBBER);
  1627			else if (!report->tool_active)
  1628				/* tool_active not set means Invert and Eraser are not set */
  1629				hid_report_release_tool(report, input, BTN_TOOL_RUBBER);
  1630	
  1631			/* no further processing */
  1632			return;
  1633	
  1634		case HID_DG_INRANGE:
  1635			report->tool_active |= !!value;
  1636	
  1637			if (report->tool_active) {
  1638				/*
  1639				 * if tool is not set but is marked as active,
  1640				 * assume ours
  1641				 */
  1642				if (!report->tool)
  1643					report->tool = usage->code;
  1644	
  1645				/* drivers may have changed the value behind our back, resend it */
  1646				hid_report_set_tool(report, input, report->tool);
  1647			} else {
  1648				hid_report_release_tool(report, input, usage->code);
  1649			}
  1650	
  1651			/* reset tool_active for the next event */
  1652			report->tool_active = false;
  1653	
  1654			/* no further processing */
  1655			return;
  1656	
  1657		case HID_DG_TIPSWITCH:
  1658			report->tool_active |= !!value;
  1659	
  1660			/* if tool is set to RUBBER we should ignore the current value */
  1661			if (report->tool == BTN_TOOL_RUBBER)
  1662				return;
  1663	
  1664			break;
  1665	
  1666		case HID_DG_TIPPRESSURE:
  1667			if (*quirks & HID_QUIRK_NOTOUCH) {
  1668				int a = field->logical_minimum;
  1669				int b = field->logical_maximum;
  1670	
  1671				if (value > a + ((b - a) >> 3)) {
  1672					input_event(input, EV_KEY, BTN_TOUCH, 1);
  1673					report->tool_active = true;
  1674				}
  1675			}
  1676			break;
  1677	
  1678		case HID_UP_PID | 0x83UL: /* Simultaneous Effects Max */
  1679			dbg_hid("Maximum Effects - %d\n",value);
  1680			return;
  1681	
  1682		case HID_UP_PID | 0x7fUL:
  1683			dbg_hid("PID Pool Report\n");
  1684			return;
  1685		}
  1686	
  1687		switch (usage->type) {
  1688		case EV_KEY:
  1689			if (usage->code == 0) /* Key 0 is "unassigned", not KEY_UNKNOWN */
  1690				return;
  1691			break;
  1692	
  1693		case EV_REL:
  1694			if (usage->code == REL_WHEEL_HI_RES ||
  1695			    usage->code == REL_HWHEEL_HI_RES) {
  1696				hidinput_handle_scroll(usage, input, value);
  1697				return;
  1698			}
  1699			break;
  1700	
  1701		case EV_ABS:
  1702			if ((field->flags & HID_MAIN_ITEM_RELATIVE) &&
  1703			    usage->code == ABS_VOLUME) {
  1704				int count = abs(value);
  1705				int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN;
  1706				int i;
  1707	
  1708				for (i = 0; i < count; i++) {
  1709					input_event(input, EV_KEY, direction, 1);
  1710					input_sync(input);
  1711					input_event(input, EV_KEY, direction, 0);
  1712					input_sync(input);
  1713				}
  1714				return;
  1715	
  1716			} else if (((*quirks & HID_QUIRK_X_INVERT) && usage->code == ABS_X) ||
  1717				   ((*quirks & HID_QUIRK_Y_INVERT) && usage->code == ABS_Y))
  1718				value = field->logical_maximum - value;
  1719			break;
  1720		}
  1721	
  1722		/*
  1723		 * Ignore reports for absolute data if the data didn't change. This is
  1724		 * not only an optimization but also fixes 'dead' key reports. Some
  1725		 * RollOver implementations for localized keys (like BACKSLASH/PIPE; HID
  1726		 * 0x31 and 0x32) report multiple keys, even though a localized keyboard
  1727		 * can only have one of them physically available. The 'dead' keys
  1728		 * report constant 0. As all map to the same keycode, they'd confuse
  1729		 * the input layer. If we filter the 'dead' keys on the HID level, we
  1730		 * skip the keycode translation and only forward real events.
  1731		 */
  1732		if (!(field->flags & (HID_MAIN_ITEM_RELATIVE |
  1733		                      HID_MAIN_ITEM_BUFFERED_BYTE)) &&
  1734				      (field->flags & HID_MAIN_ITEM_VARIABLE) &&
  1735		    usage->usage_index < field->maxusage &&
  1736		    value == field->value[usage->usage_index])
  1737			return;
  1738	
  1739		/* report the usage code as scancode if the key status has changed */
  1740		if (usage->type == EV_KEY &&
  1741		    (!test_bit(usage->code, input->key)) == value)
  1742			input_event(input, EV_MSC, MSC_SCAN, usage->hid);
  1743	
  1744		input_event(input, usage->type, usage->code, value);
  1745	
  1746		if ((field->flags & HID_MAIN_ITEM_RELATIVE) &&
  1747		    usage->type == EV_KEY && value) {
  1748			input_sync(input);
  1749			input_event(input, usage->type, usage->code, 0);
  1750		}
  1751	}
  1752	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2025-11-20  8:41 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-19 14:30 [PATCH v3 0/1] HID: Add support for multiple batteries per device Lucas Zampieri
2025-11-19 14:30 ` [PATCH v3 1/1] HID: input: " Lucas Zampieri
2025-11-20  8:17   ` kernel test robot
2025-11-20  8:40   ` kernel test robot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).