public inbox for linux-input@vger.kernel.org
 help / color / mirror / Atom feed
From: Dmitri Ollari <dmitri.ollari@protonmail.com>
To: linux-input@vger.kernel.org
Cc: jikos@kernel.org, Dmitri Ollari <dmitri.ollari@protonmail.com>
Subject: [PATCH] HID: magicmouse: enable battery polling for 2024 Magic Trackpad
Date: Sat, 11 Apr 2026 16:38:12 +0000	[thread overview]
Message-ID: <20260411163806.35759-1-dmitri.ollari@protonmail.com> (raw)


[-- Attachment #1.1: Type: text/plain, Size: 6501 bytes --]

The 2024 Magic Trackpad USB-C (PID 0x0324) does not report battery
strength via HID descriptor fields over Bluetooth. Instead it requires
an explicit HID_REQ_GET_REPORT request to retrieve the battery level.

This patch makes the following changes:

1. Replace the battery_timer (timer_list) with battery_work (delayed_work)
   so that HID_REQ_GET_REPORT can be issued from a sleepable context.
   Timers run in atomic context and cannot block, which caused deadlocks
   on the Bluetooth transport path.

2. Extend the fetch guard and probe scheduling block to include the 2024
   Magic Trackpad USB-C when connected over Bluetooth (vendor 0x004C,
   product 0x0324 via BT_VENDOR_ID_APPLE).

3. Schedule battery_work immediately at probe (delay=0) instead of
   issuing a direct magicmouse_fetch_battery() call. The direct call
   bypassed the cold-start correction logic and could publish a stale
   value before the work handler had a chance to validate it.

4. Add a cold-start
 double-poll: the device may return a stale battery
   value (e.g. 4%) on the very first GET_REPORT after power-on. On the
   first successful poll battery_validated is set and a second poll is
   scheduled 3 seconds later to obtain the real value. Subsequent polls
   use the normal 60-second interval.

5. Remove the early-return guard that skipped polling when
   battery_capacity equalled battery_max. This prevented the second
   corrective poll from firing when the first stale response happened
   to equal 100.

Signed-off-by: Dmitri Ollari <dmitri.ollari@protonmail.com>
---
 hid-magicmouse.c | 55 +++++++++++++++++++++++++++++-------------------
 1 file changed, 33 insertions(+), 22 deletions(-)

diff --git a/hid-magicmouse.c b/hid-magicmouse.c
index 9eadf32..bc9c467 100644
--- a/hid-magicmouse.c
+++ b/hid-magicmouse.c
@@ -123,7 +123,10 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
  * @tracking_ids: Mapping of current touch 
input data to @touches.
  * @hdev: Pointer to the underlying HID device.
  * @work: Workqueue to handle initialization retry for quirky devices.
- * @battery_timer: Timer for obtaining battery level information.
+ * @battery_work: Delayed work for periodic battery level polling.
+ * @battery_validated: Set after the first successful poll; gates the
+ *	second poll that corrects the stale value the device may report
+ *	on cold start.
  */
 struct magicmouse_sc {
 	struct input_dev *input;
@@ -148,7 +151,8 @@ struct magicmouse_sc {
 
 	struct hid_device *hdev;
 	struct delayed_work work;
-	struct timer_list battery_timer;
+	struct delayed_work battery_work;
+	bool battery_validated;
 };
 
 static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -820,7 +824,8 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
 
 	if (!hdev->battery ||
 	    (!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
-	     !is_usb_magictrackpad2(hdev->vendor, hdev->p
roduct)))
+	     !is_usb_magictrackpad2(hdev->vendor, hdev->product) &&
+	     hdev->product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)) /* 2024 Magic Trackpad USB-C over Bluetooth */
 		return -1;
 
 	report_enum = &hdev->report_enum[hdev->battery_report_type];
@@ -829,9 +834,6 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
 	if (!report || report->maxfield < 1)
 		return -1;
 
-	if (hdev->battery_capacity == hdev->battery_max)
-		return -1;
-
 	hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
 	return 0;
 #else
@@ -839,14 +841,23 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
 #endif
 }
 
-static void magicmouse_battery_timer_tick(struct timer_list *t)
+static void magicmouse_battery_work(struct work_struct *work)
 {
-	struct magicmouse_sc *msc = timer_container_of(msc, t, battery_timer);
+	struct magicmouse_sc *msc = container_of(work, struct magicmouse_sc, battery_work.work);
 	struct hid_device *hdev = msc->hdev;
 
 	i
f (magicmouse_fetch_battery(hdev) == 0) {
-		mod_timer(&msc->battery_timer,
-			  jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
+		if (!msc->battery_validated) {
+			/* The device may return a stale value (e.g. 4%) on the
+			 * first GET_REPORT after cold start. Schedule a second
+			 * poll shortly after to get the real value, then settle
+			 * into the normal 60s interval.
+			 */
+			msc->battery_validated = true;
+			schedule_delayed_work(&msc->battery_work, secs_to_jiffies(3));
+		} else {
+			schedule_delayed_work(&msc->battery_work, secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
+		}
 	}
 }
 
@@ -866,6 +877,7 @@ static int magicmouse_probe(struct hid_device *hdev,
 	msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
 	msc->hdev = hdev;
 	INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);
+	INIT_DELAYED_WORK(&msc->battery_work, magicmouse_battery_work);
 
 	msc->quirks = id->driver_data;
 	hid_set_drvdata(hdev, msc);
@@ -883,11 +895,13 @@ static int
 magicmouse_probe(struct hid_device *hdev,
 	}
 
 	if (is_usb_magicmouse2(id->vendor, id->product) ||
-	    is_usb_magictrackpad2(id->vendor, id->product)) {
-		timer_setup(&msc->battery_timer, magicmouse_battery_timer_tick, 0);
-		mod_timer(&msc->battery_timer,
-			  jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
-		magicmouse_fetch_battery(hdev);
+	    is_usb_magictrackpad2(id->vendor, id->product) ||
+	    id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
+		/* Schedule immediately so battery_work runs ASAP, sets battery_validated,
+		 * then reschedules every 60s. Avoids direct fetch which bypasses
+		 * battery_validated and would publish a stale startup value.
+		 */
+		schedule_delayed_work(&msc->battery_work, 0);
 	}
 
 	if (is_usb_magicmouse2(id->vendor, id->product) ||
@@ -955,10 +969,8 @@ static int magicmouse_probe(struct hid_device *hdev,
 
 	return 0;
 err_stop_hw:
-	if (is_usb_magicmouse2(id->vendor, id->product) ||
-	    is_usb_magi
ctrackpad2(id->vendor, id->product))
-		timer_delete_sync(&msc->battery_timer);
-
+	/* Clean up battery work on error */
+	cancel_delayed_work_sync(&msc->battery_work);
 	hid_hw_stop(hdev);
 	return ret;
 }
@@ -969,9 +981,8 @@ static void magicmouse_remove(struct hid_device *hdev)
 
 	if (msc) {
 		cancel_delayed_work_sync(&msc->work);
-		if (is_usb_magicmouse2(hdev->vendor, hdev->product) ||
-		    is_usb_magictrackpad2(hdev->vendor, hdev->product))
-			timer_delete_sync(&msc->battery_timer);
+		/* Cancel battery polling on device removal */
+		cancel_delayed_work_sync(&msc->battery_work);
 	}
 
 	hid_hw_stop(hdev);
-- 
2.53.0


[-- Attachment #1.2: publickey - dmitri.ollari@protonmail.com - 0xF53BC391.asc --]
[-- Type: application/pgp-keys, Size: 722 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 322 bytes --]

                 reply	other threads:[~2026-04-11 16:38 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260411163806.35759-1-dmitri.ollari@protonmail.com \
    --to=dmitri.ollari@protonmail.com \
    --cc=jikos@kernel.org \
    --cc=linux-input@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox