[PATCH] HID: magicmouse: enable battery polling for 2024 Magic Trackpad

Dmitri Ollari
2026-04-11T16:38:25Z

[-- 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 --]