Skip to content

Commit

Permalink
HID: apple: Report Magic Keyboard battery over USB
Browse files Browse the repository at this point in the history
When connected over USB, the Apple Magic Keyboard 2015 registers 3
different interfaces. One of them is used to report the battery level.

However, unlike when connected over Bluetooth, the battery level is not
reported automatically and it is required to fetch it manually.

Add a new quirk to fix the battery report descriptor and a timer to
fetch the battery level.

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
  • Loading branch information
José Expósito authored and Jiri Kosina committed Nov 19, 2021
1 parent 7f52ece commit 6e14329
Showing 1 changed file with 85 additions and 2 deletions.
87 changes: 85 additions & 2 deletions drivers/hid/hid-apple.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

#include <linux/device.h>
#include <linux/hid.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/timer.h>

#include "hid-ids.h"

Expand All @@ -30,10 +32,12 @@
#define APPLE_INVERT_HWHEEL BIT(6)
/* BIT(7) reserved, was: APPLE_IGNORE_HIDINPUT */
#define APPLE_NUMLOCK_EMULATION BIT(8)
#define APPLE_RDESC_BATTERY BIT(9)

#define APPLE_FLAG_FKEY 0x01

#define HID_COUNTRY_INTERNATIONAL_ISO 13
#define APPLE_BATTERY_TIMEOUT_MS 60000

static unsigned int fnmode = 1;
module_param(fnmode, uint, 0644);
Expand All @@ -58,10 +62,12 @@ MODULE_PARM_DESC(swap_fn_leftctrl, "Swap the Fn and left Control keys. "
"[0] = as-is, Mac layout, 1 = swapped, PC layout)");

struct apple_sc {
struct hid_device *hdev;
unsigned long quirks;
unsigned int fn_on;
unsigned int fn_found;
DECLARE_BITMAP(pressed_numlock, KEY_CNT);
struct timer_list battery_timer;
};

struct apple_key_translation {
Expand Down Expand Up @@ -333,6 +339,43 @@ static int apple_event(struct hid_device *hdev, struct hid_field *field,
return 0;
}

static int apple_fetch_battery(struct hid_device *hdev)
{
#ifdef CONFIG_HID_BATTERY_STRENGTH
struct apple_sc *asc = hid_get_drvdata(hdev);
struct hid_report_enum *report_enum;
struct hid_report *report;

if (!(asc->quirks & APPLE_RDESC_BATTERY) || !hdev->battery)
return -1;

report_enum = &hdev->report_enum[hdev->battery_report_type];
report = report_enum->report_id_hash[hdev->battery_report_id];

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
return -1;
#endif
}

static void apple_battery_timer_tick(struct timer_list *t)
{
struct apple_sc *asc = from_timer(asc, t, battery_timer);
struct hid_device *hdev = asc->hdev;

if (apple_fetch_battery(hdev) == 0) {
mod_timer(&asc->battery_timer,
jiffies + msecs_to_jiffies(APPLE_BATTERY_TIMEOUT_MS));
}
}

/*
* MacBook JIS keyboard has wrong logical maximum
* Magic Keyboard JIS has wrong logical maximum
Expand All @@ -354,6 +397,30 @@ static __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
"fixing up MacBook JIS keyboard report descriptor\n");
rdesc[53] = rdesc[59] = 0xe7;
}

/*
* Change the usage from:
* 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 0
* 0x09, 0x0b, // Usage (Vendor Usage 0x0b) 3
* To:
* 0x05, 0x01, // Usage Page (Generic Desktop) 0
* 0x09, 0x06, // Usage (Keyboard) 2
*/
if ((asc->quirks & APPLE_RDESC_BATTERY) && *rsize == 83 &&
rdesc[46] == 0x84 && rdesc[58] == 0x85) {
hid_info(hdev,
"fixing up Magic Keyboard battery report descriptor\n");
*rsize = *rsize - 1;
rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
if (!rdesc)
return NULL;

rdesc[0] = 0x05;
rdesc[1] = 0x01;
rdesc[2] = 0x09;
rdesc[3] = 0x06;
}

return rdesc;
}

Expand Down Expand Up @@ -447,6 +514,7 @@ static int apple_probe(struct hid_device *hdev,
return -ENOMEM;
}

asc->hdev = hdev;
asc->quirks = quirks;

hid_set_drvdata(hdev, asc);
Expand All @@ -463,9 +531,23 @@ static int apple_probe(struct hid_device *hdev,
return ret;
}

timer_setup(&asc->battery_timer, apple_battery_timer_tick, 0);
mod_timer(&asc->battery_timer,
jiffies + msecs_to_jiffies(APPLE_BATTERY_TIMEOUT_MS));
apple_fetch_battery(hdev);

return 0;
}

static void apple_remove(struct hid_device *hdev)
{
struct apple_sc *asc = hid_get_drvdata(hdev);

del_timer_sync(&asc->battery_timer);

hid_hw_stop(hdev);
}

static const struct hid_device_id apple_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE),
.driver_data = APPLE_MIGHTYMOUSE | APPLE_INVERT_HWHEEL },
Expand Down Expand Up @@ -540,11 +622,11 @@ static const struct hid_device_id apple_devices[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
Expand Down Expand Up @@ -650,6 +732,7 @@ static struct hid_driver apple_driver = {
.id_table = apple_devices,
.report_fixup = apple_report_fixup,
.probe = apple_probe,
.remove = apple_remove,
.event = apple_event,
.input_mapping = apple_input_mapping,
.input_mapped = apple_input_mapped,
Expand Down

0 comments on commit 6e14329

Please sign in to comment.