diff --git a/4001-Input-bcm5974-Add-support-for-the-T2-Macs.patch b/4001-Input-bcm5974-Add-support-for-the-T2-Macs.patch deleted file mode 100644 index 521f541..0000000 --- a/4001-Input-bcm5974-Add-support-for-the-T2-Macs.patch +++ /dev/null @@ -1,175 +0,0 @@ -From 327e6e1d0f6e8db68c124dff4d6a326b381ccedb Mon Sep 17 00:00:00 2001 -From: Aun-Ali Zaidi -Date: Wed, 23 Mar 2022 17:12:21 +0530 -Subject: [PATCH] Input: bcm5974 - Add support for the T2 Macs - ---- - drivers/input/mouse/bcm5974.c | 138 ++++++++++++++++++++++++++++++++++ - 1 file changed, 138 insertions(+) - -diff --git a/drivers/input/mouse/bcm5974.c b/drivers/input/mouse/bcm5974.c -index 59a14505b..88f17f21a 100644 ---- a/drivers/input/mouse/bcm5974.c -+++ b/drivers/input/mouse/bcm5974.c -@@ -83,6 +83,24 @@ - #define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO 0x0273 - #define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS 0x0274 - -+/* T2-Attached Devices */ -+/* MacbookAir8,1 (2018) */ -+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K 0x027a -+/* MacbookPro15,2 (2018) */ -+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 0x027b -+/* MacbookPro15,1 (2018) */ -+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 0x027c -+/* MacbookPro15,4 (2019) */ -+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213 0x027d -+/* MacbookPro16,2 (2020) */ -+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K 0x027e -+/* MacbookPro16,3 (2020) */ -+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 0x027f -+/* MacbookAir9,1 (2020) */ -+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K 0x0280 -+/* MacbookPro16,1 (2019)*/ -+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F 0x0340 -+ - #define BCM5974_DEVICE(prod) { \ - .match_flags = (USB_DEVICE_ID_MATCH_DEVICE | \ - USB_DEVICE_ID_MATCH_INT_CLASS | \ -@@ -147,6 +165,22 @@ static const struct usb_device_id bcm5974_table[] = { - BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI), - BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_ISO), - BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_JIS), -+ /* MacbookAir8,1 */ -+ BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K), -+ /* MacbookPro15,2 */ -+ BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132), -+ /* MacbookPro15,1 */ -+ BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680), -+ /* MacbookPro15,4 */ -+ BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213), -+ /* MacbookPro16,2 */ -+ BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K), -+ /* MacbookPro16,3 */ -+ BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223), -+ /* MacbookAir9,1 */ -+ BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K), -+ /* MacbookPro16,1 */ -+ BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F), - /* Terminating entry */ - {} - }; -@@ -483,6 +517,110 @@ static const struct bcm5974_config bcm5974_config_table[] = { - { SN_COORD, -203, 6803 }, - { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } - }, -+ { -+ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K, -+ 0, -+ 0, -+ HAS_INTEGRATED_BUTTON, -+ 0, sizeof(struct bt_data), -+ 0x83, DATAFORMAT(TYPE4), -+ { SN_PRESSURE, 0, 300 }, -+ { SN_WIDTH, 0, 2048 }, -+ { SN_COORD, -6243, 6749 }, -+ { SN_COORD, -170, 7685 }, -+ { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } -+ }, -+ { -+ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132, -+ 0, -+ 0, -+ HAS_INTEGRATED_BUTTON, -+ 0, sizeof(struct bt_data), -+ 0x83, DATAFORMAT(TYPE4), -+ { SN_PRESSURE, 0, 300 }, -+ { SN_WIDTH, 0, 2048 }, -+ { SN_COORD, -6243, 6749 }, -+ { SN_COORD, -170, 7685 }, -+ { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } -+ }, -+ { -+ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680, -+ 0, -+ 0, -+ HAS_INTEGRATED_BUTTON, -+ 0, sizeof(struct bt_data), -+ 0x83, DATAFORMAT(TYPE4), -+ { SN_PRESSURE, 0, 300 }, -+ { SN_WIDTH, 0, 2048 }, -+ { SN_COORD, -7456, 7976 }, -+ { SN_COORD, -1768, 7685 }, -+ { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } -+ }, -+ { -+ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213, -+ 0, -+ 0, -+ HAS_INTEGRATED_BUTTON, -+ 0, sizeof(struct bt_data), -+ 0x83, DATAFORMAT(TYPE4), -+ { SN_PRESSURE, 0, 300 }, -+ { SN_WIDTH, 0, 2048 }, -+ { SN_COORD, -6243, 6749 }, -+ { SN_COORD, -170, 7685 }, -+ { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } -+ }, -+ { -+ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K, -+ 0, -+ 0, -+ HAS_INTEGRATED_BUTTON, -+ 0, sizeof(struct bt_data), -+ 0x83, DATAFORMAT(TYPE4), -+ { SN_PRESSURE, 0, 300 }, -+ { SN_WIDTH, 0, 2048 }, -+ { SN_COORD, -7823, 8329 }, -+ { SN_COORD, -370, 7925 }, -+ { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } -+ }, -+ { -+ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223, -+ 0, -+ 0, -+ HAS_INTEGRATED_BUTTON, -+ 0, sizeof(struct bt_data), -+ 0x83, DATAFORMAT(TYPE4), -+ { SN_PRESSURE, 0, 300 }, -+ { SN_WIDTH, 0, 2048 }, -+ { SN_COORD, -6243, 6749 }, -+ { SN_COORD, -170, 7685 }, -+ { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } -+ }, -+ { -+ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K, -+ 0, -+ 0, -+ HAS_INTEGRATED_BUTTON, -+ 0, sizeof(struct bt_data), -+ 0x83, DATAFORMAT(TYPE4), -+ { SN_PRESSURE, 0, 300 }, -+ { SN_WIDTH, 0, 2048 }, -+ { SN_COORD, -6243, 6749 }, -+ { SN_COORD, -170, 7685 }, -+ { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } -+ }, -+ { -+ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F, -+ 0, -+ 0, -+ HAS_INTEGRATED_BUTTON, -+ 0, sizeof(struct bt_data), -+ 0x83, DATAFORMAT(TYPE4), -+ { SN_PRESSURE, 0, 300 }, -+ { SN_WIDTH, 0, 2048 }, -+ { SN_COORD, -8916, 9918 }, -+ { SN_COORD, -1934, 9835 }, -+ { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } -+ }, - {} - }; - --- -2.25.1 - diff --git a/4001-asahi-trackpad.patch b/4001-asahi-trackpad.patch new file mode 100644 index 0000000..e4dbad1 --- /dev/null +++ b/4001-asahi-trackpad.patch @@ -0,0 +1,5592 @@ +From 161b747e6a58a251865c6dcbcdf413f7a1ce2a5e Mon Sep 17 00:00:00 2001 +From: Asahi Lina +Date: Sun, 2 Feb 2025 22:48:47 +0900 +Subject: [PATCH 01/26] soc: apple: rtkit: Pass the crashlog to the crashed() + callback + +Client drivers might want a copy of the crashlog to stash into a +devcoredump blob. Since device memory management can be very variable, +the actual devcoredump implementation is left to client drivers. Pass +the raw crashlog buffer to the client callback so it can use it if +desired. + +Signed-off-by: Asahi Lina +Reviewed-by: Jens Axboe +Reviewed-by: Sven Peter +--- + drivers/nvme/host/apple.c | 2 +- + drivers/soc/apple/rtkit.c | 2 +- + include/linux/soc/apple/rtkit.h | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c +index 4319ab50c..f46b3f179 100644 +--- a/drivers/nvme/host/apple.c ++++ b/drivers/nvme/host/apple.c +@@ -221,7 +221,7 @@ static unsigned int apple_nvme_queue_depth(struct apple_nvme_queue *q) + return APPLE_ANS_MAX_QUEUE_DEPTH; + } + +-static void apple_nvme_rtkit_crashed(void *cookie) ++static void apple_nvme_rtkit_crashed(void *cookie, const void *crashlog, size_t crashlog_size) + { + struct apple_nvme *anv = cookie; + +diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c +index e6d940292..4b0783091 100644 +--- a/drivers/soc/apple/rtkit.c ++++ b/drivers/soc/apple/rtkit.c +@@ -368,7 +368,7 @@ static void apple_rtkit_crashlog_rx(struct apple_rtkit *rtk, u64 msg) + + rtk->crashed = true; + if (rtk->ops->crashed) +- rtk->ops->crashed(rtk->cookie); ++ rtk->ops->crashed(rtk->cookie, bfr, rtk->crashlog_buffer.size); + } + + static void apple_rtkit_ioreport_rx(struct apple_rtkit *rtk, u64 msg) +diff --git a/include/linux/soc/apple/rtkit.h b/include/linux/soc/apple/rtkit.h +index c06d17599..736f53018 100644 +--- a/include/linux/soc/apple/rtkit.h ++++ b/include/linux/soc/apple/rtkit.h +@@ -56,7 +56,7 @@ struct apple_rtkit_shmem { + * context. + */ + struct apple_rtkit_ops { +- void (*crashed)(void *cookie); ++ void (*crashed)(void *cookie, const void *crashlog, size_t crashlog_size); + void (*recv_message)(void *cookie, u8 endpoint, u64 message); + bool (*recv_message_early)(void *cookie, u8 endpoint, u64 message); + int (*shmem_setup)(void *cookie, struct apple_rtkit_shmem *bfr); +-- +2.43.0 + + +From 2e55a601b04cc24a220454c0a2e1462f5c0fa004 Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Sun, 12 Dec 2021 20:40:04 +0100 +Subject: [PATCH 02/26] HID: add device IDs for Apple SPI HID devices + +Apple Silicon based laptop use SPI as transport for HID. Add support for +SPI-based HID devices and and Apple keyboard and trackpad devices. +Intel based laptops using the keyboard input driver applespi use the +same HID over SPI protocol and can be supported later. + +This requires SPI keyboard/mouse HID types since Apple's intenal +keyboards/trackpads use the same product id. + +Signed-off-by: Janne Grunau +--- + drivers/hid/hid-core.c | 3 +++ + drivers/hid/hid-ids.h | 5 +++++ + include/linux/hid.h | 6 +++++- + 3 files changed, 13 insertions(+), 1 deletion(-) + +diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c +index 5ab8e10bb..534c49f9e 100644 +--- a/drivers/hid/hid-core.c ++++ b/drivers/hid/hid-core.c +@@ -2297,6 +2297,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) + case BUS_I2C: + bus = "I2C"; + break; ++ case BUS_SPI: ++ bus = "SPI"; ++ break; + case BUS_VIRTUAL: + bus = "VIRTUAL"; + break; +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +index d1d479ca5..c1c573b49 100644 +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -89,6 +89,7 @@ + + #define USB_VENDOR_ID_APPLE 0x05ac + #define BT_VENDOR_ID_APPLE 0x004c ++#define SPI_VENDOR_ID_APPLE 0x05ac + #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 + #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d + #define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269 +@@ -188,6 +189,10 @@ + #define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021 0x029f + #define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102 + #define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302 ++#define SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020 0x0281 ++#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020 0x0341 ++#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021 0x0342 ++#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021 0x0343 + + #define USB_VENDOR_ID_ASUS 0x0486 + #define USB_DEVICE_ID_ASUS_T91MT 0x0185 +diff --git a/include/linux/hid.h b/include/linux/hid.h +index cdc0dc13c..1d54bba08 100644 +--- a/include/linux/hid.h ++++ b/include/linux/hid.h +@@ -590,7 +590,9 @@ struct hid_input { + enum hid_type { + HID_TYPE_OTHER = 0, + HID_TYPE_USBMOUSE, +- HID_TYPE_USBNONE ++ HID_TYPE_USBNONE, ++ HID_TYPE_SPI_KEYBOARD, ++ HID_TYPE_SPI_MOUSE, + }; + + enum hid_battery_status { +@@ -750,6 +752,8 @@ struct hid_descriptor { + .bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod) + #define HID_I2C_DEVICE(ven, prod) \ + .bus = BUS_I2C, .vendor = (ven), .product = (prod) ++#define HID_SPI_DEVICE(ven, prod) \ ++ .bus = BUS_SPI, .vendor = (ven), .product = (prod) + + #define HID_REPORT_ID(rep) \ + .report_type = (rep) +-- +2.43.0 + + +From fd2b65a5ee9277f57102bb0562624d266dd5c71b Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Thu, 16 Dec 2021 21:15:31 +0100 +Subject: [PATCH 03/26] HID: apple: add support for internal keyboards + +Apple MacBook keyboards started using HID over SPI in 2015. With the +addition of the SPI HID transport they can be supported by this driver. +Support all product ids over with the Apple SPI vendor id for now. +Individual product ids will have to be added for a correct Fn/function +key mapping. + +Enable by default on the Apple Arm platform. + +Signed-off-by: Janne Grunau +--- + drivers/hid/Kconfig | 2 +- + drivers/hid/hid-apple.c | 6 ++++++ + 2 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 4d2a89d65..d2ea530f5 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -129,7 +129,7 @@ config HID_APPLE + tristate "Apple {i,Power,Mac}Books" + depends on LEDS_CLASS + depends on NEW_LEDS +- default !EXPERT ++ default !EXPERT || SPI_HID_APPLE + help + Support for some Apple devices which less or more break + HID specification. +diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c +index 7e1ae2a2b..2f435d543 100644 +--- a/drivers/hid/hid-apple.c ++++ b/drivers/hid/hid-apple.c +@@ -903,6 +903,10 @@ static int apple_probe(struct hid_device *hdev, + struct apple_sc *asc; + int ret; + ++ if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE && ++ hdev->type != HID_TYPE_SPI_KEYBOARD) ++ return -ENODEV; ++ + asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL); + if (asc == NULL) { + hid_err(hdev, "can't alloc apple descriptor\n"); +@@ -1158,6 +1162,8 @@ static const struct hid_device_id apple_devices[] = { + .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_2021), + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, ++ { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), ++ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT), + .driver_data = APPLE_MAGIC_BACKLIGHT }, + +-- +2.43.0 + + +From 62ed87980185b4139ef1814291b3a4ad4c4cba44 Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Sun, 19 Dec 2021 18:08:15 +0100 +Subject: [PATCH 04/26] HID: apple: add Fn key mapping for Apple silicon + MacBooks + +Signed-off-by: Janne Grunau +--- + drivers/hid/hid-apple.c | 25 +++++++++++++++++++++++++ + 1 file changed, 25 insertions(+) + +diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c +index 2f435d543..c7f77de62 100644 +--- a/drivers/hid/hid-apple.c ++++ b/drivers/hid/hid-apple.c +@@ -276,6 +276,28 @@ static const struct apple_key_translation apple_fn_keys[] = { + { } + }; + ++static const struct apple_key_translation apple_fn_keys_spi[] = { ++ { KEY_BACKSPACE, KEY_DELETE }, ++ { KEY_ENTER, KEY_INSERT }, ++ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, ++ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY }, ++ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY }, ++ { KEY_F4, KEY_SEARCH, APPLE_FLAG_FKEY }, ++ { KEY_F5, KEY_RECORD, APPLE_FLAG_FKEY }, ++ { KEY_F6, KEY_SLEEP, APPLE_FLAG_FKEY }, ++ { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY }, ++ { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY }, ++ { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY }, ++ { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY }, ++ { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY }, ++ { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY }, ++ { KEY_UP, KEY_PAGEUP }, ++ { KEY_DOWN, KEY_PAGEDOWN }, ++ { KEY_LEFT, KEY_HOME }, ++ { KEY_RIGHT, KEY_END }, ++ { } ++}; ++ + static const struct apple_key_translation powerbook_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, +@@ -491,6 +513,8 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, + else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI && + hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) + table = macbookair_fn_keys; ++ else if (hid->vendor == SPI_VENDOR_ID_APPLE) ++ table = apple_fn_keys_spi; + else if (hid->product < 0x21d || hid->product >= 0x300) + table = powerbook_fn_keys; + else +@@ -670,6 +694,7 @@ static void apple_setup_input(struct input_dev *input) + + /* Enable all needed keys */ + apple_setup_key_translation(input, apple_fn_keys); ++ apple_setup_key_translation(input, apple_fn_keys_spi); + apple_setup_key_translation(input, powerbook_fn_keys); + apple_setup_key_translation(input, powerbook_numlock_keys); + apple_setup_key_translation(input, apple_iso_keyboard); +-- +2.43.0 + + +From 44c297094ad8dbc0f35aa9a3ce85ba6d22507240 Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Wed, 5 Jan 2022 23:27:34 +0100 +Subject: [PATCH 05/26] HID: apple: add Fn key mapping for Macbook Pro with + touchbar + +Signed-off-by: Janne Grunau +--- + drivers/hid/hid-apple.c | 26 ++++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c +index c7f77de62..c772305ce 100644 +--- a/drivers/hid/hid-apple.c ++++ b/drivers/hid/hid-apple.c +@@ -298,6 +298,28 @@ static const struct apple_key_translation apple_fn_keys_spi[] = { + { } + }; + ++static const struct apple_key_translation apple_fn_keys_mbp13[] = { ++ { KEY_BACKSPACE, KEY_DELETE }, ++ { KEY_ENTER, KEY_INSERT }, ++ { KEY_UP, KEY_PAGEUP }, ++ { KEY_DOWN, KEY_PAGEDOWN }, ++ { KEY_LEFT, KEY_HOME }, ++ { KEY_RIGHT, KEY_END }, ++ { KEY_1, KEY_F1 }, ++ { KEY_2, KEY_F2 }, ++ { KEY_3, KEY_F3 }, ++ { KEY_4, KEY_F4 }, ++ { KEY_5, KEY_F5 }, ++ { KEY_6, KEY_F6 }, ++ { KEY_7, KEY_F7 }, ++ { KEY_8, KEY_F8 }, ++ { KEY_9, KEY_F9 }, ++ { KEY_0, KEY_F10 }, ++ { KEY_MINUS, KEY_F11 }, ++ { KEY_EQUAL, KEY_F12 }, ++ { } ++}; ++ + static const struct apple_key_translation powerbook_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, +@@ -513,6 +535,9 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, + else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI && + hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) + table = macbookair_fn_keys; ++ else if (hid->vendor == SPI_VENDOR_ID_APPLE && ++ hid->product == SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020) ++ table = apple_fn_keys_mbp13; + else if (hid->vendor == SPI_VENDOR_ID_APPLE) + table = apple_fn_keys_spi; + else if (hid->product < 0x21d || hid->product >= 0x300) +@@ -695,6 +720,7 @@ static void apple_setup_input(struct input_dev *input) + /* Enable all needed keys */ + apple_setup_key_translation(input, apple_fn_keys); + apple_setup_key_translation(input, apple_fn_keys_spi); ++ apple_setup_key_translation(input, apple_fn_keys_mbp13); + apple_setup_key_translation(input, powerbook_fn_keys); + apple_setup_key_translation(input, powerbook_numlock_keys); + apple_setup_key_translation(input, apple_iso_keyboard); +-- +2.43.0 + + +From 8b91c27550d6a1f9c753061156fe923b091d7615 Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Thu, 16 Dec 2021 00:10:51 +0100 +Subject: [PATCH 06/26] HID: magicmouse: use a define of the max number of + touch contacts + +Signed-off-by: Janne Grunau +--- + drivers/hid/hid-magicmouse.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index ec110dea8..bcc8bdcb9 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -61,6 +61,8 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define DOUBLE_REPORT_ID 0xf7 + #define USB_BATTERY_TIMEOUT_MS 60000 + ++#define MAX_CONTACTS 16 ++ + /* These definitions are not precise, but they're close enough. (Bits + * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem + * to be some kind of bit mask -- 0x20 may be a near-field reading, +@@ -142,8 +144,8 @@ struct magicmouse_sc { + u8 size; + bool scroll_x_active; + bool scroll_y_active; +- } touches[16]; +- int tracking_ids[16]; ++ } touches[MAX_CONTACTS]; ++ int tracking_ids[MAX_CONTACTS]; + + struct hid_device *hdev; + struct delayed_work work; +@@ -606,7 +608,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd + + __set_bit(EV_ABS, input->evbit); + +- error = input_mt_init_slots(input, 16, mt_flags); ++ error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags); + if (error) + return error; + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2, +-- +2.43.0 + + +From fbe5a490ca40b9636d2ca9df78d71399d16f309e Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Thu, 16 Dec 2021 00:12:35 +0100 +Subject: [PATCH 07/26] HID: magicmouse: use struct input_mt_pos for X/Y + +Signed-off-by: Janne Grunau +--- + drivers/hid/hid-magicmouse.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index bcc8bdcb9..8636cf9c1 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -120,6 +120,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + * @ntouches: Number of touches in most recent touch report. + * @scroll_accel: Number of consecutive scroll motions. + * @scroll_jiffies: Time of last scroll motion. ++ * @pos: multi touch position data of the last report. + * @touches: Most recent data for a touch, indexed by tracking ID. + * @tracking_ids: Mapping of current touch input data to @touches. + * @hdev: Pointer to the underlying HID device. +@@ -134,9 +135,8 @@ struct magicmouse_sc { + int scroll_accel; + unsigned long scroll_jiffies; + ++ struct input_mt_pos pos[MAX_CONTACTS]; + struct { +- short x; +- short y; + short scroll_x; + short scroll_y; + short scroll_x_hr; +@@ -193,7 +193,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) + } else if (last_state != 0) { + state = last_state; + } else if ((id = magicmouse_firm_touch(msc)) >= 0) { +- int x = msc->touches[id].x; ++ int x = msc->pos[id].x; + if (x < middle_button_start) + state = 1; + else if (x > middle_button_stop) +@@ -256,8 +256,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda + + /* Store tracking ID and other fields. */ + msc->tracking_ids[raw_id] = id; +- msc->touches[id].x = x; +- msc->touches[id].y = y; ++ msc->pos[id].x = x; ++ msc->pos[id].y = y; + msc->touches[id].size = size; + + /* If requested, emulate a scroll wheel by detecting small +-- +2.43.0 + + +From 28010896127212fa764885abe667a418d07d2a9b Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Thu, 16 Dec 2021 00:15:30 +0100 +Subject: [PATCH 08/26] HID: magicmouse: use ops function pointers for input + functionality + +Will be used for supporting MacBook trackpads connected via SPI. + +Signed-off-by: Janne Grunau +--- + drivers/hid/hid-magicmouse.c | 32 +++++++++++++++++++++++++++++++- + 1 file changed, 31 insertions(+), 1 deletion(-) + +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index 8636cf9c1..73e86bf45 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -113,6 +113,13 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define TRACKPAD2_RES_Y \ + ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) + ++ ++struct magicmouse_input_ops { ++ int (*raw_event)(struct hid_device *hdev, ++ struct hid_report *report, u8 *data, int size); ++ int (*setup_input)(struct input_dev *input, struct hid_device *hdev); ++}; ++ + /** + * struct magicmouse_sc - Tracks Magic Mouse-specific data. + * @input: Input device through which we report events. +@@ -126,6 +133,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + * @hdev: Pointer to the underlying HID device. + * @work: Workqueue to handle initialization retry for quirky devices. + * @battery_timer: Timer for obtaining battery level information. ++ * @input_ops: Input ops based on device type. + */ + struct magicmouse_sc { + struct input_dev *input; +@@ -150,6 +158,7 @@ struct magicmouse_sc { + struct hid_device *hdev; + struct delayed_work work; + struct timer_list battery_timer; ++ struct magicmouse_input_ops input_ops; + }; + + static int magicmouse_firm_touch(struct magicmouse_sc *msc) +@@ -386,6 +395,14 @@ static int magicmouse_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) + { + struct magicmouse_sc *msc = hid_get_drvdata(hdev); ++ ++ return msc->input_ops.raw_event(hdev, report, data, size); ++} ++ ++static int magicmouse_raw_event_usb(struct hid_device *hdev, ++ struct hid_report *report, u8 *data, int size) ++{ ++ struct magicmouse_sc *msc = hid_get_drvdata(hdev); + struct input_dev *input = msc->input; + int x = 0, y = 0, ii, clicks = 0, npoints; + +@@ -533,7 +550,17 @@ static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, + return 0; + } + +-static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) ++ ++static int magicmouse_setup_input(struct input_dev *input, ++ struct hid_device *hdev) ++{ ++ struct magicmouse_sc *msc = hid_get_drvdata(hdev); ++ ++ return msc->input_ops.setup_input(input, hdev); ++} ++ ++static int magicmouse_setup_input_usb(struct input_dev *input, ++ struct hid_device *hdev) + { + int error; + int mt_flags = 0; +@@ -829,6 +856,9 @@ static int magicmouse_probe(struct hid_device *hdev, + return -ENOMEM; + } + ++ msc->input_ops.raw_event = magicmouse_raw_event_usb; ++ msc->input_ops.setup_input = magicmouse_setup_input_usb; ++ + msc->scroll_accel = SCROLL_ACCEL_DEFAULT; + msc->hdev = hdev; + INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work); +-- +2.43.0 + + +From 805d164feabab6ef8fc2fdd5b2ed1e3d8a1497bf Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Thu, 16 Dec 2021 01:17:48 +0100 +Subject: [PATCH 09/26] HID: magicmouse: add support for Macbook trackpads + +The trackpads in Macbooks beginning in 2015 are HID devices connected +over SPI. On Intel Macbooks they are currently supported by applespi.c. +This chang adds support for the trackpads on Apple Silicon Macbooks +starting in late 2020. They use a new HID over SPI transport driver. +The touch report format differs from USB/BT Magic Trackpads. It is the +same format as the type 4 format supported by bcm5974.c. + +Signed-off-by: Janne Grunau +--- + drivers/hid/Kconfig | 4 +- + drivers/hid/hid-magicmouse.c | 259 ++++++++++++++++++++++++++++++++++- + 2 files changed, 260 insertions(+), 3 deletions(-) + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index d2ea530f5..2f6658702 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -687,11 +687,13 @@ config LOGIWHEELS_FF + + config HID_MAGICMOUSE + tristate "Apple Magic Mouse/Trackpad multi-touch support" ++ default SPI_HID_APPLE + help + Support for the Apple Magic Mouse/Trackpad multi-touch. + + Say Y here if you want support for the multi-touch features of the +- Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad. ++ Apple Wireless "Magic" Mouse, the Apple Wireless "Magic" Trackpad and ++ force touch Trackpads in Macbooks starting from 2015. + + config HID_MALTRON + tristate "Maltron L90 keyboard" +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index 73e86bf45..94acee33f 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -113,6 +113,18 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define TRACKPAD2_RES_Y \ + ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) + ++#define J314_TP_DIMENSION_X (float)13000 ++#define J314_TP_MIN_X -5900 ++#define J314_TP_MAX_X 6500 ++#define J314_TP_RES_X \ ++ ((J314_TP_MAX_X - J314_TP_MIN_X) / (J314_TP_DIMENSION_X / 100)) ++#define J314_TP_DIMENSION_Y (float)8100 ++#define J314_TP_MIN_Y -200 ++#define J314_TP_MAX_Y 7400 ++#define J314_TP_RES_Y \ ++ ((J314_TP_MAX_Y - J314_TP_MIN_Y) / (J314_TP_DIMENSION_Y / 100)) ++ ++#define J314_TP_MAX_FINGER_ORIENTATION 16384 + + struct magicmouse_input_ops { + int (*raw_event)(struct hid_device *hdev, +@@ -533,6 +545,157 @@ static int magicmouse_raw_event_usb(struct hid_device *hdev, + return 1; + } + ++/** ++ * struct tp_finger - single trackpad finger structure, le16-aligned ++ * ++ * @unknown1: unknown ++ * @unknown2: unknown ++ * @abs_x: absolute x coordinate ++ * @abs_y: absolute y coordinate ++ * @rel_x: relative x coordinate ++ * @rel_y: relative y coordinate ++ * @tool_major: tool area, major axis ++ * @tool_minor: tool area, minor axis ++ * @orientation: 16384 when point, else 15 bit angle ++ * @touch_major: touch area, major axis ++ * @touch_minor: touch area, minor axis ++ * @unused: zeros ++ * @pressure: pressure on forcetouch touchpad ++ * @multi: one finger: varies, more fingers: constant ++ * @crc16: on last finger: crc over the whole message struct ++ * (i.e. message header + this struct) minus the last ++ * @crc16 field; unknown on all other fingers. ++ */ ++struct tp_finger { ++ __le16 unknown1; ++ __le16 unknown2; ++ __le16 abs_x; ++ __le16 abs_y; ++ __le16 rel_x; ++ __le16 rel_y; ++ __le16 tool_major; ++ __le16 tool_minor; ++ __le16 orientation; ++ __le16 touch_major; ++ __le16 touch_minor; ++ __le16 unused[2]; ++ __le16 pressure; ++ __le16 multi; ++} __attribute__((packed, aligned(2))); ++ ++/** ++ * struct trackpad report ++ * ++ * @report_id: reportid ++ * @buttons: HID Usage Buttons 3 1-bit reports ++ * @num_fingers: the number of fingers being reported in @fingers ++ * @clicked: same as @buttons ++ */ ++struct tp_header { ++ // HID mouse report ++ u8 report_id; ++ u8 buttons; ++ u8 rel_x; ++ u8 rel_y; ++ u8 padding[4]; ++ // HID vendor part, up to 1751 bytes ++ u8 unknown[22]; ++ u8 num_fingers; ++ u8 clicked; ++ u8 unknown3[14]; ++}; ++ ++static inline int le16_to_int(__le16 x) ++{ ++ return (signed short)le16_to_cpu(x); ++} ++ ++static void report_finger_data(struct input_dev *input, int slot, ++ const struct input_mt_pos *pos, ++ const struct tp_finger *f) ++{ ++ input_mt_slot(input, slot); ++ input_mt_report_slot_state(input, MT_TOOL_FINGER, true); ++ ++ input_report_abs(input, ABS_MT_TOUCH_MAJOR, ++ le16_to_int(f->touch_major) << 1); ++ input_report_abs(input, ABS_MT_TOUCH_MINOR, ++ le16_to_int(f->touch_minor) << 1); ++ input_report_abs(input, ABS_MT_WIDTH_MAJOR, ++ le16_to_int(f->tool_major) << 1); ++ input_report_abs(input, ABS_MT_WIDTH_MINOR, ++ le16_to_int(f->tool_minor) << 1); ++ input_report_abs(input, ABS_MT_ORIENTATION, ++ J314_TP_MAX_FINGER_ORIENTATION - le16_to_int(f->orientation)); ++ input_report_abs(input, ABS_MT_PRESSURE, le16_to_int(f->pressure)); ++ input_report_abs(input, ABS_MT_POSITION_X, pos->x); ++ input_report_abs(input, ABS_MT_POSITION_Y, pos->y); ++} ++ ++static int magicmouse_raw_event_spi(struct hid_device *hdev, ++ struct hid_report *report, u8 *data, int size) ++{ ++ struct magicmouse_sc *msc = hid_get_drvdata(hdev); ++ struct input_dev *input = msc->input; ++ struct tp_header *tp_hdr; ++ struct tp_finger *f; ++ int i, n; ++ u32 npoints; ++ const size_t hdr_sz = sizeof(struct tp_header); ++ const size_t touch_sz = sizeof(struct tp_finger); ++ u8 map_contacs[MAX_CONTACTS]; ++ ++ // hid_warn(hdev, "%s\n", __func__); ++ // print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data, ++ // size, false); ++ ++ if (data[0] != TRACKPAD2_USB_REPORT_ID) ++ return 0; ++ ++ /* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */ ++ if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0) ++ return 0; ++ ++ tp_hdr = (struct tp_header *)data; ++ ++ npoints = (size - hdr_sz) / touch_sz; ++ if (npoints < tp_hdr->num_fingers || npoints > MAX_CONTACTS) { ++ hid_warn(hdev, ++ "unexpected number of touches (%u) for " ++ "report\n", ++ npoints); ++ return 0; ++ } ++ ++ n = 0; ++ for (i = 0; i < tp_hdr->num_fingers; i++) { ++ f = (struct tp_finger *)(data + hdr_sz + i * touch_sz); ++ if (le16_to_int(f->touch_major) == 0) ++ continue; ++ ++ hid_dbg(hdev, "ev x:%04x y:%04x\n", le16_to_int(f->abs_x), ++ le16_to_int(f->abs_y)); ++ msc->pos[n].x = le16_to_int(f->abs_x); ++ msc->pos[n].y = -le16_to_int(f->abs_y); ++ map_contacs[n] = i; ++ n++; ++ } ++ ++ input_mt_assign_slots(input, msc->tracking_ids, msc->pos, n, 0); ++ ++ for (i = 0; i < n; i++) { ++ int idx = map_contacs[i]; ++ f = (struct tp_finger *)(data + hdr_sz + idx * touch_sz); ++ report_finger_data(input, msc->tracking_ids[i], &msc->pos[i], f); ++ } ++ ++ input_mt_sync_frame(input); ++ input_report_key(input, BTN_MOUSE, data[1] & 1); ++ ++ input_sync(input); ++ return 1; ++} ++ + static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) + { +@@ -717,6 +880,79 @@ static int magicmouse_setup_input_usb(struct input_dev *input, + return 0; + } + ++static int magicmouse_setup_input_spi(struct input_dev *input, ++ struct hid_device *hdev) ++{ ++ int error; ++ int mt_flags = 0; ++ ++ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); ++ __clear_bit(BTN_0, input->keybit); ++ __clear_bit(BTN_RIGHT, input->keybit); ++ __clear_bit(BTN_MIDDLE, input->keybit); ++ __clear_bit(EV_REL, input->evbit); ++ __clear_bit(REL_X, input->relbit); ++ __clear_bit(REL_Y, input->relbit); ++ ++ mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK; ++ ++ /* finger touch area */ ++ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 5000, 0, 0); ++ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 5000, 0, 0); ++ ++ /* finger approach area */ ++ input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 5000, 0, 0); ++ input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 5000, 0, 0); ++ ++ /* Note: Touch Y position from the device is inverted relative ++ * to how pointer motion is reported (and relative to how USB ++ * HID recommends the coordinates work). This driver keeps ++ * the origin at the same position, and just uses the additive ++ * inverse of the reported Y. ++ */ ++ ++ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 6000, 0, 0); ++ ++ /* ++ * This makes libinput recognize this as a PressurePad and ++ * stop trying to use pressure for touch size. Pressure unit ++ * seems to be ~grams on these touchpads. ++ */ ++ input_abs_set_res(input, ABS_MT_PRESSURE, 1); ++ ++ /* finger orientation */ ++ input_set_abs_params(input, ABS_MT_ORIENTATION, -J314_TP_MAX_FINGER_ORIENTATION, ++ J314_TP_MAX_FINGER_ORIENTATION, 0, 0); ++ ++ /* finger position */ ++ input_set_abs_params(input, ABS_MT_POSITION_X, J314_TP_MIN_X, J314_TP_MAX_X, ++ 0, 0); ++ /* Y axis is inverted */ ++ input_set_abs_params(input, ABS_MT_POSITION_Y, -J314_TP_MAX_Y, -J314_TP_MIN_Y, ++ 0, 0); ++ ++ /* X/Y resolution */ ++ input_abs_set_res(input, ABS_MT_POSITION_X, J314_TP_RES_X); ++ input_abs_set_res(input, ABS_MT_POSITION_Y, J314_TP_RES_Y); ++ ++ input_set_events_per_packet(input, 60); ++ ++ /* touchpad button */ ++ input_set_capability(input, EV_KEY, BTN_MOUSE); ++ ++ /* ++ * hid-input may mark device as using autorepeat, but the trackpad does ++ * not actually want it. ++ */ ++ __clear_bit(EV_REP, input->evbit); ++ ++ error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags); ++ if (error) ++ return error; ++ ++ return 0; ++} ++ + static int magicmouse_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +@@ -775,6 +1011,9 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) + feature_size = sizeof(feature_mt_trackpad2_usb); + feature = feature_mt_trackpad2_usb; + } ++ } else if (hdev->vendor == SPI_VENDOR_ID_APPLE) { ++ feature_size = sizeof(feature_mt_trackpad2_usb); ++ feature = feature_mt_trackpad2_usb; + } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { + feature_size = sizeof(feature_mt_mouse2); + feature = feature_mt_mouse2; +@@ -850,14 +1089,26 @@ static int magicmouse_probe(struct hid_device *hdev, + struct hid_report *report; + int ret; + ++ if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE && ++ hdev->type != HID_TYPE_SPI_MOUSE) ++ return -ENODEV; ++ + msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL); + if (msc == NULL) { + hid_err(hdev, "can't alloc magicmouse descriptor\n"); + return -ENOMEM; + } + +- msc->input_ops.raw_event = magicmouse_raw_event_usb; +- msc->input_ops.setup_input = magicmouse_setup_input_usb; ++ // internal trackpad use a data format use input ops to avoid ++ // conflicts with the report ID. ++ if (id->vendor == SPI_VENDOR_ID_APPLE) { ++ msc->input_ops.raw_event = magicmouse_raw_event_spi; ++ msc->input_ops.setup_input = magicmouse_setup_input_spi; ++ ++ } else { ++ msc->input_ops.raw_event = magicmouse_raw_event_usb; ++ msc->input_ops.setup_input = magicmouse_setup_input_usb; ++ } + + msc->scroll_accel = SCROLL_ACCEL_DEFAULT; + msc->hdev = hdev; +@@ -910,6 +1161,8 @@ static int magicmouse_probe(struct hid_device *hdev, + else /* USB_VENDOR_ID_APPLE */ + report = hid_register_report(hdev, HID_INPUT_REPORT, + TRACKPAD2_USB_REPORT_ID, 0); ++ } else if (id->vendor == SPI_VENDOR_ID_APPLE) { ++ report = hid_register_report(hdev, HID_INPUT_REPORT, 2, 0); + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + report = hid_register_report(hdev, HID_INPUT_REPORT, + TRACKPAD_REPORT_ID, 0); +@@ -1009,6 +1262,8 @@ static const struct hid_device_id magic_mice[] = { + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 }, ++ { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), ++ .driver_data = 0 }, + { } + }; + MODULE_DEVICE_TABLE(hid, magic_mice); +-- +2.43.0 + + +From 63a61d072a5cd08190b29576f0c11b8694b61d2b Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Fri, 10 Dec 2021 19:38:43 +0100 +Subject: [PATCH 10/26] WIP: HID: transport: spi: add Apple SPI transport + +Keyboard and trackpad of Apple Sillicon SoCs (M1, M1 Pro/Max) laptops +are are HID devices connected via SPI. + +This is the same protocol as implemented by applespi.c. It was not +noticed that protocol is a transport for HID. Adding support for ACPI +based Intel MacBooks will be done in a separate commit. + +How HID is mapped in this protocol is not yet fully understood. + +Microsoft has a specification for HID over SPI [1] incompatible with the +transport protocol used by Apple. + +[1] https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/hid-over-spi + +Signed-off-by: Janne Grunau +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/spi-hid/Kconfig | 26 + + drivers/hid/spi-hid/Makefile | 10 + + drivers/hid/spi-hid/spi-hid-apple-core.c | 1033 ++++++++++++++++++++++ + drivers/hid/spi-hid/spi-hid-apple-of.c | 136 +++ + drivers/hid/spi-hid/spi-hid-apple.h | 31 + + 7 files changed, 1240 insertions(+) + create mode 100644 drivers/hid/spi-hid/Kconfig + create mode 100644 drivers/hid/spi-hid/Makefile + create mode 100644 drivers/hid/spi-hid/spi-hid-apple-core.c + create mode 100644 drivers/hid/spi-hid/spi-hid-apple-of.c + create mode 100644 drivers/hid/spi-hid/spi-hid-apple.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 2f6658702..85cbe1e7c 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1388,4 +1388,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" + + source "drivers/hid/surface-hid/Kconfig" + ++source "drivers/hid/spi-hid/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 24de45f36..f4b2e84c7 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -171,3 +171,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ ++ ++obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/ +diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig +new file mode 100644 +index 000000000..8e37f0fec +--- /dev/null ++++ b/drivers/hid/spi-hid/Kconfig +@@ -0,0 +1,26 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++menu "SPI HID support" ++ depends on SPI ++ ++config SPI_HID_APPLE_OF ++ tristate "HID over SPI transport layer for Apple Silicon SoCs" ++ default ARCH_APPLE ++ depends on SPI && INPUT && OF ++ help ++ Say Y here if you use Apple Silicon based laptop. The keyboard and ++ touchpad are HID based devices connected via SPI. ++ ++ If unsure, say N. ++ ++ This support is also available as a module. If so, the module ++ will be called spi-hid-apple-of. It will also build/depend on the ++ module spi-hid-apple. ++ ++endmenu ++ ++config SPI_HID_APPLE_CORE ++ tristate ++ default y if SPI_HID_APPLE_OF=y ++ default m if SPI_HID_APPLE_OF=m ++ select HID ++ select CRC16 +diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile +new file mode 100644 +index 000000000..f276ee12c +--- /dev/null ++++ b/drivers/hid/spi-hid/Makefile +@@ -0,0 +1,10 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++# ++# Makefile for SPI HID tarnsport drivers ++# ++ ++obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid-apple.o ++ ++spi-hid-apple-objs = spi-hid-apple-core.o ++ ++obj-$(CONFIG_SPI_HID_APPLE_OF) += spi-hid-apple-of.o +diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c +new file mode 100644 +index 000000000..67c70edbe +--- /dev/null ++++ b/drivers/hid/spi-hid/spi-hid-apple-core.c +@@ -0,0 +1,1033 @@ ++/* ++ * SPDX-License-Identifier: GPL-2.0 ++ * ++ * Apple SPI HID transport driver ++ * ++ * Copyright (C) The Asahi Linux Contributors ++ * ++ * Based on: drivers/input/applespi.c ++ * ++ * MacBook (Pro) SPI keyboard and touchpad driver ++ * ++ * Copyright (c) 2015-2018 Federico Lorenzi ++ * Copyright (c) 2017-2018 Ronald Tschalär ++ * ++ */ ++ ++//#define DEBUG 2 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "spi-hid-apple.h" ++ ++#define SPIHID_DEF_WAIT msecs_to_jiffies(1000) ++ ++#define SPIHID_MAX_INPUT_REPORT_SIZE 0x800 ++ ++/* support only keyboard, trackpad and management dev for now */ ++#define SPIHID_MAX_DEVICES 3 ++ ++#define SPIHID_DEVICE_ID_MNGT 0x0 ++#define SPIHID_DEVICE_ID_KBD 0x1 ++#define SPIHID_DEVICE_ID_TP 0x2 ++#define SPIHID_DEVICE_ID_INFO 0xd0 ++ ++#define SPIHID_READ_PACKET 0x20 ++#define SPIHID_WRITE_PACKET 0x40 ++ ++#define SPIHID_DESC_MAX 512 ++ ++#define SPIHID_SET_LEDS 0x0151 /* caps lock */ ++ ++#define SPI_RW_CHG_DELAY_US 200 /* 'Inter Stage Us'? */ ++ ++static const u8 spi_hid_apple_booted[4] = { 0xa0, 0x80, 0x00, 0x00 }; ++static const u8 spi_hid_apple_status_ok[4] = { 0xac, 0x27, 0x68, 0xd5 }; ++ ++struct spihid_interface { ++ struct hid_device *hid; ++ u8 *hid_desc; ++ u32 hid_desc_len; ++ u32 id; ++ unsigned country; ++ u32 max_control_report_len; ++ u32 max_input_report_len; ++ u32 max_output_report_len; ++ u8 name[32]; ++ bool ready; ++}; ++ ++struct spihid_input_report { ++ u8 *buf; ++ u32 length; ++ u32 offset; ++ u8 device; ++ u8 flags; ++}; ++ ++struct spihid_apple { ++ struct spi_device *spidev; ++ ++ struct spihid_apple_ops *ops; ++ ++ struct spihid_interface mngt; ++ struct spihid_interface kbd; ++ struct spihid_interface tp; ++ ++ wait_queue_head_t wait; ++ struct mutex tx_lock; //< protects against concurrent SPI writes ++ ++ struct spi_message rx_msg; ++ struct spi_message tx_msg; ++ struct spi_transfer rx_transfer; ++ struct spi_transfer tx_transfer; ++ struct spi_transfer status_transfer; ++ ++ u8 *rx_buf; ++ u8 *tx_buf; ++ u8 *status_buf; ++ ++ u8 vendor[32]; ++ u8 product[64]; ++ u8 serial[32]; ++ ++ u32 num_devices; ++ ++ u32 vendor_id; ++ u32 product_id; ++ u32 version_number; ++ ++ u8 msg_id; ++ ++ /* fragmented HID report */ ++ struct spihid_input_report report; ++ ++ /* state tracking flags */ ++ bool status_booted; ++}; ++ ++/** ++ * struct spihid_msg_hdr - common header of protocol messages. ++ * ++ * Each message begins with fixed header, followed by a message-type specific ++ * payload, and ends with a 16-bit crc. Because of the varying lengths of the ++ * payload, the crc is defined at the end of each payload struct, rather than ++ * in this struct. ++ * ++ * @unknown0: request type? output, input (0x10), feature, protocol ++ * @unknown1: maybe report id? ++ * @unknown2: mostly zero, in info request maybe device num ++ * @msgid: incremented on each message, rolls over after 255; there is a ++ * separate counter for each message type. ++ * @rsplen: response length (the exact nature of this field is quite ++ * speculative). On a request/write this is often the same as ++ * @length, though in some cases it has been seen to be much larger ++ * (e.g. 0x400); on a response/read this the same as on the ++ * request; for reads that are not responses it is 0. ++ * @length: length of the remainder of the data in the whole message ++ * structure (after re-assembly in case of being split over ++ * multiple spi-packets), minus the trailing crc. The total size ++ * of a message is therefore @length + 10. ++ */ ++ ++struct spihid_msg_hdr { ++ u8 unknown0; ++ u8 unknown1; ++ u8 unknown2; ++ u8 id; ++ __le16 rsplen; ++ __le16 length; ++}; ++ ++/** ++ * struct spihid_transfer_packet - a complete spi packet; always 256 bytes. This carries ++ * the (parts of the) message in the data. But note that this does not ++ * necessarily contain a complete message, as in some cases (e.g. many ++ * fingers pressed) the message is split over multiple packets (see the ++ * @offset, @remain, and @length fields). In general the data parts in ++ * spihid_transfer_packet's are concatenated until @remaining is 0, and the ++ * result is an message. ++ * ++ * @flags: 0x40 = write (to device), 0x20 = read (from device); note that ++ * the response to a write still has 0x40. ++ * @device: 1 = keyboard, 2 = touchpad ++ * @offset: specifies the offset of this packet's data in the complete ++ * message; i.e. > 0 indicates this is a continuation packet (in ++ * the second packet for a message split over multiple packets ++ * this would then be the same as the @length in the first packet) ++ * @remain: number of message bytes remaining in subsequents packets (in ++ * the first packet of a message split over two packets this would ++ * then be the same as the @length in the second packet) ++ * @length: length of the valid data in the @data in this packet ++ * @data: all or part of a message ++ * @crc16: crc over this whole structure minus this @crc16 field. This ++ * covers just this packet, even on multi-packet messages (in ++ * contrast to the crc in the message). ++ */ ++struct spihid_transfer_packet { ++ u8 flags; ++ u8 device; ++ __le16 offset; ++ __le16 remain; ++ __le16 length; ++ u8 data[246]; ++ __le16 crc16; ++}; ++ ++/* ++ * how HID is mapped onto the protocol is not fully clear. This are the known ++ * reports/request: ++ * ++ * pkt.flags pkt.dev? msg.u0 msg.u1 msg.u2 ++ * info 0x40 0xd0 0x20 0x01 0xd0 ++ * ++ * info mngt: 0x40 0xd0 0x20 0x10 0x00 ++ * info kbd: 0x40 0xd0 0x20 0x10 0x01 ++ * info tp: 0x40 0xd0 0x20 0x10 0x02 ++ * ++ * desc kbd: 0x40 0xd0 0x20 0x10 0x01 ++ * desc trackpad: 0x40 0xd0 0x20 0x10 0x02 ++ * ++ * mt mode: 0x40 0x02 0x52 0x02 0x00 set protocol? ++ * capslock led 0x40 0x01 0x51 0x01 0x00 output report ++ * ++ * report kbd: 0x20 0x01 0x10 0x01 0x00 input report ++ * report tp: 0x20 0x02 0x10 0x02 0x00 input report ++ * ++ */ ++ ++ ++static int spihid_apple_request(struct spihid_apple *spihid, u8 target, u8 unk0, ++ u8 unk1, u8 unk2, u16 resp_len, u8 *buf, ++ size_t len) ++{ ++ struct spihid_transfer_packet *pkt; ++ struct spihid_msg_hdr *hdr; ++ u16 crc; ++ int err; ++ ++ /* know reports are small enoug to fit in a single packet */ ++ if (len > sizeof(pkt->data) - sizeof(*hdr) - sizeof(__le16)) ++ return -EINVAL; ++ ++ err = mutex_lock_interruptible(&spihid->tx_lock); ++ if (err < 0) ++ return err; ++ ++ pkt = (struct spihid_transfer_packet *)spihid->tx_buf; ++ ++ memset(pkt, 0, sizeof(*pkt)); ++ pkt->flags = SPIHID_WRITE_PACKET; ++ pkt->device = target; ++ pkt->length = cpu_to_le16(sizeof(*hdr) + len + sizeof(__le16)); ++ ++ hdr = (struct spihid_msg_hdr *)&pkt->data[0]; ++ hdr->unknown0 = unk0; ++ hdr->unknown1 = unk1; ++ hdr->unknown2 = unk2; ++ hdr->id = spihid->msg_id++; ++ hdr->rsplen = cpu_to_le16(resp_len); ++ hdr->length = cpu_to_le16(len); ++ ++ if (len) ++ memcpy(pkt->data + sizeof(*hdr), buf, len); ++ crc = crc16(0, &pkt->data[0], sizeof(*hdr) + len); ++ put_unaligned_le16(crc, pkt->data + sizeof(*hdr) + len); ++ ++ pkt->crc16 = cpu_to_le16(crc16(0, spihid->tx_buf, ++ offsetof(struct spihid_transfer_packet, crc16))); ++ ++ err = spi_sync(spihid->spidev, &spihid->tx_msg); ++ mutex_unlock(&spihid->tx_lock); ++ if (err < 0) ++ return err; ++ ++ return (int)len; ++} ++ ++static struct spihid_apple *spihid_get_data(struct spihid_interface *idev) ++{ ++ switch (idev->id) { ++ case SPIHID_DEVICE_ID_KBD: ++ return container_of(idev, struct spihid_apple, kbd); ++ case SPIHID_DEVICE_ID_TP: ++ return container_of(idev, struct spihid_apple, tp); ++ default: ++ return NULL; ++ } ++} ++ ++static int apple_ll_start(struct hid_device *hdev) ++{ ++ /* no-op SPI transport is already setup */ ++ return 0; ++}; ++ ++static void apple_ll_stop(struct hid_device *hdev) ++{ ++ /* no-op, devices will be desstroyed on driver destruction */ ++} ++ ++static int apple_ll_open(struct hid_device *hdev) ++{ ++ struct spihid_apple *spihid; ++ struct spihid_interface *idev = hdev->driver_data; ++ ++ if (idev->hid_desc_len == 0) { ++ spihid = spihid_get_data(idev); ++ dev_warn(&spihid->spidev->dev, ++ "HID descriptor missing for dev %u", idev->id); ++ } else ++ idev->ready = true; ++ ++ return 0; ++} ++ ++static void apple_ll_close(struct hid_device *hdev) ++{ ++ struct spihid_interface *idev = hdev->driver_data; ++ idev->ready = false; ++} ++ ++static int apple_ll_parse(struct hid_device *hdev) ++{ ++ struct spihid_interface *idev = hdev->driver_data; ++ ++ return hid_parse_report(hdev, idev->hid_desc, idev->hid_desc_len); ++} ++ ++static int apple_ll_raw_request(struct hid_device *hdev, ++ unsigned char reportnum, __u8 *buf, size_t len, ++ unsigned char rtype, int reqtype) ++{ ++ struct spihid_interface *idev = hdev->driver_data; ++ struct spihid_apple *spihid = spihid_get_data(idev); ++ ++ dev_dbg(&spihid->spidev->dev, ++ "apple_ll_raw_request: device:%u reportnum:%hhu rtype:%hhu", ++ idev->id, reportnum, rtype); ++ ++ switch (reqtype) { ++ case HID_REQ_GET_REPORT: ++ return -EINVAL; // spihid_get_raw_report(); ++ case HID_REQ_SET_REPORT: ++ if (buf[0] != reportnum) ++ return -EINVAL; ++ if (reportnum != idev->id) { ++ dev_warn(&spihid->spidev->dev, ++ "device:%u reportnum:" ++ "%hhu mismatch", ++ idev->id, reportnum); ++ return -EINVAL; ++ } ++ return spihid_apple_request(spihid, idev->id, 0x52, reportnum, 0x00, 2, buf, len); ++ default: ++ return -EIO; ++ } ++} ++ ++static int apple_ll_output_report(struct hid_device *hdev, __u8 *buf, ++ size_t len) ++{ ++ struct spihid_interface *idev = hdev->driver_data; ++ struct spihid_apple *spihid = spihid_get_data(idev); ++ if (!spihid) ++ return -1; ++ ++ dev_dbg(&spihid->spidev->dev, ++ "apple_ll_output_report: device:%u len:%zu:", ++ idev->id, len); ++ // second idev->id should maybe be buf[0]? ++ return spihid_apple_request(spihid, idev->id, 0x51, idev->id, 0x00, 0, buf, len); ++} ++ ++static struct hid_ll_driver apple_hid_ll = { ++ .start = &apple_ll_start, ++ .stop = &apple_ll_stop, ++ .open = &apple_ll_open, ++ .close = &apple_ll_close, ++ .parse = &apple_ll_parse, ++ .raw_request = &apple_ll_raw_request, ++ .output_report = &apple_ll_output_report, ++}; ++ ++static struct spihid_interface *spihid_get_iface(struct spihid_apple *spihid, ++ u32 iface) ++{ ++ switch (iface) { ++ case SPIHID_DEVICE_ID_MNGT: ++ return &spihid->mngt; ++ case SPIHID_DEVICE_ID_KBD: ++ return &spihid->kbd; ++ case SPIHID_DEVICE_ID_TP: ++ return &spihid->tp; ++ default: ++ return NULL; ++ } ++} ++ ++static int spihid_verify_msg(struct spihid_apple *spihid, u8 *buf, size_t len) ++{ ++ u16 msg_crc, crc; ++ struct device *dev = &spihid->spidev->dev; ++ ++ crc = crc16(0, buf, len - sizeof(__le16)); ++ msg_crc = get_unaligned_le16(buf + len - sizeof(__le16)); ++ if (crc != msg_crc) { ++ dev_warn_ratelimited(dev, "Read message crc mismatch\n"); ++ return 0; ++ } ++ return 1; ++} ++ ++static bool spihid_status_report(struct spihid_apple *spihid, u8 *pl, ++ size_t len) ++{ ++ struct device *dev = &spihid->spidev->dev; ++ dev_dbg(dev, "%s: len: %zu", __func__, len); ++ if (len == 5 && pl[0] == 0xe0) ++ return true; ++ ++ return false; ++} ++ ++static bool spihid_process_input_report(struct spihid_apple *spihid, u32 device, ++ struct spihid_msg_hdr *hdr, u8 *payload, ++ size_t len) ++{ ++ //dev_dbg(&spihid>spidev->dev, "input report: req:%hx iface:%u ", hdr->unknown0, device); ++ if (hdr->unknown0 != 0x10) ++ return false; ++ ++ /* HID device as well but Vendor usage only, handle it internally for now */ ++ if (device == 0) { ++ if (hdr->unknown1 == 0xe0) { ++ return spihid_status_report(spihid, payload, len); ++ } ++ } else if (device < SPIHID_MAX_DEVICES) { ++ struct spihid_interface *iface = ++ spihid_get_iface(spihid, device); ++ if (iface && iface->hid && iface->ready) { ++ hid_input_report(iface->hid, HID_INPUT_REPORT, payload, ++ len, 1); ++ return true; ++ } ++ } else ++ dev_dbg(&spihid->spidev->dev, ++ "unexpected iface:%u for input report", device); ++ ++ return false; ++} ++ ++struct spihid_device_info { ++ __le16 u0[2]; ++ __le16 num_devices; ++ __le16 vendor_id; ++ __le16 product_id; ++ __le16 version_number; ++ __le16 vendor_str[2]; //< offset and string length ++ __le16 product_str[2]; //< offset and string length ++ __le16 serial_str[2]; //< offset and string length ++}; ++ ++static bool spihid_process_device_info(struct spihid_apple *spihid, u32 iface, ++ u8 *payload, size_t len) ++{ ++ struct device *dev = &spihid->spidev->dev; ++ ++ if (iface != SPIHID_DEVICE_ID_INFO) ++ return false; ++ ++ if (spihid->vendor_id == 0 && ++ len >= sizeof(struct spihid_device_info)) { ++ struct spihid_device_info *info = ++ (struct spihid_device_info *)payload; ++ u16 voff, vlen, poff, plen, soff, slen; ++ u32 num_devices; ++ ++ num_devices = __le16_to_cpu(info->num_devices); ++ ++ if (num_devices < SPIHID_MAX_DEVICES) { ++ dev_err(dev, ++ "Device info reports %u devices, expecting at least 3", ++ num_devices); ++ return false; ++ } ++ spihid->num_devices = num_devices; ++ ++ if (spihid->num_devices > SPIHID_MAX_DEVICES) { ++ dev_info( ++ dev, ++ "limiting the number of devices to mngt, kbd and mouse"); ++ spihid->num_devices = SPIHID_MAX_DEVICES; ++ } ++ ++ spihid->vendor_id = __le16_to_cpu(info->vendor_id); ++ spihid->product_id = __le16_to_cpu(info->product_id); ++ spihid->version_number = __le16_to_cpu(info->version_number); ++ ++ voff = __le16_to_cpu(info->vendor_str[0]); ++ vlen = __le16_to_cpu(info->vendor_str[1]); ++ ++ if (voff < len && vlen <= len - voff && ++ vlen < sizeof(spihid->vendor)) { ++ memcpy(spihid->vendor, payload + voff, vlen); ++ spihid->vendor[vlen] = '\0'; ++ } ++ ++ poff = __le16_to_cpu(info->product_str[0]); ++ plen = __le16_to_cpu(info->product_str[1]); ++ ++ if (poff < len && plen <= len - poff && ++ plen < sizeof(spihid->product)) { ++ memcpy(spihid->product, payload + poff, plen); ++ spihid->product[plen] = '\0'; ++ } ++ ++ soff = __le16_to_cpu(info->serial_str[0]); ++ slen = __le16_to_cpu(info->serial_str[1]); ++ ++ if (soff < len && slen <= len - soff && ++ slen < sizeof(spihid->serial)) { ++ memcpy(spihid->vendor, payload + soff, slen); ++ spihid->serial[slen] = '\0'; ++ } ++ ++ wake_up_interruptible(&spihid->wait); ++ } ++ return true; ++} ++ ++struct spihid_iface_info { ++ u8 u_0; ++ u8 interface_num; ++ u8 u_2; ++ u8 u_3; ++ u8 u_4; ++ u8 country_code; ++ __le16 max_input_report_len; ++ __le16 max_output_report_len; ++ __le16 max_control_report_len; ++ __le16 name_offset; ++ __le16 name_length; ++}; ++ ++static bool spihid_process_iface_info(struct spihid_apple *spihid, u32 num, ++ u8 *payload, size_t len) ++{ ++ struct spihid_iface_info *info; ++ struct spihid_interface *iface = spihid_get_iface(spihid, num); ++ u32 name_off, name_len; ++ ++ if (!iface) ++ return false; ++ ++ if (!iface->max_input_report_len) { ++ if (len < sizeof(*info)) ++ return false; ++ ++ info = (struct spihid_iface_info *)payload; ++ ++ iface->max_input_report_len = ++ le16_to_cpu(info->max_input_report_len); ++ iface->max_output_report_len = ++ le16_to_cpu(info->max_output_report_len); ++ iface->max_control_report_len = ++ le16_to_cpu(info->max_control_report_len); ++ iface->country = info->country_code; ++ ++ name_off = le16_to_cpu(info->name_offset); ++ name_len = le16_to_cpu(info->name_length); ++ ++ if (name_off < len && name_len <= len - name_off && ++ name_len < sizeof(iface->name)) { ++ memcpy(iface->name, payload + name_off, name_len); ++ iface->name[name_len] = '\0'; ++ } ++ ++ dev_dbg(&spihid->spidev->dev, "Info for %s, country code: 0x%x", ++ iface->name, iface->country); ++ ++ wake_up_interruptible(&spihid->wait); ++ } ++ ++ return true; ++} ++ ++static int spihid_register_hid_device(struct spihid_apple *spihid, ++ struct spihid_interface *idev, u8 device); ++ ++static bool spihid_process_iface_hid_report_desc(struct spihid_apple *spihid, ++ u32 num, u8 *payload, ++ size_t len) ++{ ++ struct spihid_interface *iface = spihid_get_iface(spihid, num); ++ ++ if (!iface) ++ return false; ++ ++ if (iface->hid_desc_len == 0) { ++ if (len > SPIHID_DESC_MAX) ++ return false; ++ memcpy(iface->hid_desc, payload, len); ++ iface->hid_desc_len = len; ++ ++ /* do not register the mngt iface as HID device */ ++ if (num > 0) ++ spihid_register_hid_device(spihid, iface, num); ++ ++ wake_up_interruptible(&spihid->wait); ++ } ++ return true; ++} ++ ++static bool spihid_process_response(struct spihid_apple *spihid, ++ struct spihid_msg_hdr *hdr, u8 *payload, ++ size_t len) ++{ ++ if (hdr->unknown0 == 0x20) { ++ switch (hdr->unknown1) { ++ case 0x01: ++ return spihid_process_device_info(spihid, hdr->unknown2, ++ payload, len); ++ case 0x02: ++ return spihid_process_iface_info(spihid, hdr->unknown2, ++ payload, len); ++ case 0x10: ++ return spihid_process_iface_hid_report_desc( ++ spihid, hdr->unknown2, payload, len); ++ default: ++ break; ++ } ++ } ++ ++ return false; ++} ++ ++static void spihid_process_message(struct spihid_apple *spihid, u8 *data, ++ size_t length, u8 device, u8 flags) ++{ ++ struct device *dev = &spihid->spidev->dev; ++ struct spihid_msg_hdr *hdr; ++ bool handled = false; ++ size_t payload_len; ++ u8 *payload; ++ ++ if (!spihid_verify_msg(spihid, data, length)) ++ return; ++ ++ hdr = (struct spihid_msg_hdr *)data; ++ payload_len = le16_to_cpu(hdr->length); ++ ++ if (payload_len == 0 || ++ (payload_len + sizeof(struct spihid_msg_hdr) + 2) > length) ++ return; ++ ++ payload = data + sizeof(struct spihid_msg_hdr); ++ ++ switch (flags) { ++ case SPIHID_READ_PACKET: ++ handled = spihid_process_input_report(spihid, device, hdr, ++ payload, payload_len); ++ break; ++ case SPIHID_WRITE_PACKET: ++ handled = spihid_process_response(spihid, hdr, payload, ++ payload_len); ++ break; ++ default: ++ break; ++ } ++ ++#if defined(DEBUG) && DEBUG > 1 ++ { ++ dev_dbg(dev, ++ "R msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n", ++ hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id, ++ hdr->length); ++ print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1, ++ payload, le16_to_cpu(hdr->length), true); ++ } ++#else ++ if (!handled) { ++ dev_dbg(dev, ++ "R unhandled msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n", ++ hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id, ++ hdr->length); ++ print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1, ++ payload, le16_to_cpu(hdr->length), true); ++ } ++#endif ++} ++ ++static void spihid_assemble_message(struct spihid_apple *spihid, ++ struct spihid_transfer_packet *pkt) ++{ ++ size_t length, offset, remain; ++ struct device *dev = &spihid->spidev->dev; ++ struct spihid_input_report *rep = &spihid->report; ++ ++ length = le16_to_cpu(pkt->length); ++ remain = le16_to_cpu(pkt->remain); ++ offset = le16_to_cpu(pkt->offset); ++ ++ if (offset + length + remain > U16_MAX) { ++ return; ++ } ++ ++ if (pkt->device != rep->device || pkt->flags != rep->flags || ++ offset != rep->offset) { ++ rep->device = 0; ++ rep->flags = 0; ++ rep->offset = 0; ++ rep->length = 0; ++ } ++ ++ if (offset == 0) { ++ if (rep->offset != 0) { ++ dev_warn(dev, "incomplete report off:%u len:%u", ++ rep->offset, rep->length); ++ } ++ memcpy(rep->buf, pkt->data, length); ++ rep->offset = length; ++ rep->length = length + remain; ++ rep->device = pkt->device; ++ rep->flags = pkt->flags; ++ } else if (offset == rep->offset) { ++ if (offset + length + remain != rep->length) { ++ dev_warn(dev, "incomplete report off:%u len:%u", ++ rep->offset, rep->length); ++ return; ++ } ++ memcpy(rep->buf + offset, pkt->data, length); ++ rep->offset += length; ++ ++ if (rep->offset == rep->length) { ++ spihid_process_message(spihid, rep->buf, rep->length, ++ rep->device, rep->flags); ++ rep->device = 0; ++ rep->flags = 0; ++ rep->offset = 0; ++ rep->length = 0; ++ } ++ } ++} ++ ++static void spihid_process_read(struct spihid_apple *spihid) ++{ ++ u16 crc; ++ size_t length; ++ struct device *dev = &spihid->spidev->dev; ++ struct spihid_transfer_packet *pkt; ++ ++ pkt = (struct spihid_transfer_packet *)spihid->rx_buf; ++ ++ /* check transfer packet crc */ ++ crc = crc16(0, spihid->rx_buf, ++ offsetof(struct spihid_transfer_packet, crc16)); ++ if (crc != le16_to_cpu(pkt->crc16)) { ++ dev_warn_ratelimited(dev, "Read package crc mismatch\n"); ++ return; ++ } ++ ++ length = le16_to_cpu(pkt->length); ++ ++ if (length < sizeof(struct spihid_msg_hdr) + 2) { ++ if (length == sizeof(spi_hid_apple_booted) && ++ !memcmp(pkt->data, spi_hid_apple_booted, length)) { ++ if (!spihid->status_booted) { ++ spihid->status_booted = true; ++ wake_up_interruptible(&spihid->wait); ++ } ++ } else { ++ dev_info(dev, "R short packet: len:%zu\n", length); ++ print_hex_dump(KERN_INFO, "spihid pkt:", ++ DUMP_PREFIX_OFFSET, 16, 1, pkt->data, ++ length, false); ++ } ++ return; ++ } ++ ++#if defined(DEBUG) && DEBUG > 1 ++ dev_dbg(dev, ++ "R pkt: flags:%02hhx dev:%02hhx off:%hu remain:%hu, len:%zu\n", ++ pkt->flags, pkt->device, pkt->offset, pkt->remain, length); ++#if defined(DEBUG) && DEBUG > 2 ++ print_hex_dump_debug("spihid pkt: ", DUMP_PREFIX_OFFSET, 16, 1, ++ spihid->rx_buf, ++ sizeof(struct spihid_transfer_packet), true); ++#endif ++#endif ++ ++ if (length > sizeof(pkt->data)) { ++ dev_warn_ratelimited(dev, "Invalid pkt len:%zu", length); ++ return; ++ } ++ ++ /* short message */ ++ if (pkt->offset == 0 && pkt->remain == 0) { ++ spihid_process_message(spihid, pkt->data, length, pkt->device, ++ pkt->flags); ++ } else { ++ spihid_assemble_message(spihid, pkt); ++ } ++} ++ ++static void spihid_read_packet_sync(struct spihid_apple *spihid) ++{ ++ int err; ++ ++ err = spi_sync(spihid->spidev, &spihid->rx_msg); ++ if (!err) { ++ spihid_process_read(spihid); ++ } else { ++ dev_warn(&spihid->spidev->dev, "RX failed: %d\n", err); ++ } ++} ++ ++irqreturn_t spihid_apple_core_irq(int irq, void *data) ++{ ++ struct spi_device *spi = data; ++ struct spihid_apple *spihid = spi_get_drvdata(spi); ++ ++ spihid_read_packet_sync(spihid); ++ ++ return IRQ_HANDLED; ++} ++EXPORT_SYMBOL_GPL(spihid_apple_core_irq); ++ ++static void spihid_apple_setup_spi_msgs(struct spihid_apple *spihid) ++{ ++ memset(&spihid->rx_transfer, 0, sizeof(spihid->rx_transfer)); ++ ++ spihid->rx_transfer.rx_buf = spihid->rx_buf; ++ spihid->rx_transfer.len = sizeof(struct spihid_transfer_packet); ++ ++ spi_message_init(&spihid->rx_msg); ++ spi_message_add_tail(&spihid->rx_transfer, &spihid->rx_msg); ++ ++ memset(&spihid->tx_transfer, 0, sizeof(spihid->rx_transfer)); ++ memset(&spihid->status_transfer, 0, sizeof(spihid->status_transfer)); ++ ++ spihid->tx_transfer.tx_buf = spihid->tx_buf; ++ spihid->tx_transfer.len = sizeof(struct spihid_transfer_packet); ++ spihid->tx_transfer.delay.unit = SPI_DELAY_UNIT_USECS; ++ spihid->tx_transfer.delay.value = SPI_RW_CHG_DELAY_US; ++ ++ spihid->status_transfer.rx_buf = spihid->status_buf; ++ spihid->status_transfer.len = sizeof(spi_hid_apple_status_ok); ++ ++ spi_message_init(&spihid->tx_msg); ++ spi_message_add_tail(&spihid->tx_transfer, &spihid->tx_msg); ++ spi_message_add_tail(&spihid->status_transfer, &spihid->tx_msg); ++} ++ ++static int spihid_apple_setup_spi(struct spihid_apple *spihid) ++{ ++ spihid_apple_setup_spi_msgs(spihid); ++ ++ return spihid->ops->power_on(spihid->ops); ++} ++ ++static int spihid_register_hid_device(struct spihid_apple *spihid, ++ struct spihid_interface *iface, u8 device) ++{ ++ int ret; ++ struct hid_device *hid; ++ ++ iface->id = device; ++ ++ hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ ++ strscpy(hid->name, spihid->product, sizeof(hid->name)); ++ snprintf(hid->phys, sizeof(hid->phys), "%s (%hhx)", ++ dev_name(&spihid->spidev->dev), device); ++ strscpy(hid->uniq, spihid->serial, sizeof(hid->uniq)); ++ ++ hid->ll_driver = &apple_hid_ll; ++ hid->bus = BUS_SPI; ++ hid->vendor = spihid->vendor_id; ++ hid->product = spihid->product_id; ++ hid->version = spihid->version_number; ++ ++ if (device == SPIHID_DEVICE_ID_KBD) ++ hid->type = HID_TYPE_SPI_KEYBOARD; ++ else if (device == SPIHID_DEVICE_ID_TP) ++ hid->type = HID_TYPE_SPI_MOUSE; ++ ++ hid->country = iface->country; ++ hid->dev.parent = &spihid->spidev->dev; ++ hid->driver_data = iface; ++ ++ ret = hid_add_device(hid); ++ if (ret < 0) { ++ hid_destroy_device(hid); ++ dev_warn(&spihid->spidev->dev, ++ "Failed to register hid device %hhu", device); ++ return ret; ++ } ++ ++ iface->hid = hid; ++ ++ return 0; ++} ++ ++static void spihid_destroy_hid_device(struct spihid_interface *iface) ++{ ++ if (iface->hid) { ++ hid_destroy_device(iface->hid); ++ iface->hid = NULL; ++ } ++ iface->ready = false; ++} ++ ++int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops) ++{ ++ struct device *dev = &spi->dev; ++ struct spihid_apple *spihid; ++ int err, i; ++ ++ if (!ops || !ops->power_on || !ops->power_off || !ops->enable_irq || !ops->disable_irq) ++ return -EINVAL; ++ ++ spihid = devm_kzalloc(dev, sizeof(*spihid), GFP_KERNEL); ++ if (!spihid) ++ return -ENOMEM; ++ ++ spihid->ops = ops; ++ spihid->spidev = spi; ++ ++ // init spi ++ spi_set_drvdata(spi, spihid); ++ ++ /* allocate SPI buffers */ ++ spihid->rx_buf = devm_kmalloc( ++ &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL); ++ spihid->tx_buf = devm_kmalloc( ++ &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL); ++ spihid->status_buf = devm_kmalloc( ++ &spi->dev, sizeof(spi_hid_apple_status_ok), GFP_KERNEL); ++ ++ if (!spihid->rx_buf || !spihid->tx_buf || !spihid->status_buf) ++ return -ENOMEM; ++ ++ spihid->report.buf = ++ devm_kmalloc(dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL); ++ ++ spihid->kbd.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL); ++ spihid->tp.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL); ++ ++ if (!spihid->report.buf || !spihid->kbd.hid_desc || ++ !spihid->tp.hid_desc) ++ return -ENOMEM; ++ ++ init_waitqueue_head(&spihid->wait); ++ ++ mutex_init(&spihid->tx_lock); ++ ++ /* Init spi transfer buffers and power device on */ ++ err = spihid_apple_setup_spi(spihid); ++ if (err < 0) ++ goto error; ++ ++ /* enable HID irq */ ++ spihid->ops->enable_irq(spihid->ops); ++ ++ // wait for boot message ++ err = wait_event_interruptible_timeout(spihid->wait, ++ spihid->status_booted, ++ msecs_to_jiffies(1000)); ++ if (err == 0) ++ err = -ENODEV; ++ if (err < 0) { ++ dev_err(dev, "waiting for device boot failed: %d", err); ++ goto error; ++ } ++ ++ /* request device information */ ++ dev_dbg(dev, "request device info"); ++ spihid_apple_request(spihid, 0xd0, 0x20, 0x01, 0xd0, 0, NULL, 0); ++ err = wait_event_interruptible_timeout(spihid->wait, spihid->vendor_id, ++ SPIHID_DEF_WAIT); ++ if (err == 0) ++ err = -ENODEV; ++ if (err < 0) { ++ dev_err(dev, "waiting for device info failed: %d", err); ++ goto error; ++ } ++ ++ /* request interface information */ ++ for (i = 0; i < spihid->num_devices; i++) { ++ struct spihid_interface *iface = spihid_get_iface(spihid, i); ++ if (!iface) ++ continue; ++ dev_dbg(dev, "request interface info 0x%02x", i); ++ spihid_apple_request(spihid, 0xd0, 0x20, 0x02, i, ++ SPIHID_DESC_MAX, NULL, 0); ++ err = wait_event_interruptible_timeout( ++ spihid->wait, iface->max_input_report_len, ++ SPIHID_DEF_WAIT); ++ } ++ ++ /* request HID report descriptors */ ++ for (i = 1; i < spihid->num_devices; i++) { ++ struct spihid_interface *iface = spihid_get_iface(spihid, i); ++ if (!iface) ++ continue; ++ dev_dbg(dev, "request hid report desc 0x%02x", i); ++ spihid_apple_request(spihid, 0xd0, 0x20, 0x10, i, ++ SPIHID_DESC_MAX, NULL, 0); ++ wait_event_interruptible_timeout( ++ spihid->wait, iface->hid_desc_len, SPIHID_DEF_WAIT); ++ } ++ ++ return 0; ++error: ++ return err; ++} ++EXPORT_SYMBOL_GPL(spihid_apple_core_probe); ++ ++void spihid_apple_core_remove(struct spi_device *spi) ++{ ++ struct spihid_apple *spihid = spi_get_drvdata(spi); ++ ++ /* destroy input devices */ ++ ++ spihid_destroy_hid_device(&spihid->tp); ++ spihid_destroy_hid_device(&spihid->kbd); ++ ++ /* disable irq */ ++ spihid->ops->disable_irq(spihid->ops); ++ ++ /* power SPI device down */ ++ spihid->ops->power_off(spihid->ops); ++} ++EXPORT_SYMBOL_GPL(spihid_apple_core_remove); ++ ++void spihid_apple_core_shutdown(struct spi_device *spi) ++{ ++ struct spihid_apple *spihid = spi_get_drvdata(spi); ++ ++ /* disable irq */ ++ spihid->ops->disable_irq(spihid->ops); ++ ++ /* power SPI device down */ ++ spihid->ops->power_off(spihid->ops); ++} ++EXPORT_SYMBOL_GPL(spihid_apple_core_shutdown); ++ ++MODULE_DESCRIPTION("Apple SPI HID transport driver"); ++MODULE_AUTHOR("Janne Grunau "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/spi-hid/spi-hid-apple-of.c b/drivers/hid/spi-hid/spi-hid-apple-of.c +new file mode 100644 +index 000000000..f1380bfc5 +--- /dev/null ++++ b/drivers/hid/spi-hid/spi-hid-apple-of.c +@@ -0,0 +1,136 @@ ++/* ++ * SPDX-License-Identifier: GPL-2.0 ++ * ++ * Apple SPI HID transport driver - Open Firmware ++ * ++ * Copyright (C) The Asahi Linux Contributors ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "spi-hid-apple.h" ++ ++ ++struct spihid_apple_of { ++ struct spihid_apple_ops ops; ++ ++ struct gpio_desc *enable_gpio; ++ int irq; ++}; ++ ++static int spihid_apple_of_power_on(struct spihid_apple_ops *ops) ++{ ++ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); ++ ++ /* reset the controller on boot */ ++ gpiod_direction_output(sh_of->enable_gpio, 1); ++ msleep(5); ++ gpiod_direction_output(sh_of->enable_gpio, 0); ++ msleep(5); ++ /* turn SPI device on */ ++ gpiod_direction_output(sh_of->enable_gpio, 1); ++ msleep(50); ++ ++ return 0; ++} ++ ++static int spihid_apple_of_power_off(struct spihid_apple_ops *ops) ++{ ++ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); ++ ++ /* turn SPI device off */ ++ gpiod_direction_output(sh_of->enable_gpio, 0); ++ ++ return 0; ++} ++ ++static int spihid_apple_of_enable_irq(struct spihid_apple_ops *ops) ++{ ++ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); ++ ++ enable_irq(sh_of->irq); ++ ++ return 0; ++} ++ ++static int spihid_apple_of_disable_irq(struct spihid_apple_ops *ops) ++{ ++ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); ++ ++ disable_irq(sh_of->irq); ++ ++ return 0; ++} ++ ++static int spihid_apple_of_probe(struct spi_device *spi) ++{ ++ struct device *dev = &spi->dev; ++ struct spihid_apple_of *spihid_of; ++ int err; ++ ++ spihid_of = devm_kzalloc(dev, sizeof(*spihid_of), GFP_KERNEL); ++ if (!spihid_of) ++ return -ENOMEM; ++ ++ spihid_of->ops.power_on = spihid_apple_of_power_on; ++ spihid_of->ops.power_off = spihid_apple_of_power_off; ++ spihid_of->ops.enable_irq = spihid_apple_of_enable_irq; ++ spihid_of->ops.disable_irq = spihid_apple_of_disable_irq; ++ ++ spihid_of->enable_gpio = devm_gpiod_get_index(dev, "spien", 0, 0); ++ if (IS_ERR(spihid_of->enable_gpio)) { ++ err = PTR_ERR(spihid_of->enable_gpio); ++ dev_err(dev, "failed to get 'spien' gpio pin: %d", err); ++ return err; ++ } ++ ++ spihid_of->irq = of_irq_get(dev->of_node, 0); ++ if (spihid_of->irq < 0) { ++ err = spihid_of->irq; ++ dev_err(dev, "failed to get 'extended-irq': %d", err); ++ return err; ++ } ++ err = devm_request_threaded_irq(dev, spihid_of->irq, NULL, ++ spihid_apple_core_irq, IRQF_ONESHOT | IRQF_NO_AUTOEN, ++ "spi-hid-apple-irq", spi); ++ if (err < 0) { ++ dev_err(dev, "failed to request extended-irq %d: %d", ++ spihid_of->irq, err); ++ return err; ++ } ++ ++ return spihid_apple_core_probe(spi, &spihid_of->ops); ++} ++ ++static const struct of_device_id spihid_apple_of_match[] = { ++ { .compatible = "apple,spi-hid-transport" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, spihid_apple_of_match); ++ ++static struct spi_device_id spihid_apple_of_id[] = { ++ { "spi-hid-transport", 0 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(spi, spihid_apple_of_id); ++ ++static struct spi_driver spihid_apple_of_driver = { ++ .driver = { ++ .name = "spi-hid-apple-of", ++ //.pm = &spi_hid_apple_of_pm, ++ .owner = THIS_MODULE, ++ .of_match_table = of_match_ptr(spihid_apple_of_match), ++ }, ++ ++ .id_table = spihid_apple_of_id, ++ .probe = spihid_apple_of_probe, ++ .remove = spihid_apple_core_remove, ++ .shutdown = spihid_apple_core_shutdown, ++}; ++ ++module_spi_driver(spihid_apple_of_driver); ++ ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/spi-hid/spi-hid-apple.h b/drivers/hid/spi-hid/spi-hid-apple.h +new file mode 100644 +index 000000000..2d9554e8a +--- /dev/null ++++ b/drivers/hid/spi-hid/spi-hid-apple.h +@@ -0,0 +1,31 @@ ++/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ ++ ++#ifndef SPI_HID_APPLE_H ++#define SPI_HID_APPLE_H ++ ++#include ++#include ++ ++/** ++ * struct spihid_apple_ops - Ops to control the device from the core driver. ++ * ++ * @power_on: reset and power the device on. ++ * @power_off: power the device off. ++ * @enable_irq: enable irq or ACPI gpe. ++ * @disable_irq: disable irq or ACPI gpe. ++ */ ++ ++struct spihid_apple_ops { ++ int (*power_on)(struct spihid_apple_ops *ops); ++ int (*power_off)(struct spihid_apple_ops *ops); ++ int (*enable_irq)(struct spihid_apple_ops *ops); ++ int (*disable_irq)(struct spihid_apple_ops *ops); ++}; ++ ++irqreturn_t spihid_apple_core_irq(int irq, void *data); ++ ++int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops); ++void spihid_apple_core_remove(struct spi_device *spi); ++void spihid_apple_core_shutdown(struct spi_device *spi); ++ ++#endif /* SPI_HID_APPLE_H */ +-- +2.43.0 + + +From ec10ec10f23a9636fbc22a97307640223b6881b8 Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Fri, 8 Jul 2022 00:29:43 +0900 +Subject: [PATCH 11/26] HID: add HOST vendor/device IDs for Apple MTP devices + +Apple M2 chips have an embedded MTP processor that handles all HID +functions, and does not go over a traditional bus like SPI. The devices +still have real IDs, so add them here. + +Signed-off-by: Hector Martin +--- + drivers/hid/hid-ids.h | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +index c1c573b49..192c09c27 100644 +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -90,6 +90,7 @@ + #define USB_VENDOR_ID_APPLE 0x05ac + #define BT_VENDOR_ID_APPLE 0x004c + #define SPI_VENDOR_ID_APPLE 0x05ac ++#define HOST_VENDOR_ID_APPLE 0x05ac + #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 + #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d + #define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269 +@@ -193,6 +194,8 @@ + #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020 0x0341 + #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021 0x0342 + #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021 0x0343 ++#define HOST_DEVICE_ID_APPLE_MACBOOK_AIR13_2022 0x0351 ++#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022 0x0354 + + #define USB_VENDOR_ID_ASUS 0x0486 + #define USB_DEVICE_ID_ASUS_T91MT 0x0185 +-- +2.43.0 + + +From 4b14ff60bdc957e3372e9ec091f3edbdd3fd0ce8 Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Fri, 8 Jul 2022 02:06:15 +0900 +Subject: [PATCH 12/26] HID: core: Handle HOST bus type when announcing devices + +Signed-off-by: Hector Martin +--- + drivers/hid/hid-core.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c +index 534c49f9e..9976ee646 100644 +--- a/drivers/hid/hid-core.c ++++ b/drivers/hid/hid-core.c +@@ -2300,6 +2300,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) + case BUS_SPI: + bus = "SPI"; + break; ++ case BUS_HOST: ++ bus = "HOST"; ++ break; + case BUS_VIRTUAL: + bus = "VIRTUAL"; + break; +-- +2.43.0 + + +From bcbc87f82d31f7b85b791d7785b3be0a9b2bdf7c Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Fri, 8 Jul 2022 02:12:24 +0900 +Subject: [PATCH 13/26] hid: apple: Bind to HOST devices for MTP + +We use BUS_HOST for MTP HID subdevices + +Signed-off-by: Hector Martin +--- + drivers/hid/hid-apple.c | 19 +++++++++++++------ + 1 file changed, 13 insertions(+), 6 deletions(-) + +diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c +index c772305ce..a33260293 100644 +--- a/drivers/hid/hid-apple.c ++++ b/drivers/hid/hid-apple.c +@@ -535,11 +535,16 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, + else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI && + hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) + table = macbookair_fn_keys; +- else if (hid->vendor == SPI_VENDOR_ID_APPLE && +- hid->product == SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020) +- table = apple_fn_keys_mbp13; +- else if (hid->vendor == SPI_VENDOR_ID_APPLE) +- table = apple_fn_keys_spi; ++ else if (hid->bus == BUS_HOST || hid->bus == BUS_SPI) ++ switch (hid->product) { ++ case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: ++ case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022: ++ table = apple_fn_keys_mbp13; ++ break; ++ default: ++ table = apple_fn_keys_spi; ++ break; ++ } + else if (hid->product < 0x21d || hid->product >= 0x300) + table = powerbook_fn_keys; + else +@@ -954,7 +959,7 @@ static int apple_probe(struct hid_device *hdev, + struct apple_sc *asc; + int ret; + +- if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE && ++ if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE && + hdev->type != HID_TYPE_SPI_KEYBOARD) + return -ENODEV; + +@@ -1215,6 +1220,8 @@ static const struct hid_device_id apple_devices[] = { + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, ++ { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, HID_ANY_ID), ++ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT), + .driver_data = APPLE_MAGIC_BACKLIGHT }, + +-- +2.43.0 + + +From a6d11b8956671a31ee387ca81ea6d42d1049a27b Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Fri, 8 Jul 2022 02:12:57 +0900 +Subject: [PATCH 14/26] hid: magicmouse: Add MTP multi-touch device support + +Apple M2 devices expose the multi-touch device over the HID over +DockChannel transport, which we represent as the HOST bus type. The +report format is the same, except the legacy mouse header is gone and +there is no enable request needed. + +Signed-off-by: Hector Martin +--- + drivers/hid/hid-magicmouse.c | 67 ++++++++++++++++++++++++++---------- + 1 file changed, 49 insertions(+), 18 deletions(-) + +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index 94acee33f..38f000db5 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -59,6 +59,8 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define MOUSE_REPORT_ID 0x29 + #define MOUSE2_REPORT_ID 0x12 + #define DOUBLE_REPORT_ID 0xf7 ++#define SPI_REPORT_ID 0x02 ++#define MTP_REPORT_ID 0x75 + #define USB_BATTERY_TIMEOUT_MS 60000 + + #define MAX_CONTACTS 16 +@@ -584,25 +586,32 @@ struct tp_finger { + } __attribute__((packed, aligned(2))); + + /** +- * struct trackpad report ++ * vendor trackpad report + * +- * @report_id: reportid +- * @buttons: HID Usage Buttons 3 1-bit reports + * @num_fingers: the number of fingers being reported in @fingers +- * @clicked: same as @buttons ++ * @buttons: same as HID buttons + */ + struct tp_header { ++ // HID vendor part, up to 1751 bytes ++ u8 unknown[22]; ++ u8 num_fingers; ++ u8 buttons; ++ u8 unknown3[14]; ++}; ++ ++/** ++ * standard HID mouse report ++ * ++ * @report_id: reportid ++ * @buttons: HID Usage Buttons 3 1-bit reports ++ */ ++struct tp_mouse_report { + // HID mouse report + u8 report_id; + u8 buttons; + u8 rel_x; + u8 rel_y; + u8 padding[4]; +- // HID vendor part, up to 1751 bytes +- u8 unknown[22]; +- u8 num_fingers; +- u8 clicked; +- u8 unknown3[14]; + }; + + static inline int le16_to_int(__le16 x) +@@ -632,7 +641,7 @@ static void report_finger_data(struct input_dev *input, int slot, + input_report_abs(input, ABS_MT_POSITION_Y, pos->y); + } + +-static int magicmouse_raw_event_spi(struct hid_device *hdev, ++static int magicmouse_raw_event_mtp(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) + { + struct magicmouse_sc *msc = hid_get_drvdata(hdev); +@@ -649,9 +658,6 @@ static int magicmouse_raw_event_spi(struct hid_device *hdev, + // print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data, + // size, false); + +- if (data[0] != TRACKPAD2_USB_REPORT_ID) +- return 0; +- + /* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */ + if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0) + return 0; +@@ -690,12 +696,26 @@ static int magicmouse_raw_event_spi(struct hid_device *hdev, + } + + input_mt_sync_frame(input); +- input_report_key(input, BTN_MOUSE, data[1] & 1); ++ input_report_key(input, BTN_MOUSE, tp_hdr->buttons & 1); + + input_sync(input); + return 1; + } + ++static int magicmouse_raw_event_spi(struct hid_device *hdev, ++ struct hid_report *report, u8 *data, int size) ++{ ++ const size_t hdr_sz = sizeof(struct tp_mouse_report); ++ ++ if (size < hdr_sz) ++ return 0; ++ ++ if (data[0] != TRACKPAD2_USB_REPORT_ID) ++ return 0; ++ ++ return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz); ++} ++ + static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) + { +@@ -1089,7 +1109,7 @@ static int magicmouse_probe(struct hid_device *hdev, + struct hid_report *report; + int ret; + +- if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE && ++ if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE && + hdev->type != HID_TYPE_SPI_MOUSE) + return -ENODEV; + +@@ -1101,7 +1121,10 @@ static int magicmouse_probe(struct hid_device *hdev, + + // internal trackpad use a data format use input ops to avoid + // conflicts with the report ID. +- if (id->vendor == SPI_VENDOR_ID_APPLE) { ++ if (id->bus == BUS_HOST) { ++ msc->input_ops.raw_event = magicmouse_raw_event_mtp; ++ msc->input_ops.setup_input = magicmouse_setup_input_spi; ++ } else if (id->bus == BUS_SPI) { + msc->input_ops.raw_event = magicmouse_raw_event_spi; + msc->input_ops.setup_input = magicmouse_setup_input_spi; + +@@ -1161,8 +1184,10 @@ static int magicmouse_probe(struct hid_device *hdev, + else /* USB_VENDOR_ID_APPLE */ + report = hid_register_report(hdev, HID_INPUT_REPORT, + TRACKPAD2_USB_REPORT_ID, 0); +- } else if (id->vendor == SPI_VENDOR_ID_APPLE) { +- report = hid_register_report(hdev, HID_INPUT_REPORT, 2, 0); ++ } else if (id->bus == BUS_SPI) { ++ report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0); ++ } else if (id->bus == BUS_HOST) { ++ report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0); + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + report = hid_register_report(hdev, HID_INPUT_REPORT, + TRACKPAD_REPORT_ID, 0); +@@ -1177,6 +1202,10 @@ static int magicmouse_probe(struct hid_device *hdev, + } + report->size = 6; + ++ /* MTP devices do not need the MT enable, this is handled by the MTP driver */ ++ if (id->bus == BUS_HOST) ++ return 0; ++ + /* + * Some devices repond with 'invalid report id' when feature + * report switching it into multitouch mode is sent to it. +@@ -1264,6 +1293,8 @@ static const struct hid_device_id magic_mice[] = { + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 }, + { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = 0 }, ++ { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, ++ HID_ANY_ID), .driver_data = 0 }, + { } + }; + MODULE_DEVICE_TABLE(hid, magic_mice); +-- +2.43.0 + + +From 945e22ae2534196a321be7800c1482229a761465 Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Fri, 8 Jul 2022 02:09:24 +0900 +Subject: [PATCH 15/26] soc: apple: Add DockChannel driver + +DockChannel is a simple FIFO interface used to communicate between SoC +blocks. Add a driver that represents the shared interrupt controller for +the DockChannel block, and then exposes probe and data transfer +functions that child device drivers can use to instantiate individual +FIFOs. + +Signed-off-by: Hector Martin +--- + drivers/soc/apple/Kconfig | 10 + + drivers/soc/apple/Makefile | 3 + + drivers/soc/apple/dockchannel.c | 406 ++++++++++++++++++++++++++ + include/linux/soc/apple/dockchannel.h | 26 ++ + 4 files changed, 445 insertions(+) + create mode 100644 drivers/soc/apple/dockchannel.c + create mode 100644 include/linux/soc/apple/dockchannel.h + +diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig +index 6388cbe1e..82d0edadf 100644 +--- a/drivers/soc/apple/Kconfig ++++ b/drivers/soc/apple/Kconfig +@@ -4,6 +4,16 @@ if ARCH_APPLE || COMPILE_TEST + + menu "Apple SoC drivers" + ++config APPLE_DOCKCHANNEL ++ tristate "Apple DockChannel FIFO" ++ depends on ARCH_APPLE || COMPILE_TEST ++ default ARCH_APPLE ++ help ++ DockChannel is a simple FIFO used on Apple SoCs for debug and inter-processor ++ communications. ++ ++ Say 'y' here if you have an Apple SoC. ++ + config APPLE_MAILBOX + tristate "Apple SoC mailboxes" + depends on PM +diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile +index 4d9ab8f30..0b6a9f92b 100644 +--- a/drivers/soc/apple/Makefile ++++ b/drivers/soc/apple/Makefile +@@ -1,5 +1,8 @@ + # SPDX-License-Identifier: GPL-2.0-only + ++obj-$(CONFIG_APPLE_DOCKCHANNEL) += apple-dockchannel.o ++apple-dockchannel-y = dockchannel.o ++ + obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o + apple-mailbox-y = mailbox.o + +diff --git a/drivers/soc/apple/dockchannel.c b/drivers/soc/apple/dockchannel.c +new file mode 100644 +index 000000000..3a0d79640 +--- /dev/null ++++ b/drivers/soc/apple/dockchannel.c +@@ -0,0 +1,406 @@ ++// SPDX-License-Identifier: GPL-2.0-only OR MIT ++/* ++ * Apple DockChannel FIFO driver ++ * Copyright The Asahi Linux Contributors ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DOCKCHANNEL_MAX_IRQ 32 ++ ++#define DOCKCHANNEL_TX_TIMEOUT_MS 1000 ++#define DOCKCHANNEL_RX_TIMEOUT_MS 1000 ++ ++#define IRQ_MASK 0x0 ++#define IRQ_FLAG 0x4 ++ ++#define IRQ_TX BIT(0) ++#define IRQ_RX BIT(1) ++ ++#define CONFIG_TX_THRESH 0x0 ++#define CONFIG_RX_THRESH 0x4 ++ ++#define DATA_TX8 0x4 ++#define DATA_TX16 0x8 ++#define DATA_TX24 0xc ++#define DATA_TX32 0x10 ++#define DATA_TX_FREE 0x14 ++#define DATA_RX8 0x1c ++#define DATA_RX16 0x20 ++#define DATA_RX24 0x24 ++#define DATA_RX32 0x28 ++#define DATA_RX_COUNT 0x2c ++ ++struct dockchannel { ++ struct device *dev; ++ int tx_irq; ++ int rx_irq; ++ ++ void __iomem *config_base; ++ void __iomem *data_base; ++ ++ u32 fifo_size; ++ bool awaiting; ++ struct completion tx_comp; ++ struct completion rx_comp; ++ ++ void *cookie; ++ void (*data_available)(void *cookie, size_t avail); ++}; ++ ++struct dockchannel_common { ++ struct device *dev; ++ struct irq_domain *domain; ++ int irq; ++ ++ void __iomem *irq_base; ++}; ++ ++/* Dockchannel FIFO functions */ ++ ++static irqreturn_t dockchannel_tx_irq(int irq, void *data) ++{ ++ struct dockchannel *dockchannel = data; ++ ++ disable_irq_nosync(irq); ++ complete(&dockchannel->tx_comp); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t dockchannel_rx_irq(int irq, void *data) ++{ ++ struct dockchannel *dockchannel = data; ++ ++ disable_irq_nosync(irq); ++ ++ if (dockchannel->awaiting) { ++ return IRQ_WAKE_THREAD; ++ } else { ++ complete(&dockchannel->rx_comp); ++ return IRQ_HANDLED; ++ } ++} ++ ++static irqreturn_t dockchannel_rx_irq_thread(int irq, void *data) ++{ ++ struct dockchannel *dockchannel = data; ++ size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT); ++ ++ dockchannel->awaiting = false; ++ dockchannel->data_available(dockchannel->cookie, avail); ++ ++ return IRQ_HANDLED; ++} ++ ++int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count) ++{ ++ size_t left = count; ++ const u8 *p = buf; ++ ++ while (left > 0) { ++ size_t avail = readl_relaxed(dockchannel->data_base + DATA_TX_FREE); ++ size_t block = min(left, avail); ++ ++ if (avail == 0) { ++ size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left); ++ ++ writel_relaxed(threshold, dockchannel->config_base + CONFIG_TX_THRESH); ++ reinit_completion(&dockchannel->tx_comp); ++ enable_irq(dockchannel->tx_irq); ++ ++ if (!wait_for_completion_timeout(&dockchannel->tx_comp, ++ msecs_to_jiffies(DOCKCHANNEL_TX_TIMEOUT_MS))) { ++ disable_irq(dockchannel->tx_irq); ++ return -ETIMEDOUT; ++ } ++ ++ continue; ++ } ++ ++ while (block >= 4) { ++ writel_relaxed(get_unaligned_le32(p), dockchannel->data_base + DATA_TX32); ++ p += 4; ++ left -= 4; ++ block -= 4; ++ } ++ while (block > 0) { ++ writeb_relaxed(*p++, dockchannel->data_base + DATA_TX8); ++ left--; ++ block--; ++ } ++ } ++ ++ return count; ++} ++EXPORT_SYMBOL(dockchannel_send); ++ ++int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count) ++{ ++ size_t left = count; ++ u8 *p = buf; ++ ++ while (left > 0) { ++ size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT); ++ size_t block = min(left, avail); ++ ++ if (avail == 0) { ++ size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left); ++ ++ writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH); ++ reinit_completion(&dockchannel->rx_comp); ++ enable_irq(dockchannel->rx_irq); ++ ++ if (!wait_for_completion_timeout(&dockchannel->rx_comp, ++ msecs_to_jiffies(DOCKCHANNEL_RX_TIMEOUT_MS))) { ++ disable_irq(dockchannel->rx_irq); ++ return -ETIMEDOUT; ++ } ++ ++ continue; ++ } ++ ++ while (block >= 4) { ++ put_unaligned_le32(readl_relaxed(dockchannel->data_base + DATA_RX32), p); ++ p += 4; ++ left -= 4; ++ block -= 4; ++ } ++ while (block > 0) { ++ *p++ = readl_relaxed(dockchannel->data_base + DATA_RX8) >> 8; ++ left--; ++ block--; ++ } ++ } ++ ++ return count; ++} ++EXPORT_SYMBOL(dockchannel_recv); ++ ++int dockchannel_await(struct dockchannel *dockchannel, ++ void (*callback)(void *cookie, size_t avail), ++ void *cookie, size_t count) ++{ ++ size_t threshold = min((size_t)dockchannel->fifo_size, count); ++ ++ if (!count) { ++ dockchannel->awaiting = false; ++ disable_irq(dockchannel->rx_irq); ++ return 0; ++ } ++ ++ dockchannel->data_available = callback; ++ dockchannel->cookie = cookie; ++ dockchannel->awaiting = true; ++ writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH); ++ enable_irq(dockchannel->rx_irq); ++ ++ return threshold; ++} ++EXPORT_SYMBOL(dockchannel_await); ++ ++struct dockchannel *dockchannel_init(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct dockchannel *dockchannel; ++ int ret; ++ ++ dockchannel = devm_kzalloc(dev, sizeof(*dockchannel), GFP_KERNEL); ++ if (!dockchannel) ++ return ERR_PTR(-ENOMEM); ++ ++ dockchannel->dev = dev; ++ dockchannel->config_base = devm_platform_ioremap_resource_byname(pdev, "config"); ++ if (IS_ERR(dockchannel->config_base)) ++ return (__force void *)dockchannel->config_base; ++ ++ dockchannel->data_base = devm_platform_ioremap_resource_byname(pdev, "data"); ++ if (IS_ERR(dockchannel->data_base)) ++ return (__force void *)dockchannel->data_base; ++ ++ ret = of_property_read_u32(dev->of_node, "apple,fifo-size", &dockchannel->fifo_size); ++ if (ret) ++ return ERR_PTR(dev_err_probe(dev, ret, "Missing apple,fifo-size property")); ++ ++ init_completion(&dockchannel->tx_comp); ++ init_completion(&dockchannel->rx_comp); ++ ++ dockchannel->tx_irq = platform_get_irq_byname(pdev, "tx"); ++ if (dockchannel->tx_irq <= 0) { ++ return ERR_PTR(dev_err_probe(dev, dockchannel->tx_irq, ++ "Failed to get TX IRQ")); ++ } ++ ++ dockchannel->rx_irq = platform_get_irq_byname(pdev, "rx"); ++ if (dockchannel->rx_irq <= 0) { ++ return ERR_PTR(dev_err_probe(dev, dockchannel->rx_irq, ++ "Failed to get RX IRQ")); ++ } ++ ++ ret = devm_request_irq(dev, dockchannel->tx_irq, dockchannel_tx_irq, IRQF_NO_AUTOEN, ++ "apple-dockchannel-tx", dockchannel); ++ if (ret) ++ return ERR_PTR(dev_err_probe(dev, ret, "Failed to request TX IRQ")); ++ ++ ret = devm_request_threaded_irq(dev, dockchannel->rx_irq, dockchannel_rx_irq, ++ dockchannel_rx_irq_thread, IRQF_NO_AUTOEN, ++ "apple-dockchannel-rx", dockchannel); ++ if (ret) ++ return ERR_PTR(dev_err_probe(dev, ret, "Failed to request RX IRQ")); ++ ++ return dockchannel; ++} ++EXPORT_SYMBOL(dockchannel_init); ++ ++ ++/* Dockchannel IRQchip */ ++ ++static void dockchannel_irq(struct irq_desc *desc) ++{ ++ unsigned int irq = irq_desc_get_irq(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ struct dockchannel_common *dcc = irq_get_handler_data(irq); ++ unsigned long flags = readl_relaxed(dcc->irq_base + IRQ_FLAG); ++ int bit; ++ ++ chained_irq_enter(chip, desc); ++ ++ for_each_set_bit(bit, &flags, DOCKCHANNEL_MAX_IRQ) ++ generic_handle_domain_irq(dcc->domain, bit); ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void dockchannel_irq_ack(struct irq_data *data) ++{ ++ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); ++ unsigned int hwirq = data->hwirq; ++ ++ writel_relaxed(BIT(hwirq), dcc->irq_base + IRQ_FLAG); ++} ++ ++static void dockchannel_irq_mask(struct irq_data *data) ++{ ++ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); ++ unsigned int hwirq = data->hwirq; ++ u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK); ++ ++ writel_relaxed(val & ~BIT(hwirq), dcc->irq_base + IRQ_MASK); ++} ++ ++static void dockchannel_irq_unmask(struct irq_data *data) ++{ ++ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); ++ unsigned int hwirq = data->hwirq; ++ u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK); ++ ++ writel_relaxed(val | BIT(hwirq), dcc->irq_base + IRQ_MASK); ++} ++ ++static const struct irq_chip dockchannel_irqchip = { ++ .name = "dockchannel-irqc", ++ .irq_ack = dockchannel_irq_ack, ++ .irq_mask = dockchannel_irq_mask, ++ .irq_unmask = dockchannel_irq_unmask, ++}; ++ ++static int dockchannel_irq_domain_map(struct irq_domain *d, unsigned int virq, ++ irq_hw_number_t hw) ++{ ++ irq_set_chip_data(virq, d->host_data); ++ irq_set_chip_and_handler(virq, &dockchannel_irqchip, handle_level_irq); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops dockchannel_irq_domain_ops = { ++ .xlate = irq_domain_xlate_twocell, ++ .map = dockchannel_irq_domain_map, ++}; ++ ++static int dockchannel_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct dockchannel_common *dcc; ++ struct device_node *child; ++ ++ dcc = devm_kzalloc(dev, sizeof(*dcc), GFP_KERNEL); ++ if (!dcc) ++ return -ENOMEM; ++ ++ dcc->dev = dev; ++ platform_set_drvdata(pdev, dcc); ++ ++ dcc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq"); ++ if (IS_ERR(dcc->irq_base)) ++ return PTR_ERR(dcc->irq_base); ++ ++ writel_relaxed(0, dcc->irq_base + IRQ_MASK); ++ writel_relaxed(~0, dcc->irq_base + IRQ_FLAG); ++ ++ dcc->domain = irq_domain_add_linear(dev->of_node, DOCKCHANNEL_MAX_IRQ, ++ &dockchannel_irq_domain_ops, dcc); ++ if (!dcc->domain) ++ return -ENOMEM; ++ ++ dcc->irq = platform_get_irq(pdev, 0); ++ if (dcc->irq <= 0) ++ return dev_err_probe(dev, dcc->irq, "Failed to get IRQ"); ++ ++ irq_set_handler_data(dcc->irq, dcc); ++ irq_set_chained_handler(dcc->irq, dockchannel_irq); ++ ++ for_each_child_of_node(dev->of_node, child) ++ of_platform_device_create(child, NULL, dev); ++ ++ return 0; ++} ++ ++static void dockchannel_remove(struct platform_device *pdev) ++{ ++ struct dockchannel_common *dcc = platform_get_drvdata(pdev); ++ int hwirq; ++ ++ device_for_each_child(&pdev->dev, NULL, of_platform_device_destroy); ++ ++ irq_set_chained_handler_and_data(dcc->irq, NULL, NULL); ++ ++ for (hwirq = 0; hwirq < DOCKCHANNEL_MAX_IRQ; hwirq++) ++ irq_dispose_mapping(irq_find_mapping(dcc->domain, hwirq)); ++ ++ irq_domain_remove(dcc->domain); ++ ++ writel_relaxed(0, dcc->irq_base + IRQ_MASK); ++ writel_relaxed(~0, dcc->irq_base + IRQ_FLAG); ++} ++ ++static const struct of_device_id dockchannel_of_match[] = { ++ { .compatible = "apple,dockchannel" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, dockchannel_of_match); ++ ++static struct platform_driver dockchannel_driver = { ++ .driver = { ++ .name = "dockchannel", ++ .of_match_table = dockchannel_of_match, ++ }, ++ .probe = dockchannel_probe, ++ .remove = dockchannel_remove, ++}; ++module_platform_driver(dockchannel_driver); ++ ++MODULE_AUTHOR("Hector Martin "); ++MODULE_LICENSE("Dual MIT/GPL"); ++MODULE_DESCRIPTION("Apple DockChannel driver"); +diff --git a/include/linux/soc/apple/dockchannel.h b/include/linux/soc/apple/dockchannel.h +new file mode 100644 +index 000000000..0b7093935 +--- /dev/null ++++ b/include/linux/soc/apple/dockchannel.h +@@ -0,0 +1,26 @@ ++/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ ++/* ++ * Apple Dockchannel devices ++ * Copyright (C) The Asahi Linux Contributors ++ */ ++#ifndef _LINUX_APPLE_DOCKCHANNEL_H_ ++#define _LINUX_APPLE_DOCKCHANNEL_H_ ++ ++#include ++#include ++#include ++ ++#if IS_ENABLED(CONFIG_APPLE_DOCKCHANNEL) ++ ++struct dockchannel; ++ ++struct dockchannel *dockchannel_init(struct platform_device *pdev); ++ ++int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count); ++int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count); ++int dockchannel_await(struct dockchannel *dockchannel, ++ void (*callback)(void *cookie, size_t avail), ++ void *cookie, size_t count); ++ ++#endif ++#endif +-- +2.43.0 + + +From a1677c243b91598acb1e7609d966d3ddfb3b0b0d Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Fri, 8 Jul 2022 02:11:21 +0900 +Subject: [PATCH 16/26] hid: Add Apple DockChannel HID transport driver + +Apple M2 devices have an MTP coprocessor embedded in the SoC that +handles HID for the integrated touchpad/keyboard, and communicates +over the DockChannel interface. This driver implements this new +interface. + +Signed-off-by: Hector Martin +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/dockchannel-hid/Kconfig | 14 + + drivers/hid/dockchannel-hid/Makefile | 6 + + drivers/hid/dockchannel-hid/dockchannel-hid.c | 1213 +++++++++++++++++ + 5 files changed, 1237 insertions(+) + create mode 100644 drivers/hid/dockchannel-hid/Kconfig + create mode 100644 drivers/hid/dockchannel-hid/Makefile + create mode 100644 drivers/hid/dockchannel-hid/dockchannel-hid.c + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 85cbe1e7c..e4f81de85 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1390,4 +1390,6 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/spi-hid/Kconfig" + ++source "drivers/hid/dockchannel-hid/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index f4b2e84c7..a5026febe 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -173,3 +173,5 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/ ++ ++obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/ +diff --git a/drivers/hid/dockchannel-hid/Kconfig b/drivers/hid/dockchannel-hid/Kconfig +new file mode 100644 +index 000000000..8a81d551a +--- /dev/null ++++ b/drivers/hid/dockchannel-hid/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-only OR MIT ++menu "DockChannel HID support" ++ depends on APPLE_DOCKCHANNEL ++ ++config HID_DOCKCHANNEL ++ tristate "HID over DockChannel transport layer for Apple Silicon SoCs" ++ default ARCH_APPLE ++ depends on APPLE_DOCKCHANNEL && INPUT && OF && HID ++ help ++ Say Y here if you use an M2 or later Apple Silicon based laptop. ++ The keyboard and touchpad are HID based devices connected via the ++ proprietary DockChannel interface. ++ ++endmenu +diff --git a/drivers/hid/dockchannel-hid/Makefile b/drivers/hid/dockchannel-hid/Makefile +new file mode 100644 +index 000000000..7dba766b0 +--- /dev/null ++++ b/drivers/hid/dockchannel-hid/Makefile +@@ -0,0 +1,6 @@ ++# SPDX-License-Identifier: GPL-2.0-only OR MIT ++# ++# Makefile for DockChannel HID transport drivers ++# ++ ++obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid.o +diff --git a/drivers/hid/dockchannel-hid/dockchannel-hid.c b/drivers/hid/dockchannel-hid/dockchannel-hid.c +new file mode 100644 +index 000000000..a712a724d +--- /dev/null ++++ b/drivers/hid/dockchannel-hid/dockchannel-hid.c +@@ -0,0 +1,1213 @@ ++/* ++ * SPDX-License-Identifier: GPL-2.0 OR MIT ++ * ++ * Apple DockChannel HID transport driver ++ * ++ * Copyright The Asahi Linux Contributors ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../hid-ids.h" ++ ++#define COMMAND_TIMEOUT_MS 1000 ++#define START_TIMEOUT_MS 2000 ++ ++#define MAX_INTERFACES 16 ++ ++/* Data + checksum */ ++#define MAX_PKT_SIZE (0xffff + 4) ++ ++#define DCHID_CHANNEL_CMD 0x11 ++#define DCHID_CHANNEL_REPORT 0x12 ++ ++struct dchid_hdr { ++ u8 hdr_len; ++ u8 channel; ++ u16 length; ++ u8 seq; ++ u8 iface; ++ u16 pad; ++} __packed; ++ ++#define IFACE_COMM 0 ++ ++#define FLAGS_GROUP GENMASK(7, 6) ++#define FLAGS_REQ GENMASK(5, 0) ++ ++#define REQ_SET_REPORT 0 ++#define REQ_GET_REPORT 1 ++ ++struct dchid_subhdr { ++ u8 flags; ++ u8 unk; ++ u16 length; ++ u32 retcode; ++} __packed; ++ ++#define EVENT_GPIO_CMD 0xa0 ++#define EVENT_INIT 0xf0 ++#define EVENT_READY 0xf1 ++ ++struct dchid_init_hdr { ++ u8 type; ++ u8 unk1; ++ u8 unk2; ++ u8 iface; ++ char name[16]; ++ u8 more_packets; ++ u8 unkpad; ++} __packed; ++ ++#define INIT_HID_DESCRIPTOR 0 ++#define INIT_GPIO_REQUEST 1 ++#define INIT_TERMINATOR 2 ++#define INIT_PRODUCT_NAME 7 ++ ++#define CMD_RESET_INTERFACE 0x40 ++#define CMD_SEND_FIRMWARE 0x95 ++#define CMD_ENABLE_INTERFACE 0xb4 ++#define CMD_ACK_GPIO_CMD 0xa1 ++ ++struct dchid_init_block_hdr { ++ u16 type; ++ u16 length; ++} __packed; ++ ++#define MAX_GPIO_NAME 32 ++ ++struct dchid_gpio_request { ++ u16 unk; ++ u16 id; ++ char name[MAX_GPIO_NAME]; ++} __packed; ++ ++struct dchid_gpio_cmd { ++ u8 type; ++ u8 iface; ++ u8 gpio; ++ u8 unk; ++ u8 cmd; ++} __packed; ++ ++struct dchid_gpio_ack { ++ u8 type; ++ u32 retcode; ++ u8 cmd[]; ++} __packed; ++ ++#define STM_REPORT_ID 0x10 ++#define STM_REPORT_SERIAL 0x11 ++#define STM_REPORT_KEYBTYPE 0x14 ++ ++struct dchid_stm_id { ++ u8 unk; ++ u16 vendor_id; ++ u16 product_id; ++ u16 version_number; ++ u8 unk2; ++ u8 unk3; ++ u8 keyboard_type; ++ u8 serial_length; ++ /* Serial follows, but we grab it with a different report. */ ++} __packed; ++ ++#define FW_MAGIC 0x46444948 ++#define FW_VER 1 ++ ++struct fw_header { ++ u32 magic; ++ u32 version; ++ u32 hdr_length; ++ u32 data_length; ++ u32 iface_offset; ++} __packed; ++ ++struct dchid_work { ++ struct work_struct work; ++ struct dchid_iface *iface; ++ ++ struct dchid_hdr hdr; ++ u8 data[]; ++}; ++ ++struct dchid_iface { ++ struct dockchannel_hid *dchid; ++ struct hid_device *hid; ++ struct workqueue_struct *wq; ++ ++ bool creating; ++ struct work_struct create_work; ++ ++ int index; ++ const char *name; ++ const struct device_node *of_node; ++ ++ uint8_t tx_seq; ++ bool deferred; ++ bool starting; ++ bool open; ++ struct completion ready; ++ ++ void *hid_desc; ++ size_t hid_desc_len; ++ ++ struct gpio_desc *gpio; ++ char gpio_name[MAX_GPIO_NAME]; ++ int gpio_id; ++ ++ struct mutex out_mutex; ++ u32 out_flags; ++ int out_report; ++ u32 retcode; ++ void *resp_buf; ++ size_t resp_size; ++ struct completion out_complete; ++ ++ u32 keyboard_layout_id; ++}; ++ ++struct dockchannel_hid { ++ struct device *dev; ++ struct dockchannel *dc; ++ struct device_link *helper_link; ++ ++ bool id_ready; ++ struct dchid_stm_id device_id; ++ char serial[64]; ++ ++ struct dchid_iface *comm; ++ struct dchid_iface *ifaces[MAX_INTERFACES]; ++ ++ u8 pkt_buf[MAX_PKT_SIZE]; ++ ++ /* Workqueue to asynchronously create HID devices */ ++ struct workqueue_struct *new_iface_wq; ++}; ++ ++static ssize_t apple_layout_id_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct hid_device *hdev = to_hid_device(dev); ++ struct dchid_iface *iface = hdev->driver_data; ++ ++ return scnprintf(buf, PAGE_SIZE, "%d\n", iface->keyboard_layout_id); ++} ++ ++static DEVICE_ATTR_RO(apple_layout_id); ++ ++static struct dchid_iface * ++dchid_get_interface(struct dockchannel_hid *dchid, int index, const char *name) ++{ ++ struct dchid_iface *iface; ++ ++ if (index >= MAX_INTERFACES) { ++ dev_err(dchid->dev, "Interface index %d out of range\n", index); ++ return NULL; ++ } ++ ++ if (dchid->ifaces[index]) ++ return dchid->ifaces[index]; ++ ++ iface = devm_kzalloc(dchid->dev, sizeof(struct dchid_iface), GFP_KERNEL); ++ if (!iface) ++ return NULL; ++ ++ iface->index = index; ++ iface->name = devm_kstrdup(dchid->dev, name, GFP_KERNEL); ++ iface->dchid = dchid; ++ iface->out_report= -1; ++ init_completion(&iface->out_complete); ++ init_completion(&iface->ready); ++ mutex_init(&iface->out_mutex); ++ iface->wq = alloc_ordered_workqueue("dchid-%s", WQ_MEM_RECLAIM, iface->name); ++ if (!iface->wq) ++ return NULL; ++ ++ /* Comm is not a HID subdevice */ ++ if (!strcmp(name, "comm")) { ++ dchid->ifaces[index] = iface; ++ return iface; ++ } ++ ++ iface->of_node = of_get_child_by_name(dchid->dev->of_node, name); ++ if (!iface->of_node) { ++ dev_warn(dchid->dev, "No OF node for subdevice %s, ignoring.", name); ++ return NULL; ++ } ++ ++ dchid->ifaces[index] = iface; ++ return iface; ++} ++ ++static u32 dchid_checksum(void *p, size_t length) ++{ ++ u32 sum = 0; ++ ++ while (length >= 4) { ++ sum += get_unaligned_le32(p); ++ p += 4; ++ length -= 4; ++ } ++ ++ WARN_ON_ONCE(length); ++ return sum; ++} ++ ++static int dchid_send(struct dchid_iface *iface, u32 flags, void *msg, size_t size) ++{ ++ u32 checksum = 0xffffffff; ++ size_t wsize = round_down(size, 4); ++ size_t tsize = size - wsize; ++ int ret; ++ struct { ++ struct dchid_hdr hdr; ++ struct dchid_subhdr sub; ++ } __packed h; ++ ++ memset(&h, 0, sizeof(h)); ++ h.hdr.hdr_len = sizeof(h.hdr); ++ h.hdr.channel = DCHID_CHANNEL_CMD; ++ h.hdr.length = round_up(size, 4) + sizeof(h.sub); ++ h.hdr.seq = iface->tx_seq; ++ h.hdr.iface = iface->index; ++ h.sub.flags = flags; ++ h.sub.length = size; ++ ++ ret = dockchannel_send(iface->dchid->dc, &h, sizeof(h)); ++ if (ret < 0) ++ return ret; ++ checksum -= dchid_checksum(&h, sizeof(h)); ++ ++ ret = dockchannel_send(iface->dchid->dc, msg, wsize); ++ if (ret < 0) ++ return ret; ++ checksum -= dchid_checksum(msg, wsize); ++ ++ if (tsize) { ++ u8 tail[4] = {0, 0, 0, 0}; ++ ++ memcpy(tail, msg + wsize, tsize); ++ ret = dockchannel_send(iface->dchid->dc, tail, sizeof(tail)); ++ if (ret < 0) ++ return ret; ++ checksum -= dchid_checksum(tail, sizeof(tail)); ++ } ++ ++ ret = dockchannel_send(iface->dchid->dc, &checksum, sizeof(checksum)); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req, ++ void *data, size_t size, void *resp_buf, size_t resp_size) ++{ ++ int ret; ++ int report_id = *(u8*)data; ++ ++ mutex_lock(&iface->out_mutex); ++ ++ WARN_ON(iface->out_report != -1); ++ iface->out_report = report_id; ++ iface->out_flags = FIELD_PREP(FLAGS_GROUP, type) | FIELD_PREP(FLAGS_REQ, req); ++ iface->resp_buf = resp_buf; ++ iface->resp_size = resp_size; ++ reinit_completion(&iface->out_complete); ++ ++ ret = dchid_send(iface, iface->out_flags, data, size); ++ if (ret < 0) ++ goto done; ++ ++ if (!wait_for_completion_timeout(&iface->out_complete, msecs_to_jiffies(COMMAND_TIMEOUT_MS))) { ++ dev_err(iface->dchid->dev, "output report 0x%x to iface %d (%s) timed out\n", ++ report_id, iface->index, iface->name); ++ ret = -ETIMEDOUT; ++ goto done; ++ } ++ ++ ret = iface->resp_size; ++ if (iface->retcode) { ++ dev_err(iface->dchid->dev, ++ "output report 0x%x to iface %d (%s) failed with err 0x%x\n", ++ report_id, iface->index, iface->name, iface->retcode); ++ ret = -EIO; ++ } ++ ++done: ++ iface->tx_seq++; ++ iface->out_report = -1; ++ iface->out_flags = 0; ++ iface->resp_buf = NULL; ++ iface->resp_size = 0; ++ mutex_unlock(&iface->out_mutex); ++ return ret; ++} ++ ++static int dchid_comm_cmd(struct dockchannel_hid *dchid, void *cmd, size_t size) ++{ ++ return dchid_cmd(dchid->comm, HID_FEATURE_REPORT, REQ_SET_REPORT, cmd, size, NULL, 0); ++} ++ ++static int dchid_enable_interface(struct dchid_iface *iface) ++{ ++ u8 msg[] = { CMD_ENABLE_INTERFACE, iface->index }; ++ ++ return dchid_comm_cmd(iface->dchid, msg, sizeof(msg)); ++} ++ ++static int dchid_reset_interface(struct dchid_iface *iface, int state) ++{ ++ u8 msg[] = { CMD_RESET_INTERFACE, 1, iface->index, state }; ++ ++ return dchid_comm_cmd(iface->dchid, msg, sizeof(msg)); ++} ++ ++static int dchid_send_firmware(struct dchid_iface *iface, void *firmware, size_t size) ++{ ++ struct { ++ u8 cmd; ++ u8 unk1; ++ u8 unk2; ++ u8 iface; ++ u64 addr; ++ u32 size; ++ } __packed msg = { ++ .cmd = CMD_SEND_FIRMWARE, ++ .unk1 = 2, ++ .unk2 = 0, ++ .iface = iface->index, ++ .size = size, ++ }; ++ dma_addr_t addr; ++ void *buf = dmam_alloc_coherent(iface->dchid->dev, size, &addr, GFP_KERNEL); ++ ++ if (IS_ERR_OR_NULL(buf)) ++ return buf ? PTR_ERR(buf) : -ENOMEM; ++ ++ msg.addr = addr; ++ memcpy(buf, firmware, size); ++ wmb(); ++ ++ return dchid_comm_cmd(iface->dchid, &msg, sizeof(msg)); ++} ++ ++static int dchid_get_firmware(struct dchid_iface *iface, void **firmware, size_t *size) ++{ ++ int ret; ++ const char *fw_name; ++ const struct firmware *fw; ++ struct fw_header *hdr; ++ u8 *fw_data; ++ ++ ret = of_property_read_string(iface->of_node, "firmware-name", &fw_name); ++ if (ret) { ++ /* Firmware is only for some devices */ ++ *firmware = NULL; ++ *size = 0; ++ return 0; ++ } ++ ++ ret = request_firmware(&fw, fw_name, iface->dchid->dev); ++ if (ret) ++ return ret; ++ ++ hdr = (struct fw_header *)fw->data; ++ ++ if (hdr->magic != FW_MAGIC || hdr->version != FW_VER || ++ hdr->hdr_length < sizeof(*hdr) || hdr->hdr_length > fw->size || ++ (hdr->hdr_length + (size_t)hdr->data_length) > fw->size || ++ hdr->iface_offset >= hdr->data_length) { ++ dev_warn(iface->dchid->dev, "%s: invalid firmware header\n", ++ fw_name); ++ ret = -EINVAL; ++ goto done; ++ } ++ ++ fw_data = devm_kmemdup(iface->dchid->dev, fw->data + hdr->hdr_length, ++ hdr->data_length, GFP_KERNEL); ++ if (!fw_data) { ++ ret = -ENOMEM; ++ goto done; ++ } ++ ++ if (hdr->iface_offset) ++ fw_data[hdr->iface_offset] = iface->index; ++ ++ *firmware = fw_data; ++ *size = hdr->data_length; ++ ++done: ++ release_firmware(fw); ++ return ret; ++} ++ ++static int dchid_request_gpio(struct dchid_iface *iface) ++{ ++ char prop_name[MAX_GPIO_NAME + 16]; ++ ++ if (iface->gpio) ++ return 0; ++ ++ dev_info(iface->dchid->dev, "Requesting GPIO %s#%d: %s\n", ++ iface->name, iface->gpio_id, iface->gpio_name); ++ ++ snprintf(prop_name, sizeof(prop_name), "apple,%s", iface->gpio_name); ++ ++ iface->gpio = devm_gpiod_get_index(iface->dchid->dev, prop_name, 0, GPIOD_OUT_LOW); ++ ++ if (IS_ERR_OR_NULL(iface->gpio)) { ++ dev_err(iface->dchid->dev, "Failed to request GPIO %s-gpios\n", prop_name); ++ iface->gpio = NULL; ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int dchid_start_interface(struct dchid_iface *iface) ++{ ++ void *fw; ++ size_t size; ++ int ret; ++ ++ if (iface->starting) { ++ dev_warn(iface->dchid->dev, "Interface %s is already starting", iface->name); ++ return -EINPROGRESS; ++ } ++ ++ dev_info(iface->dchid->dev, "Starting interface %s\n", iface->name); ++ ++ iface->starting = true; ++ ++ /* Look to see if we need firmware */ ++ ret = dchid_get_firmware(iface, &fw, &size); ++ if (ret < 0) ++ goto err; ++ ++ /* If we need a GPIO, make sure we have it. */ ++ if (iface->gpio_id) { ++ ret = dchid_request_gpio(iface); ++ if (ret < 0) ++ goto err; ++ } ++ ++ /* Only multi-touch has firmware */ ++ if (fw && size) { ++ ++ /* Send firmware to the device */ ++ dev_info(iface->dchid->dev, "Sending firmware for %s\n", iface->name); ++ ret = dchid_send_firmware(iface, fw, size); ++ if (ret < 0) { ++ dev_err(iface->dchid->dev, "Failed to send %s firmwareS", iface->name); ++ goto err; ++ } ++ ++ /* After loading firmware, multi-touch needs a reset */ ++ dev_info(iface->dchid->dev, "Resetting %s\n", iface->name); ++ dchid_reset_interface(iface, 0); ++ dchid_reset_interface(iface, 2); ++ } ++ ++ return 0; ++ ++err: ++ iface->starting = false; ++ return ret; ++} ++ ++static int dchid_start(struct hid_device *hdev) ++{ ++ struct dchid_iface *iface = hdev->driver_data; ++ ++ if (iface->keyboard_layout_id) { ++ int ret = device_create_file(&hdev->dev, &dev_attr_apple_layout_id); ++ if (ret) { ++ dev_warn(iface->dchid->dev, "Failed to create apple_layout_id: %d", ret); ++ iface->keyboard_layout_id = 0; ++ } ++ } ++ ++ return 0; ++}; ++ ++static void dchid_stop(struct hid_device *hdev) ++{ ++ struct dchid_iface *iface = hdev->driver_data; ++ ++ if (iface->keyboard_layout_id) ++ device_remove_file(&hdev->dev, &dev_attr_apple_layout_id); ++} ++ ++static int dchid_open(struct hid_device *hdev) ++{ ++ struct dchid_iface *iface = hdev->driver_data; ++ int ret; ++ ++ if (!completion_done(&iface->ready)) { ++ ret = dchid_start_interface(iface); ++ if (ret < 0) ++ return ret; ++ ++ if (!wait_for_completion_timeout(&iface->ready, msecs_to_jiffies(START_TIMEOUT_MS))) { ++ dev_err(iface->dchid->dev, "iface %s start timed out\n", iface->name); ++ return -ETIMEDOUT; ++ } ++ } ++ ++ iface->open = true; ++ return 0; ++} ++ ++static void dchid_close(struct hid_device *hdev) ++{ ++ struct dchid_iface *iface = hdev->driver_data; ++ ++ iface->open = false; ++} ++ ++static int dchid_parse(struct hid_device *hdev) ++{ ++ struct dchid_iface *iface = hdev->driver_data; ++ ++ return hid_parse_report(hdev, iface->hid_desc, iface->hid_desc_len); ++} ++ ++/* Note: buf excludes report number! For ease of fetching strings/etc. */ ++static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum, void *buf, size_t len) ++{ ++ int ret = dchid_cmd(iface, HID_FEATURE_REPORT, REQ_GET_REPORT, &reportnum, 1, buf, len); ++ ++ return ret <= 0 ? ret : ret - 1; ++} ++ ++/* Note: buf includes report number! */ ++static int dchid_set_report(struct dchid_iface *iface, void *buf, size_t len) ++{ ++ return dchid_cmd(iface, HID_OUTPUT_REPORT, REQ_SET_REPORT, buf, len, NULL, 0); ++} ++ ++static int dchid_raw_request(struct hid_device *hdev, ++ unsigned char reportnum, __u8 *buf, size_t len, ++ unsigned char rtype, int reqtype) ++{ ++ struct dchid_iface *iface = hdev->driver_data; ++ ++ switch (reqtype) { ++ case HID_REQ_GET_REPORT: ++ buf[0] = reportnum; ++ return dchid_cmd(iface, rtype, REQ_GET_REPORT, &reportnum, 1, buf + 1, len - 1); ++ case HID_REQ_SET_REPORT: ++ return dchid_set_report(iface, buf, len); ++ default: ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static struct hid_ll_driver dchid_ll = { ++ .start = &dchid_start, ++ .stop = &dchid_stop, ++ .open = &dchid_open, ++ .close = &dchid_close, ++ .parse = &dchid_parse, ++ .raw_request = &dchid_raw_request, ++}; ++ ++static void dchid_create_interface_work(struct work_struct *ws) ++{ ++ struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work); ++ struct dockchannel_hid *dchid = iface->dchid; ++ struct hid_device *hid; ++ int ret; ++ ++ if (iface->hid) { ++ dev_warn(dchid->dev, "Interface %s already created!\n", ++ iface->name); ++ return; ++ } ++ ++ dev_info(dchid->dev, "New interface %s\n", iface->name); ++ ++ /* Start the interface. This is not the entire init process, as firmware is loaded later on device open. */ ++ ret = dchid_enable_interface(iface); ++ if (ret < 0) { ++ dev_warn(dchid->dev, "Failed to enable %s: %d\n", iface->name, ret); ++ return; ++ } ++ ++ iface->deferred = false; ++ ++ hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return; ++ ++ snprintf(hid->name, sizeof(hid->name), "Apple MTP %s", iface->name); ++ snprintf(hid->phys, sizeof(hid->phys), "%s.%d (%s)", ++ dev_name(dchid->dev), iface->index, iface->name); ++ strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq)); ++ ++ hid->ll_driver = &dchid_ll; ++ hid->bus = BUS_HOST; ++ hid->vendor = dchid->device_id.vendor_id; ++ hid->product = dchid->device_id.product_id; ++ hid->version = dchid->device_id.version_number; ++ hid->type = HID_TYPE_OTHER; ++ if (!strcmp(iface->name, "multi-touch")) { ++ hid->type = HID_TYPE_SPI_MOUSE; ++ } else if (!strcmp(iface->name, "keyboard")) { ++ u32 country_code = 0; ++ ++ hid->type = HID_TYPE_SPI_KEYBOARD; ++ ++ /* ++ * We have to get the country code from the device tree, since the ++ * device provides no reliable way to get this info. ++ */ ++ if (!of_property_read_u32(iface->of_node, "hid-country-code", &country_code)) ++ hid->country = country_code; ++ ++ of_property_read_u32(iface->of_node, "apple,keyboard-layout-id", ++ &iface->keyboard_layout_id); ++ } ++ ++ hid->dev.parent = iface->dchid->dev; ++ hid->driver_data = iface; ++ ++ iface->hid = hid; ++ ++ ret = hid_add_device(hid); ++ if (ret < 0) { ++ iface->hid = NULL; ++ hid_destroy_device(hid); ++ dev_warn(iface->dchid->dev, "Failed to register hid device %s", iface->name); ++ } ++} ++ ++static int dchid_create_interface(struct dchid_iface *iface) ++{ ++ if (iface->creating) ++ return -EBUSY; ++ ++ iface->creating = true; ++ INIT_WORK(&iface->create_work, dchid_create_interface_work); ++ return queue_work(iface->dchid->new_iface_wq, &iface->create_work); ++} ++ ++static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc, size_t desc_len) ++{ ++ if (iface->hid) { ++ dev_warn(iface->dchid->dev, "Tried to initialize already started interface %s!\n", ++ iface->name); ++ return; ++ } ++ ++ iface->hid_desc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len, GFP_KERNEL); ++ if (!iface->hid_desc) ++ return; ++ ++ iface->hid_desc_len = desc_len; ++} ++ ++static void dchid_handle_ready(struct dockchannel_hid *dchid, void *data, size_t length) ++{ ++ struct dchid_iface *iface; ++ u8 *pkt = data; ++ u8 index; ++ int i, ret; ++ ++ if (length < 2) { ++ dev_err(dchid->dev, "Bad length for ready message: %zu\n", length); ++ return; ++ } ++ ++ index = pkt[1]; ++ ++ if (index >= MAX_INTERFACES) { ++ dev_err(dchid->dev, "Got ready notification for bad iface %d\n", index); ++ return; ++ } ++ ++ iface = dchid->ifaces[index]; ++ if (!iface) { ++ dev_err(dchid->dev, "Got ready notification for unknown iface %d\n", index); ++ return; ++ } ++ ++ dev_info(dchid->dev, "Interface %s is now ready\n", iface->name); ++ complete_all(&iface->ready); ++ ++ /* When STM is ready, grab global device info */ ++ if (!strcmp(iface->name, "stm")) { ++ ret = dchid_get_report_cmd(iface, STM_REPORT_ID, &dchid->device_id, ++ sizeof(dchid->device_id)); ++ if (ret < sizeof(dchid->device_id)) { ++ dev_warn(iface->dchid->dev, "Failed to get device ID from STM!\n"); ++ /* Fake it and keep going. Things might still work... */ ++ memset(&dchid->device_id, 0, sizeof(dchid->device_id)); ++ dchid->device_id.vendor_id = HOST_VENDOR_ID_APPLE; ++ } ++ ret = dchid_get_report_cmd(iface, STM_REPORT_SERIAL, dchid->serial, ++ sizeof(dchid->serial) - 1); ++ if (ret < 0) { ++ dev_warn(iface->dchid->dev, "Failed to get serial from STM!\n"); ++ dchid->serial[0] = 0; ++ } ++ ++ dchid->id_ready = true; ++ for (i = 0; i < MAX_INTERFACES; i++) { ++ if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred) ++ continue; ++ dchid_create_interface(dchid->ifaces[i]); ++ } ++ } ++} ++ ++static void dchid_handle_init(struct dockchannel_hid *dchid, void *data, size_t length) ++{ ++ struct dchid_init_hdr *hdr = data; ++ struct dchid_iface *iface; ++ struct dchid_init_block_hdr *blk; ++ ++ if (length < sizeof(*hdr)) ++ return; ++ ++ iface = dchid_get_interface(dchid, hdr->iface, hdr->name); ++ if (!iface) ++ return; ++ ++ data += sizeof(*hdr); ++ length -= sizeof(*hdr); ++ ++ while (length >= sizeof(*blk)) { ++ blk = data; ++ data += sizeof(*blk); ++ length -= sizeof(*blk); ++ ++ if (blk->length > length) ++ break; ++ ++ switch (blk->type) { ++ case INIT_HID_DESCRIPTOR: ++ dchid_handle_descriptor(iface, data, blk->length); ++ break; ++ ++ case INIT_GPIO_REQUEST: { ++ struct dchid_gpio_request *req = data; ++ ++ if (sizeof(*req) > length) ++ break; ++ ++ if (iface->gpio_id) { ++ dev_err(dchid->dev, ++ "Cannot request more than one GPIO per interface!\n"); ++ break; ++ } ++ ++ strscpy(iface->gpio_name, req->name, MAX_GPIO_NAME); ++ iface->gpio_id = req->id; ++ break; ++ } ++ ++ case INIT_TERMINATOR: ++ break; ++ ++ case INIT_PRODUCT_NAME: { ++ char *product = data; ++ ++ if (product[blk->length - 1] != 0) { ++ dev_warn(dchid->dev, "Unterminated product name for %s\n", ++ iface->name); ++ } else { ++ dev_info(dchid->dev, "Product name for %s: %s\n", ++ iface->name, product); ++ } ++ break; ++ } ++ ++ default: ++ dev_warn(dchid->dev, "Unknown init packet %d for %s\n", ++ blk->type, iface->name); ++ break; ++ } ++ ++ data += blk->length; ++ length -= blk->length; ++ ++ if (blk->type == INIT_TERMINATOR) ++ break; ++ } ++ ++ if (hdr->more_packets) ++ return; ++ ++ /* We need to enable STM first, since it'll give us the device IDs */ ++ if (iface->dchid->id_ready || !strcmp(iface->name, "stm")) { ++ dchid_create_interface(iface); ++ } else { ++ iface->deferred = true; ++ } ++} ++ ++static void dchid_handle_gpio(struct dockchannel_hid *dchid, void *data, size_t length) ++{ ++ struct dchid_gpio_cmd *cmd = data; ++ struct dchid_iface *iface; ++ u32 retcode = 0xe000f00d; /* Give it a random Apple-style error code */ ++ struct dchid_gpio_ack *ack; ++ ++ if (length < sizeof(*cmd)) ++ return; ++ ++ if (cmd->iface >= MAX_INTERFACES || !(iface = dchid->ifaces[cmd->iface])) { ++ dev_err(dchid->dev, "Got GPIO command for bad inteface %d\n", cmd->iface); ++ goto err; ++ } ++ ++ if (dchid_request_gpio(iface) < 0) ++ goto err; ++ ++ if (!iface->gpio || cmd->gpio != iface->gpio_id) { ++ dev_err(dchid->dev, "Got GPIO command for bad GPIO %s#%d\n", ++ iface->name, cmd->gpio); ++ goto err; ++ } ++ ++ dev_info(dchid->dev, "GPIO command: %s#%d: %d\n", iface->name, cmd->gpio, cmd->cmd); ++ ++ switch (cmd->cmd) { ++ case 3: ++ /* Pulse. */ ++ gpiod_set_value_cansleep(iface->gpio, 1); ++ msleep(10); /* Random guess... */ ++ gpiod_set_value_cansleep(iface->gpio, 0); ++ retcode = 0; ++ break; ++ default: ++ dev_err(dchid->dev, "Unknown GPIO command %d\n", cmd->cmd ); ++ break; ++ } ++ ++err: ++ /* Ack it */ ++ ack = kzalloc(sizeof(*ack) + length, GFP_KERNEL); ++ if (!ack) ++ return; ++ ++ ack->type = CMD_ACK_GPIO_CMD; ++ ack->retcode = retcode; ++ memcpy(ack->cmd, data, length); ++ ++ if (dchid_comm_cmd(dchid, ack, sizeof(*ack) + length) < 0) ++ dev_err(dchid->dev, "Failed to ACK GPIO command\n"); ++ ++ kfree(ack); ++} ++ ++static void dchid_handle_event(struct dockchannel_hid *dchid, void *data, size_t length) ++{ ++ u8 *p = data; ++ switch (*p) { ++ case EVENT_INIT: ++ dchid_handle_init(dchid, data, length); ++ break; ++ case EVENT_READY: ++ dchid_handle_ready(dchid, data, length); ++ break; ++ case EVENT_GPIO_CMD: ++ dchid_handle_gpio(dchid, data, length); ++ break; ++ } ++} ++ ++static void dchid_handle_report(struct dchid_iface *iface, void *data, size_t length) ++{ ++ struct dockchannel_hid *dchid = iface->dchid; ++ ++ if (!iface->hid) { ++ dev_warn(dchid->dev, "Report received but %s is not initialized!\n", iface->name); ++ return; ++ } ++ ++ if (!iface->open) ++ return; ++ ++ hid_input_report(iface->hid, HID_INPUT_REPORT, data, length, 1); ++} ++ ++static void dchid_packet_work(struct work_struct *ws) ++{ ++ struct dchid_work *work = container_of(ws, struct dchid_work, work); ++ struct dchid_subhdr *shdr = (void *)work->data; ++ struct dockchannel_hid *dchid = work->iface->dchid; ++ int type = FIELD_GET(FLAGS_GROUP, shdr->flags); ++ u8 *payload = work->data + sizeof(*shdr); ++ ++ if (shdr->length + sizeof(*shdr) > work->hdr.length) { ++ dev_err(dchid->dev, "Bad sub header length (%d > %zu)\n", ++ shdr->length, work->hdr.length - sizeof(*shdr)); ++ return; ++ } ++ ++ switch (type) { ++ case HID_INPUT_REPORT: ++ if (work->hdr.iface == IFACE_COMM) ++ dchid_handle_event(dchid, payload, shdr->length); ++ else ++ dchid_handle_report(work->iface, payload, shdr->length); ++ break; ++ default: ++ dev_err(dchid->dev, "Received unknown packet type %d\n", type); ++ break; ++ } ++ ++ kfree(work); ++} ++ ++static void dchid_handle_ack(struct dchid_iface *iface, struct dchid_hdr *hdr, void *data) ++{ ++ struct dchid_subhdr *shdr = (void *)data; ++ u8 *payload = data + sizeof(*shdr); ++ ++ if (shdr->length + sizeof(*shdr) > hdr->length) { ++ dev_err(iface->dchid->dev, "Bad sub header length (%d > %ld)\n", ++ shdr->length, hdr->length - sizeof(*shdr)); ++ return; ++ } ++ if (shdr->flags != iface->out_flags) { ++ dev_err(iface->dchid->dev, ++ "Received unexpected flags 0x%x on ACK channel (expFected 0x%x)\n", ++ shdr->flags, iface->out_flags); ++ return; ++ } ++ ++ if (shdr->length < 1) { ++ dev_err(iface->dchid->dev, "Received length 0 output report ack\n"); ++ return; ++ } ++ if (iface->tx_seq != hdr->seq) { ++ dev_err(iface->dchid->dev, "Received ACK with bad seq (expected %d, got %d)\n", ++ iface->tx_seq, hdr->seq); ++ return; ++ } ++ if (iface->out_report != payload[0]) { ++ dev_err(iface->dchid->dev, "Received ACK with bad report (expected %d, got %d\n", ++ iface->out_report, payload[0]); ++ return; ++ } ++ ++ if (iface->resp_buf && iface->resp_size) ++ memcpy(iface->resp_buf, payload + 1, min((size_t)shdr->length - 1, iface->resp_size)); ++ ++ iface->resp_size = shdr->length; ++ iface->out_report = -1; ++ iface->retcode = shdr->retcode; ++ complete(&iface->out_complete); ++} ++ ++static void dchid_handle_packet(void *cookie, size_t avail) ++{ ++ struct dockchannel_hid *dchid = cookie; ++ struct dchid_hdr hdr; ++ struct dchid_work *work; ++ struct dchid_iface *iface; ++ u32 checksum; ++ ++ if (dockchannel_recv(dchid->dc, &hdr, sizeof(hdr)) != sizeof(hdr)) { ++ dev_err(dchid->dev, "Read failed (header)\n"); ++ return; ++ } ++ ++ if (hdr.hdr_len != sizeof(hdr)) { ++ dev_err(dchid->dev, "Bad header length %d\n", hdr.hdr_len); ++ goto done; ++ } ++ ++ if (dockchannel_recv(dchid->dc, dchid->pkt_buf, hdr.length + 4) != (hdr.length + 4)) { ++ dev_err(dchid->dev, "Read failed (body)\n"); ++ goto done; ++ } ++ ++ checksum = dchid_checksum(&hdr, sizeof(hdr)); ++ checksum += dchid_checksum(dchid->pkt_buf, hdr.length + 4); ++ ++ if (checksum != 0xffffffff) { ++ dev_err(dchid->dev, "Checksum mismatch (iface %d): 0x%08x != 0xffffffff\n", ++ hdr.iface, checksum); ++ goto done; ++ } ++ ++ ++ if (hdr.iface >= MAX_INTERFACES) { ++ dev_err(dchid->dev, "Bad iface %d\n", hdr.iface); ++ } ++ ++ iface = dchid->ifaces[hdr.iface]; ++ ++ if (!iface) { ++ dev_err(dchid->dev, "Received packet for uninitialized iface %d\n", hdr.iface); ++ goto done; ++ } ++ ++ switch (hdr.channel) { ++ case DCHID_CHANNEL_CMD: ++ dchid_handle_ack(iface, &hdr, dchid->pkt_buf); ++ goto done; ++ case DCHID_CHANNEL_REPORT: ++ break; ++ default: ++ dev_warn(dchid->dev, "Unknown channel 0x%x, treating as report...\n", ++ hdr.channel); ++ break; ++ } ++ ++ work = kzalloc(sizeof(*work) + hdr.length, GFP_KERNEL); ++ if (!work) ++ return; ++ ++ work->hdr = hdr; ++ work->iface = iface; ++ memcpy(work->data, dchid->pkt_buf, hdr.length); ++ INIT_WORK(&work->work, dchid_packet_work); ++ ++ queue_work(iface->wq, &work->work); ++ ++done: ++ dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr)); ++} ++ ++static int dockchannel_hid_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct dockchannel_hid *dchid; ++ struct device_node *child, *helper; ++ struct platform_device *helper_pdev; ++ struct property *prop; ++ int ret; ++ ++ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); ++ if (ret) ++ return ret; ++ ++ dchid = devm_kzalloc(dev, sizeof(*dchid), GFP_KERNEL); ++ if (!dchid) { ++ return -ENOMEM; ++ } ++ ++ dchid->dev = dev; ++ ++ /* ++ * First make sure all the GPIOs are available, in cased we need to defer. ++ * This is necessary because MTP will request them by name later, and by then ++ * it's too late to defer the probe. ++ */ ++ ++ for_each_child_of_node(dev->of_node, child) { ++ for_each_property_of_node(child, prop) { ++ size_t len = strlen(prop->name); ++ struct gpio_desc *gpio; ++ ++ if (len < 12 || strncmp("apple,", prop->name, 6) || ++ strcmp("-gpios", prop->name + len - 6)) ++ continue; ++ ++ gpio = fwnode_gpiod_get_index(&child->fwnode, prop->name, 0, GPIOD_ASIS, ++ prop->name); ++ if (IS_ERR_OR_NULL(gpio)) { ++ if (PTR_ERR(gpio) == -EPROBE_DEFER) { ++ of_node_put(child); ++ return -EPROBE_DEFER; ++ } ++ } else { ++ gpiod_put(gpio); ++ } ++ } ++ } ++ ++ /* ++ * Make sure we also have the MTP coprocessor available, and ++ * defer probe if the helper hasn't probed yet. ++ */ ++ helper = of_parse_phandle(dev->of_node, "apple,helper-cpu", 0); ++ if (!helper) { ++ dev_err(dev, "Missing apple,helper-cpu property"); ++ return -EINVAL; ++ } ++ ++ helper_pdev = of_find_device_by_node(helper); ++ of_node_put(helper); ++ if (!helper_pdev) { ++ dev_err(dev, "Failed to find helper device"); ++ return -EINVAL; ++ } ++ ++ dchid->helper_link = device_link_add(dev, &helper_pdev->dev, ++ DL_FLAG_AUTOREMOVE_CONSUMER); ++ put_device(&helper_pdev->dev); ++ if (!dchid->helper_link) { ++ dev_err(dev, "Failed to link to helper device"); ++ return -EINVAL; ++ } ++ ++ if (dchid->helper_link->supplier->links.status != DL_DEV_DRIVER_BOUND) ++ return -EPROBE_DEFER; ++ ++ /* Now it is safe to begin initializing */ ++ dchid->dc = dockchannel_init(pdev); ++ if (IS_ERR_OR_NULL(dchid->dc)) { ++ return PTR_ERR(dchid->dc); ++ } ++ dchid->new_iface_wq = alloc_workqueue("dchid-new", WQ_MEM_RECLAIM, 0); ++ if (!dchid->new_iface_wq) ++ return -ENOMEM; ++ ++ dchid->comm = dchid_get_interface(dchid, IFACE_COMM, "comm"); ++ if (!dchid->comm) { ++ dev_err(dchid->dev, "Failed to initialize comm interface"); ++ return -EIO; ++ } ++ ++ dev_info(dchid->dev, "Initialized, awaiting packets\n"); ++ dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr)); ++ ++ return 0; ++} ++ ++static void dockchannel_hid_remove(struct platform_device *pdev) ++{ ++ BUG_ON(1); ++} ++ ++static const struct of_device_id dockchannel_hid_of_match[] = { ++ { .compatible = "apple,dockchannel-hid" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, dockchannel_hid_of_match); ++MODULE_FIRMWARE("apple/tpmtfw-*.bin"); ++ ++static struct platform_driver dockchannel_hid_driver = { ++ .driver = { ++ .name = "dockchannel-hid", ++ .of_match_table = dockchannel_hid_of_match, ++ }, ++ .probe = dockchannel_hid_probe, ++ .remove = dockchannel_hid_remove, ++}; ++module_platform_driver(dockchannel_hid_driver); ++ ++MODULE_DESCRIPTION("Apple DockChannel HID transport driver"); ++MODULE_AUTHOR("Hector Martin "); ++MODULE_LICENSE("Dual MIT/GPL"); +-- +2.43.0 + + +From 29394718f2114a8081ac0b1947d2651908ee9bde Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Sun, 3 Jul 2022 23:33:37 +0900 +Subject: [PATCH 17/26] soc: apple: Add RTKit helper driver + +This driver can be used for coprocessors that do some background task or +communicate out-of-band, and do not do any mailbox I/O beyond the +standard RTKit initialization. + +Signed-off-by: Hector Martin +--- + drivers/soc/apple/Kconfig | 14 +++ + drivers/soc/apple/Makefile | 3 + + drivers/soc/apple/rtkit-helper.c | 151 +++++++++++++++++++++++++++++++ + 3 files changed, 168 insertions(+) + create mode 100644 drivers/soc/apple/rtkit-helper.c + +diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig +index 82d0edadf..50f092732 100644 +--- a/drivers/soc/apple/Kconfig ++++ b/drivers/soc/apple/Kconfig +@@ -40,6 +40,20 @@ config APPLE_RTKIT + + Say 'y' here if you have an Apple SoC. + ++config APPLE_RTKIT_HELPER ++ tristate "Apple Generic RTKit helper co-processor" ++ depends on APPLE_RTKIT ++ depends on ARCH_APPLE || COMPILE_TEST ++ default ARCH_APPLE ++ help ++ Apple SoCs such as the M1 come with various co-processors running ++ their proprietary RTKit operating system. This option enables support ++ for a generic co-processor that does not implement any additional ++ in-band communications. It can be used for testing purposes, or for ++ coprocessors such as MTP that communicate over a different interface. ++ ++ Say 'y' here if you have an Apple SoC. ++ + config APPLE_SART + tristate "Apple SART DMA address filter" + depends on ARCH_APPLE || COMPILE_TEST +diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile +index 0b6a9f92b..5e526a9ed 100644 +--- a/drivers/soc/apple/Makefile ++++ b/drivers/soc/apple/Makefile +@@ -9,5 +9,8 @@ apple-mailbox-y = mailbox.o + obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o + apple-rtkit-y = rtkit.o rtkit-crashlog.o + ++obj-$(CONFIG_APPLE_RTKIT_HELPER) += apple-rtkit-helper.o ++apple-rtkit-helper-y = rtkit-helper.o ++ + obj-$(CONFIG_APPLE_SART) += apple-sart.o + apple-sart-y = sart.o +diff --git a/drivers/soc/apple/rtkit-helper.c b/drivers/soc/apple/rtkit-helper.c +new file mode 100644 +index 000000000..080d083ed +--- /dev/null ++++ b/drivers/soc/apple/rtkit-helper.c +@@ -0,0 +1,151 @@ ++// SPDX-License-Identifier: GPL-2.0-only OR MIT ++/* ++ * Apple Generic RTKit helper coprocessor ++ * Copyright The Asahi Linux Contributors ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define APPLE_ASC_CPU_CONTROL 0x44 ++#define APPLE_ASC_CPU_CONTROL_RUN BIT(4) ++ ++struct apple_rtkit_helper { ++ struct device *dev; ++ struct apple_rtkit *rtk; ++ ++ void __iomem *asc_base; ++ ++ struct resource *sram; ++ void __iomem *sram_base; ++}; ++ ++static int apple_rtkit_helper_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) ++{ ++ struct apple_rtkit_helper *helper = cookie; ++ struct resource res = { ++ .start = bfr->iova, ++ .end = bfr->iova + bfr->size - 1, ++ .name = "rtkit_map", ++ }; ++ ++ if (!bfr->iova) { ++ bfr->buffer = dma_alloc_coherent(helper->dev, bfr->size, ++ &bfr->iova, GFP_KERNEL); ++ if (!bfr->buffer) ++ return -ENOMEM; ++ return 0; ++ } ++ ++ if (!helper->sram) { ++ dev_err(helper->dev, ++ "RTKit buffer request with no SRAM region: %pR", &res); ++ return -EFAULT; ++ } ++ ++ res.flags = helper->sram->flags; ++ ++ if (res.end < res.start || !resource_contains(helper->sram, &res)) { ++ dev_err(helper->dev, ++ "RTKit buffer request outside SRAM region: %pR", &res); ++ return -EFAULT; ++ } ++ ++ bfr->iomem = helper->sram_base + (res.start - helper->sram->start); ++ bfr->is_mapped = true; ++ ++ return 0; ++} ++ ++static void apple_rtkit_helper_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) ++{ ++ // no-op ++} ++ ++static const struct apple_rtkit_ops apple_rtkit_helper_ops = { ++ .shmem_setup = apple_rtkit_helper_shmem_setup, ++ .shmem_destroy = apple_rtkit_helper_shmem_destroy, ++}; ++ ++static int apple_rtkit_helper_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct apple_rtkit_helper *helper; ++ int ret; ++ ++ /* 44 bits for addresses in standard RTKit requests */ ++ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(44)); ++ if (ret) ++ return ret; ++ ++ helper = devm_kzalloc(dev, sizeof(*helper), GFP_KERNEL); ++ if (!helper) ++ return -ENOMEM; ++ ++ helper->dev = dev; ++ platform_set_drvdata(pdev, helper); ++ ++ helper->asc_base = devm_platform_ioremap_resource_byname(pdev, "asc"); ++ if (IS_ERR(helper->asc_base)) ++ return PTR_ERR(helper->asc_base); ++ ++ helper->sram = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); ++ if (helper->sram) { ++ helper->sram_base = devm_ioremap_resource(dev, helper->sram); ++ if (IS_ERR(helper->sram_base)) ++ return dev_err_probe(dev, PTR_ERR(helper->sram_base), ++ "Failed to map SRAM region"); ++ } ++ ++ helper->rtk = ++ devm_apple_rtkit_init(dev, helper, NULL, 0, &apple_rtkit_helper_ops); ++ if (IS_ERR(helper->rtk)) ++ return dev_err_probe(dev, PTR_ERR(helper->rtk), ++ "Failed to intialize RTKit"); ++ ++ writel_relaxed(APPLE_ASC_CPU_CONTROL_RUN, ++ helper->asc_base + APPLE_ASC_CPU_CONTROL); ++ ++ /* Works for both wake and boot */ ++ ret = apple_rtkit_wake(helper->rtk); ++ if (ret != 0) ++ return dev_err_probe(dev, ret, "Failed to wake up coprocessor"); ++ ++ return 0; ++} ++ ++static void apple_rtkit_helper_remove(struct platform_device *pdev) ++{ ++ struct apple_rtkit_helper *helper = platform_get_drvdata(pdev); ++ ++ if (apple_rtkit_is_running(helper->rtk)) ++ apple_rtkit_quiesce(helper->rtk); ++ ++ writel_relaxed(0, helper->asc_base + APPLE_ASC_CPU_CONTROL); ++} ++ ++static const struct of_device_id apple_rtkit_helper_of_match[] = { ++ { .compatible = "apple,rtk-helper-asc4" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, apple_rtkit_helper_of_match); ++ ++static struct platform_driver apple_rtkit_helper_driver = { ++ .driver = { ++ .name = "rtkit-helper", ++ .of_match_table = apple_rtkit_helper_of_match, ++ }, ++ .probe = apple_rtkit_helper_probe, ++ .remove = apple_rtkit_helper_remove, ++}; ++module_platform_driver(apple_rtkit_helper_driver); ++ ++MODULE_AUTHOR("Hector Martin "); ++MODULE_LICENSE("Dual MIT/GPL"); ++MODULE_DESCRIPTION("Apple RTKit helper driver"); +-- +2.43.0 + + +From 2da9abc727369ec03915d3b7fbfef4e03bfa396e Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Sun, 11 Dec 2022 20:08:45 +0100 +Subject: [PATCH 18/26] HID: transport: spi: Check status message after + transmits + +Probably pointless but might be helpful to debug issues. +Gets rid of 'spi_hid_apple_status_ok' is unused compiler warning. + +Signed-off-by: Janne Grunau +--- + drivers/hid/spi-hid/spi-hid-apple-core.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c +index 67c70edbe..a10a5a38b 100644 +--- a/drivers/hid/spi-hid/spi-hid-apple-core.c ++++ b/drivers/hid/spi-hid/spi-hid-apple-core.c +@@ -248,7 +248,17 @@ static int spihid_apple_request(struct spihid_apple *spihid, u8 target, u8 unk0, + pkt->crc16 = cpu_to_le16(crc16(0, spihid->tx_buf, + offsetof(struct spihid_transfer_packet, crc16))); + ++ memset(spihid->status_buf, 0, sizeof(spi_hid_apple_status_ok)); ++ + err = spi_sync(spihid->spidev, &spihid->tx_msg); ++ ++ if (memcmp(spihid->status_buf, spi_hid_apple_status_ok, ++ sizeof(spi_hid_apple_status_ok))) { ++ u8 *b = spihid->status_buf; ++ dev_warn_ratelimited(&spihid->spidev->dev, "status message " ++ "mismatch: %02x %02x %02x %02x\n", ++ b[0], b[1], b[2], b[3]); ++ } + mutex_unlock(&spihid->tx_lock); + if (err < 0) + return err; +-- +2.43.0 + + +From 5a95df27fe58e5257ce920d17b6bc6270a9ca39c Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Sun, 11 Dec 2022 22:56:16 +0100 +Subject: [PATCH 19/26] HID: magicmouse: Add .reset_resume for SPI trackpads + +The trackpad has to request multi touch reports during resume. + +Signed-off-by: Janne Grunau +--- + drivers/hid/hid-magicmouse.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index 38f000db5..a2cbf8652 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -1299,6 +1299,16 @@ static const struct hid_device_id magic_mice[] = { + }; + MODULE_DEVICE_TABLE(hid, magic_mice); + ++#ifdef CONFIG_PM ++static int magicmouse_reset_resume(struct hid_device *hdev) ++{ ++ if (hdev->bus == BUS_SPI) ++ return magicmouse_enable_multitouch(hdev); ++ ++ return 0; ++} ++#endif ++ + static struct hid_driver magicmouse_driver = { + .name = "magicmouse", + .id_table = magic_mice, +@@ -1309,6 +1319,10 @@ static struct hid_driver magicmouse_driver = { + .event = magicmouse_event, + .input_mapping = magicmouse_input_mapping, + .input_configured = magicmouse_input_configured, ++#ifdef CONFIG_PM ++ .reset_resume = magicmouse_reset_resume, ++#endif ++ + }; + module_hid_driver(magicmouse_driver); + +-- +2.43.0 + + +From 38d46cf571361d1b0c1c2af4fb8f9858a16e4522 Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Sun, 11 Dec 2022 23:24:41 +0100 +Subject: [PATCH 20/26] HID: transport: spi: Add suspend support + +Working suspend and resume. The keyboard can not yet used as wakeup +source. Most likely caused by missing irq_set_wake support in the +gpio/pinctrl driver. + +Signed-off-by: Janne Grunau +--- + drivers/hid/spi-hid/spi-hid-apple-core.c | 89 ++++++++++++++++++++++++ + drivers/hid/spi-hid/spi-hid-apple-of.c | 19 ++++- + drivers/hid/spi-hid/spi-hid-apple.h | 4 ++ + 3 files changed, 110 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c +index a10a5a38b..d48cb559a 100644 +--- a/drivers/hid/spi-hid/spi-hid-apple-core.c ++++ b/drivers/hid/spi-hid/spi-hid-apple-core.c +@@ -115,6 +115,10 @@ struct spihid_apple { + + /* state tracking flags */ + bool status_booted; ++ ++#ifdef IRQ_WAKE_SUPPORT ++ bool irq_wake_enabled; ++#endif + }; + + /** +@@ -1038,6 +1042,91 @@ void spihid_apple_core_shutdown(struct spi_device *spi) + } + EXPORT_SYMBOL_GPL(spihid_apple_core_shutdown); + ++#ifdef CONFIG_PM_SLEEP ++static int spihid_apple_core_suspend(struct device *dev) ++{ ++ int ret; ++#ifdef IRQ_WAKE_SUPPORT ++ int wake_status; ++#endif ++ struct spihid_apple *spihid = spi_get_drvdata(to_spi_device(dev)); ++ ++ if (spihid->tp.hid) { ++ ret = hid_driver_suspend(spihid->tp.hid, PMSG_SUSPEND); ++ if (ret < 0) ++ return ret; ++ } ++ ++ if (spihid->kbd.hid) { ++ ret = hid_driver_suspend(spihid->kbd.hid, PMSG_SUSPEND); ++ if (ret < 0) { ++ if (spihid->tp.hid) ++ hid_driver_resume(spihid->tp.hid); ++ return ret; ++ } ++ } ++ ++ /* Save some power */ ++ spihid->ops->disable_irq(spihid->ops); ++ ++#ifdef IRQ_WAKE_SUPPORT ++ if (device_may_wakeup(dev)) { ++ wake_status = spihid->ops->enable_irq_wake(spihid->ops); ++ if (!wake_status) ++ spihid->irq_wake_enabled = true; ++ else ++ dev_warn(dev, "Failed to enable irq wake: %d\n", ++ wake_status); ++ } else { ++ spihid->ops->power_off(spihid->ops); ++ } ++#else ++ spihid->ops->power_off(spihid->ops); ++#endif ++ ++ return 0; ++} ++ ++static int spihid_apple_core_resume(struct device *dev) ++{ ++ int ret_tp = 0, ret_kbd = 0; ++ struct spihid_apple *spihid = spi_get_drvdata(to_spi_device(dev)); ++#ifdef IRQ_WAKE_SUPPORT ++ int wake_status; ++ ++ if (!device_may_wakeup(dev)) { ++ spihid->ops->power_on(spihid->ops); ++ } else if (spihid->irq_wake_enabled) { ++ wake_status = spihid->ops->disable_irq_wake(spihid->ops); ++ if (!wake_status) ++ spihid->irq_wake_enabled = false; ++ else ++ dev_warn(dev, "Failed to disable irq wake: %d\n", ++ wake_status); ++ } ++#endif ++ ++ spihid->ops->enable_irq(spihid->ops); ++ spihid->ops->power_on(spihid->ops); ++ ++ if (spihid->tp.hid) ++ ret_tp = hid_driver_reset_resume(spihid->tp.hid); ++ if (spihid->kbd.hid) ++ ret_kbd = hid_driver_reset_resume(spihid->kbd.hid); ++ ++ if (ret_tp < 0) ++ return ret_tp; ++ ++ return ret_kbd; ++} ++#endif ++ ++const struct dev_pm_ops spihid_apple_core_pm = { ++ SET_SYSTEM_SLEEP_PM_OPS(spihid_apple_core_suspend, ++ spihid_apple_core_resume) ++}; ++EXPORT_SYMBOL_GPL(spihid_apple_core_pm); ++ + MODULE_DESCRIPTION("Apple SPI HID transport driver"); + MODULE_AUTHOR("Janne Grunau "); + MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/spi-hid/spi-hid-apple-of.c b/drivers/hid/spi-hid/spi-hid-apple-of.c +index f1380bfc5..3f87b2993 100644 +--- a/drivers/hid/spi-hid/spi-hid-apple-of.c ++++ b/drivers/hid/spi-hid/spi-hid-apple-of.c +@@ -65,6 +65,20 @@ static int spihid_apple_of_disable_irq(struct spihid_apple_ops *ops) + return 0; + } + ++static int spihid_apple_of_enable_irq_wake(struct spihid_apple_ops *ops) ++{ ++ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); ++ ++ return enable_irq_wake(sh_of->irq); ++} ++ ++static int spihid_apple_of_disable_irq_wake(struct spihid_apple_ops *ops) ++{ ++ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); ++ ++ return disable_irq_wake(sh_of->irq); ++} ++ + static int spihid_apple_of_probe(struct spi_device *spi) + { + struct device *dev = &spi->dev; +@@ -79,6 +93,8 @@ static int spihid_apple_of_probe(struct spi_device *spi) + spihid_of->ops.power_off = spihid_apple_of_power_off; + spihid_of->ops.enable_irq = spihid_apple_of_enable_irq; + spihid_of->ops.disable_irq = spihid_apple_of_disable_irq; ++ spihid_of->ops.enable_irq_wake = spihid_apple_of_enable_irq_wake; ++ spihid_of->ops.disable_irq_wake = spihid_apple_of_disable_irq_wake; + + spihid_of->enable_gpio = devm_gpiod_get_index(dev, "spien", 0, 0); + if (IS_ERR(spihid_of->enable_gpio)) { +@@ -120,8 +136,7 @@ MODULE_DEVICE_TABLE(spi, spihid_apple_of_id); + static struct spi_driver spihid_apple_of_driver = { + .driver = { + .name = "spi-hid-apple-of", +- //.pm = &spi_hid_apple_of_pm, +- .owner = THIS_MODULE, ++ .pm = &spihid_apple_core_pm, + .of_match_table = of_match_ptr(spihid_apple_of_match), + }, + +diff --git a/drivers/hid/spi-hid/spi-hid-apple.h b/drivers/hid/spi-hid/spi-hid-apple.h +index 2d9554e8a..9abecd1ba 100644 +--- a/drivers/hid/spi-hid/spi-hid-apple.h ++++ b/drivers/hid/spi-hid/spi-hid-apple.h +@@ -20,6 +20,8 @@ struct spihid_apple_ops { + int (*power_off)(struct spihid_apple_ops *ops); + int (*enable_irq)(struct spihid_apple_ops *ops); + int (*disable_irq)(struct spihid_apple_ops *ops); ++ int (*enable_irq_wake)(struct spihid_apple_ops *ops); ++ int (*disable_irq_wake)(struct spihid_apple_ops *ops); + }; + + irqreturn_t spihid_apple_core_irq(int irq, void *data); +@@ -28,4 +30,6 @@ int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops + void spihid_apple_core_remove(struct spi_device *spi); + void spihid_apple_core_shutdown(struct spi_device *spi); + ++extern const struct dev_pm_ops spihid_apple_core_pm; ++ + #endif /* SPI_HID_APPLE_H */ +-- +2.43.0 + + +From 4d78e57cf83ab0aace665182346a907be3004b0b Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Mon, 10 Apr 2023 22:44:44 +0900 +Subject: [PATCH 21/26] HID: Bump maximum report size to 16384 + +This maximum is arbitrary. Recent Apple devices have some vendor-defined +reports with 16384 here which fail to parse without this, so let's bump +it to that. + +This value is used as follows: + +report->size += parser->global.report_size * parser->global.report_count; + +[...] + +/* Total size check: Allow for possible report index byte */ +if (report->size > (max_buffer_size - 1) << 3) { + hid_err(parser->device, "report is too long\n"); + return -1; +} + +All of these fields are unsigned integers, and report_count is bounded +by HID_MAX_USAGES (12288). Therefore, as long as the respective maximums +do not overflow an unsigned integer (let's say a signed integer just in +case), we're safe. This holds for 16384. + +Signed-off-by: Hector Martin +--- + drivers/hid/hid-core.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c +index 9976ee646..1a718cc41 100644 +--- a/drivers/hid/hid-core.c ++++ b/drivers/hid/hid-core.c +@@ -464,7 +464,10 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item) + + case HID_GLOBAL_ITEM_TAG_REPORT_SIZE: + parser->global.report_size = item_udata(item); +- if (parser->global.report_size > 256) { ++ /* Arbitrary maximum. Some Apple devices have 16384 here. ++ * This * HID_MAX_USAGES must fit in a signed integer. ++ */ ++ if (parser->global.report_size > 16384) { + hid_err(parser->device, "invalid report_size %d\n", + parser->global.report_size); + return -1; +-- +2.43.0 + + +From 75ff2988c9c70c2a4d0f4733dc1405456ecd5f1d Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Sun, 30 Apr 2023 23:48:45 +0900 +Subject: [PATCH 22/26] HID: magicmouse: Handle touch controller resets on SPI + devices + +On at least some SPI devices (e.g. recent Apple Silicon machines), the +Broadcom touch controller is prone to crashing. When this happens, the +STM eventually notices and resets it. It then notifies the driver via +HID report 0x60, and the driver needs to re-enable MT mode to make +things work again. + +This poses an additional issue: the hidinput core will close the +low-level transport while the device is closed, which can cause us to +miss a reset notification. To fix this, override the input open/close +callbacks and send the MT enable every time the HID device is opened, +instead of only once on probe. This should increase general robustness, +even if the reset mechanism doesn't work for some reason, so it's worth +doing it for USB devices too. MTP devices are exempt since they do not +require the MT enable at all. + +Signed-off-by: Hector Martin +--- + drivers/hid/hid-magicmouse.c | 205 +++++++++++++++++++++++------------ + 1 file changed, 134 insertions(+), 71 deletions(-) + +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index a2cbf8652..87f5f371c 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -60,6 +60,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define MOUSE2_REPORT_ID 0x12 + #define DOUBLE_REPORT_ID 0xf7 + #define SPI_REPORT_ID 0x02 ++#define SPI_RESET_REPORT_ID 0x60 + #define MTP_REPORT_ID 0x75 + #define USB_BATTERY_TIMEOUT_MS 60000 + +@@ -175,6 +176,98 @@ struct magicmouse_sc { + struct magicmouse_input_ops input_ops; + }; + ++static int magicmouse_enable_multitouch(struct hid_device *hdev) ++{ ++ const u8 *feature; ++ const u8 feature_mt[] = { 0xD7, 0x01 }; ++ const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 }; ++ const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 }; ++ const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 }; ++ u8 *buf; ++ int ret; ++ int feature_size; ++ ++ if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 || ++ hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) { ++ if (hdev->vendor == BT_VENDOR_ID_APPLE) { ++ feature_size = sizeof(feature_mt_trackpad2_bt); ++ feature = feature_mt_trackpad2_bt; ++ } else { /* USB_VENDOR_ID_APPLE */ ++ feature_size = sizeof(feature_mt_trackpad2_usb); ++ feature = feature_mt_trackpad2_usb; ++ } ++ } else if (hdev->vendor == SPI_VENDOR_ID_APPLE) { ++ feature_size = sizeof(feature_mt_trackpad2_usb); ++ feature = feature_mt_trackpad2_usb; ++ } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { ++ feature_size = sizeof(feature_mt_mouse2); ++ feature = feature_mt_mouse2; ++ } else { ++ feature_size = sizeof(feature_mt); ++ feature = feature_mt; ++ } ++ ++ buf = kmemdup(feature, feature_size, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size, ++ HID_FEATURE_REPORT, HID_REQ_SET_REPORT); ++ kfree(buf); ++ return ret; ++} ++ ++static void magicmouse_enable_mt_work(struct work_struct *work) ++{ ++ struct magicmouse_sc *msc = ++ container_of(work, struct magicmouse_sc, work.work); ++ int ret; ++ ++ ret = magicmouse_enable_multitouch(msc->hdev); ++ if (ret < 0) ++ hid_err(msc->hdev, "unable to request touch data (%d)\n", ret); ++} ++ ++static int magicmouse_open(struct input_dev *dev) ++{ ++ struct hid_device *hdev = input_get_drvdata(dev); ++ struct magicmouse_sc *msc = hid_get_drvdata(hdev); ++ int ret; ++ ++ ret = hid_hw_open(hdev); ++ if (ret) ++ return ret; ++ ++ /* ++ * Some devices repond with 'invalid report id' when feature ++ * report switching it into multitouch mode is sent to it. ++ * ++ * This results in -EIO from the _raw low-level transport callback, ++ * but there seems to be no other way of switching the mode. ++ * Thus the super-ugly hacky success check below. ++ */ ++ ret = magicmouse_enable_multitouch(hdev); ++ if (ret == -EIO && hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { ++ schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); ++ return 0; ++ } ++ if (ret < 0) ++ hid_err(hdev, "unable to request touch data (%d)\n", ret); ++ ++ /* ++ * MT enable is usually not required after the first time, so don't ++ * consider it fatal. ++ */ ++ return 0; ++} ++ ++static void magicmouse_close(struct input_dev *dev) ++{ ++ struct hid_device *hdev = input_get_drvdata(dev); ++ ++ hid_hw_close(hdev); ++} ++ + static int magicmouse_firm_touch(struct magicmouse_sc *msc) + { + int touch = -1; +@@ -705,12 +798,19 @@ static int magicmouse_raw_event_mtp(struct hid_device *hdev, + static int magicmouse_raw_event_spi(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) + { ++ struct magicmouse_sc *msc = hid_get_drvdata(hdev); + const size_t hdr_sz = sizeof(struct tp_mouse_report); + +- if (size < hdr_sz) ++ if (!size) + return 0; + +- if (data[0] != TRACKPAD2_USB_REPORT_ID) ++ if (data[0] == SPI_RESET_REPORT_ID) { ++ hid_info(hdev, "Touch controller was reset, re-enabling touch mode\n"); ++ schedule_delayed_work(&msc->work, msecs_to_jiffies(10)); ++ return 1; ++ } ++ ++ if (data[0] != TRACKPAD2_USB_REPORT_ID || size < hdr_sz) + return 0; + + return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz); +@@ -897,10 +997,17 @@ static int magicmouse_setup_input_usb(struct input_dev *input, + */ + __clear_bit(EV_REP, input->evbit); + ++ /* ++ * This isn't strictly speaking needed for USB, but enabling MT on ++ * device open is probably more robust than only doing it once on probe ++ * even if USB devices are not known to suffer from the SPI reset issue. ++ */ ++ input->open = magicmouse_open; ++ input->close = magicmouse_close; + return 0; + } + +-static int magicmouse_setup_input_spi(struct input_dev *input, ++static int magicmouse_setup_input_mtp(struct input_dev *input, + struct hid_device *hdev) + { + int error; +@@ -973,6 +1080,25 @@ static int magicmouse_setup_input_spi(struct input_dev *input, + return 0; + } + ++static int magicmouse_setup_input_spi(struct input_dev *input, ++ struct hid_device *hdev) ++{ ++ int ret = magicmouse_setup_input_mtp(input, hdev); ++ if (ret) ++ return ret; ++ ++ /* ++ * Override the default input->open function to send the MT ++ * enable every time the device is opened. This ensures it works ++ * even if we missed a reset event due to the device being closed. ++ * input->close is overridden for symmetry. ++ */ ++ input->open = magicmouse_open; ++ input->close = magicmouse_close; ++ ++ return 0; ++} ++ + static int magicmouse_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +@@ -1011,58 +1137,6 @@ static int magicmouse_input_configured(struct hid_device *hdev, + return 0; + } + +-static int magicmouse_enable_multitouch(struct hid_device *hdev) +-{ +- const u8 *feature; +- const u8 feature_mt[] = { 0xD7, 0x01 }; +- const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 }; +- const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 }; +- const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 }; +- u8 *buf; +- int ret; +- int feature_size; +- +- if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 || +- hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) { +- if (hdev->vendor == BT_VENDOR_ID_APPLE) { +- feature_size = sizeof(feature_mt_trackpad2_bt); +- feature = feature_mt_trackpad2_bt; +- } else { /* USB_VENDOR_ID_APPLE */ +- feature_size = sizeof(feature_mt_trackpad2_usb); +- feature = feature_mt_trackpad2_usb; +- } +- } else if (hdev->vendor == SPI_VENDOR_ID_APPLE) { +- feature_size = sizeof(feature_mt_trackpad2_usb); +- feature = feature_mt_trackpad2_usb; +- } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { +- feature_size = sizeof(feature_mt_mouse2); +- feature = feature_mt_mouse2; +- } else { +- feature_size = sizeof(feature_mt); +- feature = feature_mt; +- } +- +- buf = kmemdup(feature, feature_size, GFP_KERNEL); +- if (!buf) +- return -ENOMEM; +- +- ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size, +- HID_FEATURE_REPORT, HID_REQ_SET_REPORT); +- kfree(buf); +- return ret; +-} +- +-static void magicmouse_enable_mt_work(struct work_struct *work) +-{ +- struct magicmouse_sc *msc = +- container_of(work, struct magicmouse_sc, work.work); +- int ret; +- +- ret = magicmouse_enable_multitouch(msc->hdev); +- if (ret < 0) +- hid_err(msc->hdev, "unable to request touch data (%d)\n", ret); +-} +- + static int magicmouse_fetch_battery(struct hid_device *hdev) + { + #ifdef CONFIG_HID_BATTERY_STRENGTH +@@ -1123,7 +1197,7 @@ static int magicmouse_probe(struct hid_device *hdev, + // conflicts with the report ID. + if (id->bus == BUS_HOST) { + msc->input_ops.raw_event = magicmouse_raw_event_mtp; +- msc->input_ops.setup_input = magicmouse_setup_input_spi; ++ msc->input_ops.setup_input = magicmouse_setup_input_mtp; + } else if (id->bus == BUS_SPI) { + msc->input_ops.raw_event = magicmouse_raw_event_spi; + msc->input_ops.setup_input = magicmouse_setup_input_spi; +@@ -1206,21 +1280,10 @@ static int magicmouse_probe(struct hid_device *hdev, + if (id->bus == BUS_HOST) + return 0; + +- /* +- * Some devices repond with 'invalid report id' when feature +- * report switching it into multitouch mode is sent to it. +- * +- * This results in -EIO from the _raw low-level transport callback, +- * but there seems to be no other way of switching the mode. +- * Thus the super-ugly hacky success check below. +- */ +- ret = magicmouse_enable_multitouch(hdev); +- if (ret != -EIO && ret < 0) { +- hid_err(hdev, "unable to request touch data (%d)\n", ret); +- goto err_stop_hw; +- } +- if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { +- schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); ++ /* SPI devices need to watch for reset events to re-send the MT enable */ ++ if (id->bus == BUS_SPI) { ++ report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_RESET_REPORT_ID, 0); ++ report->size = 2; + } + + return 0; +-- +2.43.0 + + +From ab1dafddf9471bc7a1f0aebd1f36006cd8e15a0f Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Sun, 3 Dec 2023 23:10:21 +0900 +Subject: [PATCH 23/26] HID: transport: spi: Implement GET FEATURE + +This is used for fetching trackpad dimensions. + +Signed-off-by: Hector Martin +--- + drivers/hid/spi-hid/spi-hid-apple-core.c | 51 ++++++++++++++++++++++-- + 1 file changed, 48 insertions(+), 3 deletions(-) + +diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c +index d48cb559a..f4a1042f0 100644 +--- a/drivers/hid/spi-hid/spi-hid-apple-core.c ++++ b/drivers/hid/spi-hid/spi-hid-apple-core.c +@@ -65,6 +65,8 @@ struct spihid_interface { + u32 max_input_report_len; + u32 max_output_report_len; + u8 name[32]; ++ u8 reply_buf[SPIHID_DESC_MAX]; ++ u32 reply_len; + bool ready; + }; + +@@ -327,6 +329,7 @@ static int apple_ll_raw_request(struct hid_device *hdev, + { + struct spihid_interface *idev = hdev->driver_data; + struct spihid_apple *spihid = spihid_get_data(idev); ++ int ret; + + dev_dbg(&spihid->spidev->dev, + "apple_ll_raw_request: device:%u reportnum:%hhu rtype:%hhu", +@@ -334,7 +337,25 @@ static int apple_ll_raw_request(struct hid_device *hdev, + + switch (reqtype) { + case HID_REQ_GET_REPORT: +- return -EINVAL; // spihid_get_raw_report(); ++ if (rtype != HID_FEATURE_REPORT) ++ return -EINVAL; ++ ++ idev->reply_len = 0; ++ ret = spihid_apple_request(spihid, idev->id, 0x32, reportnum, 0x00, len, NULL, 0); ++ if (ret < 0) ++ return ret; ++ ++ ret = wait_event_interruptible_timeout(spihid->wait, idev->reply_len, ++ SPIHID_DEF_WAIT); ++ if (ret == 0) ++ ret = -ETIMEDOUT; ++ if (ret < 0) { ++ dev_err(&spihid->spidev->dev, "waiting for get report failed: %d", ret); ++ return ret; ++ } ++ memcpy(buf, idev->reply_buf, max_t(size_t, len, idev->reply_len)); ++ return idev->reply_len; ++ + case HID_REQ_SET_REPORT: + if (buf[0] != reportnum) + return -EINVAL; +@@ -606,7 +627,27 @@ static bool spihid_process_iface_hid_report_desc(struct spihid_apple *spihid, + return true; + } + +-static bool spihid_process_response(struct spihid_apple *spihid, ++static bool spihid_process_iface_get_report(struct spihid_apple *spihid, ++ u32 device, u8 report, ++ u8 *payload, size_t len) ++{ ++ struct spihid_interface *iface = spihid_get_iface(spihid, device); ++ ++ if (!iface) ++ return false; ++ ++ if (len > sizeof(iface->reply_buf) || len < 1) ++ return false; ++ ++ memcpy(iface->reply_buf, payload, len); ++ iface->reply_len = len; ++ ++ wake_up_interruptible(&spihid->wait); ++ ++ return true; ++} ++ ++static bool spihid_process_response(struct spihid_apple *spihid, u32 device, + struct spihid_msg_hdr *hdr, u8 *payload, + size_t len) + { +@@ -626,6 +667,10 @@ static bool spihid_process_response(struct spihid_apple *spihid, + } + } + ++ if (hdr->unknown0 == 0x32) { ++ return spihid_process_iface_get_report(spihid, device, hdr->unknown1, payload, len); ++ } ++ + return false; + } + +@@ -656,7 +701,7 @@ static void spihid_process_message(struct spihid_apple *spihid, u8 *data, + payload, payload_len); + break; + case SPIHID_WRITE_PACKET: +- handled = spihid_process_response(spihid, hdr, payload, ++ handled = spihid_process_response(spihid, device, hdr, payload, + payload_len); + break; + default: +-- +2.43.0 + + +From 35a00f05d9f795ca9558d3e402e7bf18c155a919 Mon Sep 17 00:00:00 2001 +From: Hector Martin +Date: Sun, 3 Dec 2023 21:08:17 +0900 +Subject: [PATCH 24/26] HID: magicmouse: Query device dimensions via HID report + +For SPI/MTP trackpads, query the dimensions via HID report instead of +hardcoding values. + +TODO: Does this work for the USB/BT devices? Maybe we can get rid of the +hardcoded sizes everywhere? + +Signed-off-by: Hector Martin +--- + drivers/hid/hid-magicmouse.c | 102 +++++++++++++++++++++++++++-------- + 1 file changed, 81 insertions(+), 21 deletions(-) + +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index 87f5f371c..d5ab547b1 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -62,6 +62,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define SPI_REPORT_ID 0x02 + #define SPI_RESET_REPORT_ID 0x60 + #define MTP_REPORT_ID 0x75 ++#define SENSOR_DIMENSIONS_REPORT_ID 0xd9 + #define USB_BATTERY_TIMEOUT_MS 60000 + + #define MAX_CONTACTS 16 +@@ -116,6 +117,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define TRACKPAD2_RES_Y \ + ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) + ++/* These are fallback values, since the real values will be queried from the device. */ + #define J314_TP_DIMENSION_X (float)13000 + #define J314_TP_MIN_X -5900 + #define J314_TP_MAX_X 6500 +@@ -139,6 +141,7 @@ struct magicmouse_input_ops { + * struct magicmouse_sc - Tracks Magic Mouse-specific data. + * @input: Input device through which we report events. + * @quirks: Currently unused. ++ * @query_dimensions: Whether to query and update dimensions on first open + * @ntouches: Number of touches in most recent touch report. + * @scroll_accel: Number of consecutive scroll motions. + * @scroll_jiffies: Time of last scroll motion. +@@ -153,6 +156,7 @@ struct magicmouse_input_ops { + struct magicmouse_sc { + struct input_dev *input; + unsigned long quirks; ++ bool query_dimensions; + + int ntouches; + int scroll_accel; +@@ -176,6 +180,11 @@ struct magicmouse_sc { + struct magicmouse_input_ops input_ops; + }; + ++static inline int le16_to_int(__le16 x) ++{ ++ return (signed short)le16_to_cpu(x); ++} ++ + static int magicmouse_enable_multitouch(struct hid_device *hdev) + { + const u8 *feature; +@@ -245,19 +254,71 @@ static int magicmouse_open(struct input_dev *dev) + * This results in -EIO from the _raw low-level transport callback, + * but there seems to be no other way of switching the mode. + * Thus the super-ugly hacky success check below. ++ * ++ * MTP devices do not need this. + */ +- ret = magicmouse_enable_multitouch(hdev); +- if (ret == -EIO && hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { +- schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); +- return 0; ++ if (hdev->bus != BUS_HOST) { ++ ret = magicmouse_enable_multitouch(hdev); ++ if (ret == -EIO && hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { ++ schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); ++ return 0; ++ } ++ if (ret < 0) ++ hid_err(hdev, "unable to request touch data (%d)\n", ret); + } +- if (ret < 0) +- hid_err(hdev, "unable to request touch data (%d)\n", ret); +- + /* + * MT enable is usually not required after the first time, so don't + * consider it fatal. + */ ++ ++ /* ++ * For Apple Silicon trackpads, we want to query the dimensions on ++ * device open. This is because doing so requires the firmware, but ++ * we don't want to force a firmware load until the device is opened ++ * for the first time. So do that here and update the input properties ++ * just in time before userspace queries them. ++ */ ++ if (msc->query_dimensions) { ++ struct input_dev *input = msc->input; ++ u8 buf[32]; ++ struct { ++ __le32 width; ++ __le32 height; ++ __le16 min_x; ++ __le16 min_y; ++ __le16 max_x; ++ __le16 max_y; ++ } dim; ++ uint32_t x_span, y_span; ++ ++ ret = hid_hw_raw_request(hdev, SENSOR_DIMENSIONS_REPORT_ID, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); ++ if (ret < (int)(1 + sizeof(dim))) { ++ hid_err(hdev, "unable to request dimensions (%d)\n", ret); ++ return ret; ++ } ++ ++ memcpy(&dim, buf + 1, sizeof(dim)); ++ ++ /* finger position */ ++ input_set_abs_params(input, ABS_MT_POSITION_X, ++ le16_to_int(dim.min_x), le16_to_int(dim.max_x), 0, 0); ++ /* Y axis is inverted */ ++ input_set_abs_params(input, ABS_MT_POSITION_Y, ++ -le16_to_int(dim.max_y), -le16_to_int(dim.min_y), 0, 0); ++ x_span = le16_to_int(dim.max_x) - le16_to_int(dim.min_x); ++ y_span = le16_to_int(dim.max_y) - le16_to_int(dim.min_y); ++ ++ /* X/Y resolution */ ++ input_abs_set_res(input, ABS_MT_POSITION_X, 100 * x_span / le32_to_cpu(dim.width) ); ++ input_abs_set_res(input, ABS_MT_POSITION_Y, 100 * y_span / le32_to_cpu(dim.height) ); ++ ++ /* copy info, as input_mt_init_slots() does */ ++ dev->absinfo[ABS_X] = dev->absinfo[ABS_MT_POSITION_X]; ++ dev->absinfo[ABS_Y] = dev->absinfo[ABS_MT_POSITION_Y]; ++ ++ msc->query_dimensions = false; ++ } ++ + return 0; + } + +@@ -707,11 +768,6 @@ struct tp_mouse_report { + u8 padding[4]; + }; + +-static inline int le16_to_int(__le16 x) +-{ +- return (signed short)le16_to_cpu(x); +-} +- + static void report_finger_data(struct input_dev *input, int slot, + const struct input_mt_pos *pos, + const struct tp_finger *f) +@@ -1012,6 +1068,7 @@ static int magicmouse_setup_input_mtp(struct input_dev *input, + { + int error; + int mt_flags = 0; ++ struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + __clear_bit(BTN_0, input->keybit); +@@ -1077,6 +1134,18 @@ static int magicmouse_setup_input_mtp(struct input_dev *input, + if (error) + return error; + ++ /* ++ * Override the default input->open function to send the MT ++ * enable every time the device is opened. This ensures it works ++ * even if we missed a reset event due to the device being closed. ++ * input->close is overridden for symmetry. ++ * ++ * This also takes care of the dimensions query. ++ */ ++ input->open = magicmouse_open; ++ input->close = magicmouse_close; ++ msc->query_dimensions = true; ++ + return 0; + } + +@@ -1087,15 +1156,6 @@ static int magicmouse_setup_input_spi(struct input_dev *input, + if (ret) + return ret; + +- /* +- * Override the default input->open function to send the MT +- * enable every time the device is opened. This ensures it works +- * even if we missed a reset event due to the device being closed. +- * input->close is overridden for symmetry. +- */ +- input->open = magicmouse_open; +- input->close = magicmouse_close; +- + return 0; + } + +-- +2.43.0 + + +From d415b0be9469d3405f265d5c0c14f19c621298c3 Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Thu, 3 Oct 2024 19:30:23 +0200 +Subject: [PATCH 25/26] HID: transport: spi: apple: Increase receive buffer + size + +The SPI receive buffer is passed directly to hid_input_report() if it +contains a complete report. It is then passed to hid_report_raw_event() +which computes the expected report size and memsets the "missing +trailing data up to HID_MAX_BUFFER_SIZE (16K) or +hid_ll_driver.max_buffer_size (if set) to zero. + +Signed-off-by: Janne Grunau +--- + drivers/hid/spi-hid/spi-hid-apple-core.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c +index f4a1042f0..aff822d61 100644 +--- a/drivers/hid/spi-hid/spi-hid-apple-core.c ++++ b/drivers/hid/spi-hid/spi-hid-apple-core.c +@@ -395,6 +395,7 @@ static struct hid_ll_driver apple_hid_ll = { + .parse = &apple_ll_parse, + .raw_request = &apple_ll_raw_request, + .output_report = &apple_ll_output_report, ++ .max_buffer_size = SPIHID_MAX_INPUT_REPORT_SIZE, + }; + + static struct spihid_interface *spihid_get_iface(struct spihid_apple *spihid, +@@ -971,9 +972,15 @@ int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops + // init spi + spi_set_drvdata(spi, spihid); + +- /* allocate SPI buffers */ ++ /* ++ * allocate SPI buffers ++ * Overallocate the receice buffer since it passed directly into ++ * hid_input_report / hid_report_raw_event. The later expects the buffer ++ * to be HID_MAX_BUFFER_SIZE (16k) or hid_ll_driver.max_buffer_size if ++ * set. ++ */ + spihid->rx_buf = devm_kmalloc( +- &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL); ++ &spi->dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL); + spihid->tx_buf = devm_kmalloc( + &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL); + spihid->status_buf = devm_kmalloc( +-- +2.43.0 + + +From 39732f183d1a7dad48fc1c55615e790c21a41b86 Mon Sep 17 00:00:00 2001 +From: Janne Grunau +Date: Tue, 21 Jan 2025 21:04:33 +0100 +Subject: [PATCH 26/26] HID: transport: spi: apple: Use distinctive names input + devices + +Kwin uses the tripple Vendor ID, Product ID, Name to identify input +devices. If the same name ("Apple Internal Keyboard / Trackpad") is used +for both keyboard and trackpad actions like disabling the touchpad have +confusing effects. On the next login the keyboard will be disabled as +well since it shares all identifiers. + +Signed-off-by: Janne Grunau +--- + drivers/hid/spi-hid/spi-hid-apple-core.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c +index aff822d61..1f8fa64d6 100644 +--- a/drivers/hid/spi-hid/spi-hid-apple-core.c ++++ b/drivers/hid/spi-hid/spi-hid-apple-core.c +@@ -903,6 +903,7 @@ static int spihid_register_hid_device(struct spihid_apple *spihid, + struct spihid_interface *iface, u8 device) + { + int ret; ++ char *suffix; + struct hid_device *hid; + + iface->id = device; +@@ -911,7 +912,16 @@ static int spihid_register_hid_device(struct spihid_apple *spihid, + if (IS_ERR(hid)) + return PTR_ERR(hid); + +- strscpy(hid->name, spihid->product, sizeof(hid->name)); ++ /* ++ * Use 'Apple SPI Keyboard' and 'Apple SPI Trackpad' as input device ++ * names. The device names need to be distinct since at least Kwin uses ++ * the tripple Vendor ID, Product ID, Name to identify devices. ++ */ ++ snprintf(hid->name, sizeof(hid->name), "Apple SPI %s", iface->name); ++ // strip ' / Boot' suffix from the name ++ suffix = strstr(hid->name, " / Boot"); ++ if (suffix) ++ suffix[0] = '\0'; + snprintf(hid->phys, sizeof(hid->phys), "%s (%hhx)", + dev_name(&spihid->spidev->dev), device); + strscpy(hid->uniq, spihid->serial, sizeof(hid->uniq)); +-- +2.43.0 + diff --git a/4002-HID-quirks-remove-T2-devices-from-hid_mouse_ignore_l.patch b/4002-HID-quirks-remove-T2-devices-from-hid_mouse_ignore_l.patch new file mode 100644 index 0000000..596fcc1 --- /dev/null +++ b/4002-HID-quirks-remove-T2-devices-from-hid_mouse_ignore_l.patch @@ -0,0 +1,35 @@ +From 165066841c211201b3f228f03e18e28423e98e94 Mon Sep 17 00:00:00 2001 +From: Aditya Garg +Date: Mon, 10 Mar 2025 22:53:28 +0530 +Subject: [PATCH 1/3] HID: quirks: remove T2 devices from hid_mouse_ignore_list + +In order to use hid-magicmouse for T2 trackpads, we need to +remove them from hid_mouse_ignore_list + +Signed-off-by: Aditya Garg +--- + drivers/hid/hid-quirks.c | 8 -------- + 1 file changed, 8 deletions(-) + +diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c +index e0bbf0c63..2eb15a7ae 100644 +--- a/drivers/hid/hid-quirks.c ++++ b/drivers/hid/hid-quirks.c +@@ -956,14 +956,6 @@ static const struct hid_device_id hid_mouse_ignore_list[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, + { } +-- +2.43.0 + diff --git a/4003-HID-apple-ignore-the-trackpad-on-T2-Macs.patch b/4003-HID-apple-ignore-the-trackpad-on-T2-Macs.patch new file mode 100644 index 0000000..1a1af09 --- /dev/null +++ b/4003-HID-apple-ignore-the-trackpad-on-T2-Macs.patch @@ -0,0 +1,65 @@ +From cc2712238a3179b046f7cd36a24a9a606b99aa81 Mon Sep 17 00:00:00 2001 +From: Aditya Garg +Date: Mon, 10 Mar 2025 23:03:29 +0530 +Subject: [PATCH 2/3] HID: apple: ignore the trackpad on T2 Macs + +In order to manage the trackpad on T2 Macs by hid-magicmouse driver +we need to ensure that it is not bound by the hid-apple driver. Use +the existing APPLE_IGNORE_MOUSE quirk for the same. + +Signed-off-by: Aditya Garg +--- + drivers/hid/hid-apple.c | 23 +++++++++++++++-------- + 1 file changed, 15 insertions(+), 8 deletions(-) + +diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c +index a33260293..fdfbfe6ab 100644 +--- a/drivers/hid/hid-apple.c ++++ b/drivers/hid/hid-apple.c +@@ -963,6 +963,9 @@ static int apple_probe(struct hid_device *hdev, + hdev->type != HID_TYPE_SPI_KEYBOARD) + return -ENODEV; + ++ if (quirks & APPLE_IGNORE_MOUSE && hdev->type == HID_TYPE_USBMOUSE) ++ return -ENODEV; ++ + asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL); + if (asc == NULL) { + hid_err(hdev, "can't alloc apple descriptor\n"); +@@ -1180,21 +1183,25 @@ static const struct hid_device_id apple_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS), + .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K), +- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK }, ++ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK | ++ APPLE_IGNORE_MOUSE }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132), +- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK }, ++ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK | ++ APPLE_IGNORE_MOUSE }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680), +- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK }, ++ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK | ++ APPLE_IGNORE_MOUSE }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213), +- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK }, ++ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK | ++ APPLE_IGNORE_MOUSE }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K), +- .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, ++ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_IGNORE_MOUSE }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223), +- .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, ++ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_IGNORE_MOUSE }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K), +- .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, ++ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_IGNORE_MOUSE }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F), +- .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, ++ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_IGNORE_MOUSE }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI), + .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO), +-- +2.43.0 + diff --git a/4004-HID-magicmouse-Add-support-for-trackpads-found-on-T2.patch b/4004-HID-magicmouse-Add-support-for-trackpads-found-on-T2.patch new file mode 100644 index 0000000..f8edede --- /dev/null +++ b/4004-HID-magicmouse-Add-support-for-trackpads-found-on-T2.patch @@ -0,0 +1,516 @@ +From c2e1c104044b02b21e8db083fb4401fd30e9f5a8 Mon Sep 17 00:00:00 2001 +From: Aditya Garg +Date: Tue, 11 Mar 2025 18:44:06 +0530 +Subject: [PATCH 3/3] HID: magicmouse: Add support for trackpads found on T2 + Macs + +This patch adds support for trackpads found on Macs with the T2 +Security Chip. The touch report format differs from other trackpads. +It is the same format as type 4 in bcm5974.c + +Signed-off-by: Aditya Garg +--- + drivers/hid/hid-magicmouse.c | 408 ++++++++++++++++++++++++++++++----- + 1 file changed, 360 insertions(+), 48 deletions(-) + +diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c +index d5ab547b1..70934a221 100644 +--- a/drivers/hid/hid-magicmouse.c ++++ b/drivers/hid/hid-magicmouse.c +@@ -117,6 +117,94 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define TRACKPAD2_RES_Y \ + ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) + ++#define J140K_TP_DIMENSION_X (float)12100 ++#define J140K_TP_MIN_X -5318 ++#define J140K_TP_MAX_X 5787 ++#define J140K_TP_RES_X \ ++ ((J140K_TP_MAX_X - J140K_TP_MIN_X) / (J140K_TP_DIMENSION_X / 100)) ++#define J140K_TP_DIMENSION_Y (float)8200 ++#define J140K_TP_MIN_Y -157 ++#define J140K_TP_MAX_Y 7102 ++#define J140K_TP_RES_Y \ ++ ((J140K_TP_MAX_Y - J140K_TP_MIN_Y) / (J140K_TP_DIMENSION_Y / 100)) ++ ++#define J132_TP_DIMENSION_X (float)13500 ++#define J132_TP_MIN_X -6243 ++#define J132_TP_MAX_X 6749 ++#define J132_TP_RES_X \ ++ ((J132_TP_MAX_X - J132_TP_MIN_X) / (J132_TP_DIMENSION_X / 100)) ++#define J132_TP_DIMENSION_Y (float)8400 ++#define J132_TP_MIN_Y -170 ++#define J132_TP_MAX_Y 7685 ++#define J132_TP_RES_Y \ ++ ((J132_TP_MAX_Y - J132_TP_MIN_Y) / (J132_TP_DIMENSION_Y / 100)) ++ ++#define J680_TP_DIMENSION_X (float)16000 ++#define J680_TP_MIN_X -7456 ++#define J680_TP_MAX_X 7976 ++#define J680_TP_RES_X \ ++ ((J680_TP_MAX_X - J680_TP_MIN_X) / (J680_TP_DIMENSION_X / 100)) ++#define J680_TP_DIMENSION_Y (float)10000 ++#define J680_TP_MIN_Y -163 ++#define J680_TP_MAX_Y 9283 ++#define J680_TP_RES_Y \ ++ ((J680_TP_MAX_Y - J680_TP_MIN_Y) / (J680_TP_DIMENSION_Y / 100)) ++ ++#define J213_TP_DIMENSION_X (float)13500 ++#define J213_TP_MIN_X -6243 ++#define J213_TP_MAX_X 6749 ++#define J213_TP_RES_X \ ++ ((J213_TP_MAX_X - J213_TP_MIN_X) / (J213_TP_DIMENSION_X / 100)) ++#define J213_TP_DIMENSION_Y (float)8400 ++#define J213_TP_MIN_Y -170 ++#define J213_TP_MAX_Y 7685 ++#define J213_TP_RES_Y \ ++ ((J213_TP_MAX_Y - J213_TP_MIN_Y) / (J213_TP_DIMENSION_Y / 100)) ++ ++#define J214K_TP_DIMENSION_X (float)13200 ++#define J214K_TP_MIN_X -6046 ++#define J214K_TP_MAX_X 6536 ++#define J214K_TP_RES_X \ ++ ((J214K_TP_MAX_X - J214K_TP_MIN_X) / (J214K_TP_DIMENSION_X / 100)) ++#define J214K_TP_DIMENSION_Y (float)8200 ++#define J214K_TP_MIN_Y -164 ++#define J214K_TP_MAX_Y 7439 ++#define J214K_TP_RES_Y \ ++ ((J214K_TP_MAX_Y - J214K_TP_MIN_Y) / (J214K_TP_DIMENSION_Y / 100)) ++ ++#define J223_TP_DIMENSION_X (float)13200 ++#define J223_TP_MIN_X -6046 ++#define J223_TP_MAX_X 6536 ++#define J223_TP_RES_X \ ++ ((J223_TP_MAX_X - J223_TP_MIN_X) / (J223_TP_DIMENSION_X / 100)) ++#define J223_TP_DIMENSION_Y (float)8200 ++#define J223_TP_MIN_Y -164 ++#define J223_TP_MAX_Y 7439 ++#define J223_TP_RES_Y \ ++ ((J223_TP_MAX_Y - J223_TP_MIN_Y) / (J223_TP_DIMENSION_Y / 100)) ++ ++#define J230K_TP_DIMENSION_X (float)12100 ++#define J230K_TP_MIN_X -5318 ++#define J230K_TP_MAX_X 5787 ++#define J230K_TP_RES_X \ ++ ((J230K_TP_MAX_X - J230K_TP_MIN_X) / (J230K_TP_DIMENSION_X / 100)) ++#define J230K_TP_DIMENSION_Y (float)8200 ++#define J230K_TP_MIN_Y -157 ++#define J230K_TP_MAX_Y 7102 ++#define J230K_TP_RES_Y \ ++ ((J230K_TP_MAX_Y - J230K_TP_MIN_Y) / (J230K_TP_DIMENSION_Y / 100)) ++ ++#define J152F_TP_DIMENSION_X (float)16000 ++#define J152F_TP_MIN_X -7456 ++#define J152F_TP_MAX_X 7976 ++#define J152F_TP_RES_X \ ++ ((J152F_TP_MAX_X - J152F_TP_MIN_X) / (J152F_TP_DIMENSION_X / 100)) ++#define J152F_TP_DIMENSION_Y (float)10000 ++#define J152F_TP_MIN_Y -163 ++#define J152F_TP_MAX_Y 9283 ++#define J152F_TP_RES_Y \ ++ ((J152F_TP_MAX_Y - J152F_TP_MIN_Y) / (J152F_TP_DIMENSION_Y / 100)) ++ + /* These are fallback values, since the real values will be queried from the device. */ + #define J314_TP_DIMENSION_X (float)13000 + #define J314_TP_MIN_X -5900 +@@ -129,7 +217,11 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie + #define J314_TP_RES_Y \ + ((J314_TP_MAX_Y - J314_TP_MIN_Y) / (J314_TP_DIMENSION_Y / 100)) + +-#define J314_TP_MAX_FINGER_ORIENTATION 16384 ++#define T2_TOUCHPAD_ENTRY(model) \ ++ { USB_DEVICE_ID_APPLE_WELLSPRINGT2_##model, model##_TP_MIN_X, model##_TP_MIN_Y, \ ++model##_TP_MAX_X, model##_TP_MAX_Y, model##_TP_RES_X, model##_TP_RES_Y } ++ ++#define INTERNAL_TP_MAX_FINGER_ORIENTATION 16384 + + struct magicmouse_input_ops { + int (*raw_event)(struct hid_device *hdev, +@@ -196,24 +288,45 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) + int ret; + int feature_size; + +- if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 || +- hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) { +- if (hdev->vendor == BT_VENDOR_ID_APPLE) { +- feature_size = sizeof(feature_mt_trackpad2_bt); +- feature = feature_mt_trackpad2_bt; +- } else { /* USB_VENDOR_ID_APPLE */ ++ switch (hdev->bus) { ++ case BUS_SPI: ++ case BUS_HOST: ++ feature_size = sizeof(feature_mt_trackpad2_usb); ++ feature = feature_mt_trackpad2_usb; ++ break; ++ default: ++ switch (hdev->product) { ++ case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2: ++ case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC: ++ switch (hdev->vendor) { ++ case BT_VENDOR_ID_APPLE: ++ feature_size = sizeof(feature_mt_trackpad2_bt); ++ feature = feature_mt_trackpad2_bt; ++ break; ++ default: /* USB_VENDOR_ID_APPLE */ ++ feature_size = sizeof(feature_mt_trackpad2_usb); ++ feature = feature_mt_trackpad2_usb; ++ } ++ break; ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F: + feature_size = sizeof(feature_mt_trackpad2_usb); + feature = feature_mt_trackpad2_usb; ++ break; ++ case USB_DEVICE_ID_APPLE_MAGICMOUSE2: ++ feature_size = sizeof(feature_mt_mouse2); ++ feature = feature_mt_mouse2; ++ break; ++ default: ++ feature_size = sizeof(feature_mt); ++ feature = feature_mt; + } +- } else if (hdev->vendor == SPI_VENDOR_ID_APPLE) { +- feature_size = sizeof(feature_mt_trackpad2_usb); +- feature = feature_mt_trackpad2_usb; +- } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { +- feature_size = sizeof(feature_mt_mouse2); +- feature = feature_mt_mouse2; +- } else { +- feature_size = sizeof(feature_mt); +- feature = feature_mt; + } + + buf = kmemdup(feature, feature_size, GFP_KERNEL); +@@ -784,7 +897,7 @@ static void report_finger_data(struct input_dev *input, int slot, + input_report_abs(input, ABS_MT_WIDTH_MINOR, + le16_to_int(f->tool_minor) << 1); + input_report_abs(input, ABS_MT_ORIENTATION, +- J314_TP_MAX_FINGER_ORIENTATION - le16_to_int(f->orientation)); ++ INTERNAL_TP_MAX_FINGER_ORIENTATION - le16_to_int(f->orientation)); + input_report_abs(input, ABS_MT_PRESSURE, le16_to_int(f->pressure)); + input_report_abs(input, ABS_MT_POSITION_X, pos->x); + input_report_abs(input, ABS_MT_POSITION_Y, pos->y); +@@ -872,6 +985,20 @@ static int magicmouse_raw_event_spi(struct hid_device *hdev, + return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz); + } + ++static int magicmouse_raw_event_t2(struct hid_device *hdev, ++ struct hid_report *report, u8 *data, int size) ++{ ++ const size_t hdr_sz = sizeof(struct tp_mouse_report); ++ ++ if (!size) ++ return 0; ++ ++ if (data[0] != TRACKPAD2_USB_REPORT_ID || size < hdr_sz) ++ return 0; ++ ++ return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz); ++} ++ + static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) + { +@@ -1063,6 +1190,124 @@ static int magicmouse_setup_input_usb(struct input_dev *input, + return 0; + } + ++struct magicmouse_t2_properties { ++ u32 id; ++ int min_x; ++ int min_y; ++ int max_x; ++ int max_y; ++ int res_x; ++ int res_y; ++}; ++ ++static const struct magicmouse_t2_properties magicmouse_t2_configs[] = { ++ T2_TOUCHPAD_ENTRY(J140K), ++ T2_TOUCHPAD_ENTRY(J132), ++ T2_TOUCHPAD_ENTRY(J680), ++ T2_TOUCHPAD_ENTRY(J213), ++ T2_TOUCHPAD_ENTRY(J214K), ++ T2_TOUCHPAD_ENTRY(J223), ++ T2_TOUCHPAD_ENTRY(J230K), ++ T2_TOUCHPAD_ENTRY(J152F), ++}; ++ ++static int magicmouse_setup_input_t2(struct input_dev *input, ++ struct hid_device *hdev) ++{ ++ int min_x, min_y, max_x, max_y, res_x, res_y, error; ++ int mt_flags = 0; ++ struct magicmouse_sc *msc = hid_get_drvdata(hdev); ++ ++ for (size_t i = 0; i < ARRAY_SIZE(magicmouse_t2_configs); i++) { ++ if (magicmouse_t2_configs[i].id == hdev->product) { ++ min_x = magicmouse_t2_configs[i].min_x; ++ min_y = magicmouse_t2_configs[i].min_y; ++ max_x = magicmouse_t2_configs[i].max_x; ++ max_y = magicmouse_t2_configs[i].max_y; ++ res_x = magicmouse_t2_configs[i].res_x; ++ res_y = magicmouse_t2_configs[i].res_y; ++ } ++ } ++ ++ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); ++ __clear_bit(BTN_0, input->keybit); ++ __clear_bit(BTN_RIGHT, input->keybit); ++ __clear_bit(BTN_MIDDLE, input->keybit); ++ __clear_bit(EV_REL, input->evbit); ++ __clear_bit(REL_X, input->relbit); ++ __clear_bit(REL_Y, input->relbit); ++ ++ mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK; ++ ++ /* finger touch area */ ++ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 5000, 0, 0); ++ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 5000, 0, 0); ++ ++ /* finger approach area */ ++ input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 5000, 0, 0); ++ input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 5000, 0, 0); ++ ++ /* Note: Touch Y position from the device is inverted relative ++ * to how pointer motion is reported (and relative to how USB ++ * HID recommends the coordinates work). This driver keeps ++ * the origin at the same position, and just uses the additive ++ * inverse of the reported Y. ++ */ ++ ++ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 6000, 0, 0); ++ ++ /* ++ * This makes libinput recognize this as a PressurePad and ++ * stop trying to use pressure for touch size. Pressure unit ++ * seems to be ~grams on these touchpads. ++ */ ++ input_abs_set_res(input, ABS_MT_PRESSURE, 1); ++ ++ /* finger orientation */ ++ input_set_abs_params(input, ABS_MT_ORIENTATION, -INTERNAL_TP_MAX_FINGER_ORIENTATION, ++ INTERNAL_TP_MAX_FINGER_ORIENTATION, 0, 0); ++ ++ /* finger position */ ++ input_set_abs_params(input, ABS_MT_POSITION_X, min_x, max_x, ++ 0, 0); ++ /* Y axis is inverted */ ++ input_set_abs_params(input, ABS_MT_POSITION_Y, -max_y, -min_y, ++ 0, 0); ++ ++ /* X/Y resolution */ ++ input_abs_set_res(input, ABS_MT_POSITION_X, res_x); ++ input_abs_set_res(input, ABS_MT_POSITION_Y, res_y); ++ ++ input_set_events_per_packet(input, 60); ++ ++ /* touchpad button */ ++ input_set_capability(input, EV_KEY, BTN_MOUSE); ++ ++ /* ++ * hid-input may mark device as using autorepeat, but the trackpad does ++ * not actually want it. ++ */ ++ __clear_bit(EV_REP, input->evbit); ++ ++ error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags); ++ if (error) ++ return error; ++ ++ /* ++ * Override the default input->open function to send the MT ++ * enable every time the device is opened. This ensures it works ++ * even if we missed a reset event due to the device being closed. ++ * input->close is overridden for symmetry. ++ * ++ * This also takes care of the dimensions query. ++ */ ++ input->open = magicmouse_open; ++ input->close = magicmouse_close; ++ msc->query_dimensions = false; ++ ++ return 0; ++} ++ + static int magicmouse_setup_input_mtp(struct input_dev *input, + struct hid_device *hdev) + { +@@ -1105,8 +1350,8 @@ static int magicmouse_setup_input_mtp(struct input_dev *input, + input_abs_set_res(input, ABS_MT_PRESSURE, 1); + + /* finger orientation */ +- input_set_abs_params(input, ABS_MT_ORIENTATION, -J314_TP_MAX_FINGER_ORIENTATION, +- J314_TP_MAX_FINGER_ORIENTATION, 0, 0); ++ input_set_abs_params(input, ABS_MT_ORIENTATION, -INTERNAL_TP_MAX_FINGER_ORIENTATION, ++ INTERNAL_TP_MAX_FINGER_ORIENTATION, 0, 0); + + /* finger position */ + input_set_abs_params(input, ABS_MT_POSITION_X, J314_TP_MIN_X, J314_TP_MAX_X, +@@ -1244,8 +1489,22 @@ static int magicmouse_probe(struct hid_device *hdev, + int ret; + + if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE && +- hdev->type != HID_TYPE_SPI_MOUSE) +- return -ENODEV; ++ hdev->type != HID_TYPE_SPI_MOUSE) ++ return -ENODEV; ++ ++ switch (id->product) { ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F: ++ if (hdev->type != HID_TYPE_USBMOUSE) ++ return -ENODEV; ++ break; ++ } + + msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL); + if (msc == NULL) { +@@ -1255,16 +1514,32 @@ static int magicmouse_probe(struct hid_device *hdev, + + // internal trackpad use a data format use input ops to avoid + // conflicts with the report ID. +- if (id->bus == BUS_HOST) { ++ switch (id->bus) { ++ case BUS_HOST: + msc->input_ops.raw_event = magicmouse_raw_event_mtp; + msc->input_ops.setup_input = magicmouse_setup_input_mtp; +- } else if (id->bus == BUS_SPI) { ++ break; ++ case BUS_SPI: + msc->input_ops.raw_event = magicmouse_raw_event_spi; + msc->input_ops.setup_input = magicmouse_setup_input_spi; +- +- } else { +- msc->input_ops.raw_event = magicmouse_raw_event_usb; +- msc->input_ops.setup_input = magicmouse_setup_input_usb; ++ break; ++ default: ++ switch (id->product) { ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F: ++ msc->input_ops.raw_event = magicmouse_raw_event_t2; ++ msc->input_ops.setup_input = magicmouse_setup_input_t2; ++ break; ++ default: ++ msc->input_ops.raw_event = magicmouse_raw_event_usb; ++ msc->input_ops.setup_input = magicmouse_setup_input_usb; ++ } + } + + msc->scroll_accel = SCROLL_ACCEL_DEFAULT; +@@ -1304,29 +1579,50 @@ static int magicmouse_probe(struct hid_device *hdev, + goto err_stop_hw; + } + +- if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE) +- report = hid_register_report(hdev, HID_INPUT_REPORT, +- MOUSE_REPORT_ID, 0); +- else if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) +- report = hid_register_report(hdev, HID_INPUT_REPORT, +- MOUSE2_REPORT_ID, 0); +- else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 || +- id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) { +- if (id->vendor == BT_VENDOR_ID_APPLE) +- report = hid_register_report(hdev, HID_INPUT_REPORT, +- TRACKPAD2_BT_REPORT_ID, 0); +- else /* USB_VENDOR_ID_APPLE */ +- report = hid_register_report(hdev, HID_INPUT_REPORT, +- TRACKPAD2_USB_REPORT_ID, 0); +- } else if (id->bus == BUS_SPI) { ++ switch (id->bus) { ++ case BUS_SPI: + report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0); +- } else if (id->bus == BUS_HOST) { ++ break; ++ case BUS_HOST: + report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0); +- } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ +- report = hid_register_report(hdev, HID_INPUT_REPORT, +- TRACKPAD_REPORT_ID, 0); +- report = hid_register_report(hdev, HID_INPUT_REPORT, +- DOUBLE_REPORT_ID, 0); ++ break; ++ default: ++ switch (id->product) { ++ case USB_DEVICE_ID_APPLE_MAGICMOUSE: ++ report = hid_register_report(hdev, HID_INPUT_REPORT, MOUSE_REPORT_ID, 0); ++ break; ++ case USB_DEVICE_ID_APPLE_MAGICMOUSE2: ++ report = hid_register_report(hdev, HID_INPUT_REPORT, MOUSE2_REPORT_ID, 0); ++ break; ++ case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2: ++ case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC: ++ switch (id->vendor) { ++ case BT_VENDOR_ID_APPLE: ++ report = hid_register_report(hdev, HID_INPUT_REPORT, ++ TRACKPAD2_BT_REPORT_ID, 0); ++ break; ++ default: ++ report = hid_register_report(hdev, HID_INPUT_REPORT, ++ TRACKPAD2_USB_REPORT_ID, 0); ++ } ++ break; ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K: ++ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F: ++ report = hid_register_report(hdev, HID_INPUT_REPORT, ++ TRACKPAD2_USB_REPORT_ID, 0); ++ break; ++ default: /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ ++ report = hid_register_report(hdev, HID_INPUT_REPORT, ++ TRACKPAD_REPORT_ID, 0); ++ report = hid_register_report(hdev, HID_INPUT_REPORT, ++ DOUBLE_REPORT_ID, 0); ++ } + } + + if (!report) { +@@ -1414,6 +1710,22 @@ static const struct hid_device_id magic_mice[] = { + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, ++ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K), .driver_data = 0 }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, ++ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132), .driver_data = 0 }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, ++ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680), .driver_data = 0 }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, ++ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213), .driver_data = 0 }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, ++ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K), .driver_data = 0 }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, ++ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223), .driver_data = 0 }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, ++ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K), .driver_data = 0 }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, ++ USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F), .driver_data = 0 }, + { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = 0 }, + { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, +-- +2.43.0 + diff --git a/extra_config b/extra_config index 97f7e18..29447d6 100644 --- a/extra_config +++ b/extra_config @@ -8,6 +8,7 @@ CONFIG_BT_HCIUART=m CONFIG_HID_APPLETB_BL=m CONFIG_HID_APPLETB_KBD=m CONFIG_HID_APPLE=m +CONFIG_HID_MAGICMOUSE=m CONFIG_DRM_APPLETBDRM=m CONFIG_DRM_KUNIT_TEST=m CONFIG_HID_SENSOR_ALS=m diff --git a/version b/version index 8759f2a..82ec740 100644 --- a/version +++ b/version @@ -1 +1 @@ -KVER=6.12.13 +KVER=6.12.19