From 934d9c0b72d29a868d6baad3ad4e1c6b96a6eb68 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Dec 2021 20:40:04 +0100 Subject: [PATCH 01/42] 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 41a79e43c82b32..a6168cae090b68 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2366,6 +2366,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) case BUS_SDW: bus = "SOUNDWIRE"; 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 426ff78c1c033d..84d6447c3a6d6a 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -96,6 +96,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 @@ -200,6 +201,10 @@ #define USB_DEVICE_ID_APPLE_IRCONTROL5 0x8243 #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_ASETEK 0x2433 #define USB_DEVICE_ID_ASETEK_INVICTA 0xf300 diff --git a/include/linux/hid.h b/include/linux/hid.h index 47dc0bc89fa4a0..1dee79ec6dcfca 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -625,7 +625,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 { @@ -819,6 +821,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) From 42c2f66a54e12eb6d4c17719dccf2b8ab8aa8004 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 00:29:43 +0900 Subject: [PATCH 02/42] 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 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 84d6447c3a6d6a..64e0e66a76412e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -97,6 +97,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 @@ -205,6 +206,10 @@ #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_PRO14_2023 0x0352 +#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO16_2023 0x0353 +#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022 0x0354 #define USB_VENDOR_ID_ASETEK 0x2433 #define USB_DEVICE_ID_ASETEK_INVICTA 0xf300 From 63c29944d2e58b008d698d57eca1d4a4e426fabf Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:06:15 +0900 Subject: [PATCH 03/42] 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 a6168cae090b68..be4fc908533146 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2369,6 +2369,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; From eb0982f418b4c5cb75443c0384032739f6742850 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 10 Apr 2023 22:44:44 +0900 Subject: [PATCH 04/42] 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 be4fc908533146..8633a5ecdaec02 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -471,7 +471,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; From 1e49512e8a8e297a04a5708d44594737c665bdd4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 21:15:31 +0100 Subject: [PATCH 05/42] HID: apple: Bind Apple silicon SPI devices 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. The Macbook Pro (M1, 13-inch, 2020) uses the same function key mapping as other Macbook Pros with touchbar and dedicated ESC key. Apple silicon Macbooks use the same function key mapping as the 2021 and later Magic Keyboards. Signed-off-by: Janne Grunau --- drivers/hid/hid-apple.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index bf7dd0fbf2496f..59e11f4506a087 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -477,6 +477,18 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, asc->fn_on = !!value; if (real_fnmode) { + switch (hid->bus) { + case BUS_SPI: + switch (hid->product) { + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + table = macbookpro_dedicated_esc_fn_keys; + break; + default: + table = magic_keyboard_2021_and_2024_fn_keys; + break; + } + break; + default: switch (hid->product) { case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI: case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO: @@ -525,6 +537,7 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, else table = apple_fn_keys; } + } trans = apple_find_translation(table, code); @@ -944,6 +957,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"); @@ -1198,6 +1215,8 @@ static const struct hid_device_id apple_devices[] = { .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY), .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, + { 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_MAGIC_KEYBOARD_2021), .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_2021), From 4cdbfc2864a2079538d9239ca63b3f3628b65975 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:12:24 +0900 Subject: [PATCH 06/42] 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 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 59e11f4506a087..9d325e29d1066c 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -478,9 +478,11 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, if (real_fnmode) { switch (hid->bus) { + case BUS_HOST: case BUS_SPI: switch (hid->product) { - case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022: table = macbookpro_dedicated_esc_fn_keys; break; default: @@ -957,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; @@ -1229,6 +1231,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_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_MAGIC_KEYBOARD_2024), .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_2024), From 857bb25aa97b22de5cea54f039976c640151d0aa Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 7 Apr 2025 20:04:23 +0200 Subject: [PATCH 07/42] DO NOT MERGE: HID: apple: Add fnmode which ignores function keys This mode is added to ease adding new xkeyboard configs for Apple silicon Macbook keyboards. The existing ones have strange quirks [1] and as the keyboard sends a key code for the 'fn' there is desire to use it as additional modifier [2]. [1]: https://pagure.io/fedora-asahi/remix-bugs/issue/17 [2]: https://asahilinux.org/docs/project/help-wanted/ (Keyboard layout cleanup) Signed-off-by: Janne Grunau --- drivers/hid/hid-apple.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 9d325e29d1066c..d72e1121ebdd94 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -54,10 +54,16 @@ #define APPLE_MAGIC_REPORT_ID_POWER 3 #define APPLE_MAGIC_REPORT_ID_BRIGHTNESS 1 +// DO NOT UPSTREAM: +// temporary Fn key mode until xkeyboard-config has keyboard layouts with media +// key mappings. At that point auto mode can drop function key mappings and this +// mode can be dropped. +#define FKEYS_IGNORE 5 + static unsigned int fnmode = 3; module_param(fnmode, uint, 0644); MODULE_PARM_DESC(fnmode, "Mode of fn key on Apple keyboards (0 = disabled, " - "1 = fkeyslast, 2 = fkeysfirst, [3] = auto, 4 = fkeysdisabled)"); + "1 = fkeyslast, 2 = fkeysfirst, [3] = auto, 4 = fkeysdisabled, 5 = fkeysignore))"); static int iso_layout = -1; module_param(iso_layout, int, 0644); @@ -277,6 +283,16 @@ static const struct apple_key_translation apple_fn_keys[] = { { } }; +static const struct apple_key_translation apple_fn_keys_minimal[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { 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 }, @@ -437,6 +453,8 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, real_fnmode = 2; else real_fnmode = 1; + } else if (fnmode == FKEYS_IGNORE) { + real_fnmode = 2; } else { real_fnmode = fnmode; } @@ -486,7 +504,10 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, table = macbookpro_dedicated_esc_fn_keys; break; default: - table = magic_keyboard_2021_and_2024_fn_keys; + if (fnmode == FKEYS_IGNORE) + table = apple_fn_keys_minimal; + else + table = magic_keyboard_2021_and_2024_fn_keys; break; } break; @@ -723,6 +744,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_minimal); apple_setup_key_translation(input, powerbook_fn_keys); apple_setup_key_translation(input, powerbook_numlock_keys); apple_setup_key_translation(input, apple_iso_keyboard); @@ -963,6 +985,11 @@ static int apple_probe(struct hid_device *hdev, hdev->type != HID_TYPE_SPI_KEYBOARD) return -ENODEV; + // key remapping will happen in xkeyboard-config so ignore + // APPLE_ISO_TILDE_QUIRK + if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && fnmode == FKEYS_IGNORE) + quirks &= ~APPLE_ISO_TILDE_QUIRK; + asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL); if (asc == NULL) { hid_err(hdev, "can't alloc apple descriptor\n"); @@ -1218,7 +1245,7 @@ static const struct hid_device_id apple_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY), .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, // TODO: remove APPLE_ISO_TILDE_QUIRK { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), .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_2021), @@ -1232,7 +1259,7 @@ static const struct hid_device_id apple_devices[] = { { 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_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, HID_ANY_ID), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, // TODO: remove APPLE_ISO_TILDE_QUIRK { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024), .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_2024), From 604082ff7c40038d8c5eaa7b0e8b27219aa6560d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 00:10:51 +0100 Subject: [PATCH 08/42] 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 802a3479e24b92..ecd38178d570ed 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -62,6 +62,8 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define DOUBLE_REPORT_ID 0xf7 #define USB_BATTERY_TIMEOUT_SEC 60 +#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, @@ -143,8 +145,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; @@ -631,7 +633,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, From d45b52d79c1333990bb8557bbc4aadd11098392f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 00:12:35 +0100 Subject: [PATCH 09/42] 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 ecd38178d570ed..3b8ed55ff1fc3e 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -121,6 +121,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. @@ -135,9 +136,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; @@ -194,7 +194,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) @@ -258,8 +258,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 From 0091e0158e6a3871f01e39a7c1bd42ff182ef0b0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 00:15:30 +0100 Subject: [PATCH 10/42] 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 3b8ed55ff1fc3e..acffa3bd177769 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -114,6 +114,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. @@ -127,6 +134,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; @@ -151,6 +159,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) @@ -389,6 +398,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; @@ -554,7 +571,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; @@ -883,6 +910,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); From 3914db8d41990c594d8f9362f7f82ad2b7a68872 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 01:17:48 +0100 Subject: [PATCH 11/42] 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 | 3 +- drivers/hid/hid-magicmouse.c | 273 ++++++++++++++++++++++++++++++++++- 2 files changed, 267 insertions(+), 9 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index ff2f580b660ba6..a9037ccbcb9422 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -743,7 +743,8 @@ config HID_MAGICMOUSE 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 acffa3bd177769..4d21c2459a7f66 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 MOUSE_REPORT_ID 0x29 #define MOUSE2_REPORT_ID 0x12 #define DOUBLE_REPORT_ID 0xf7 +#define SPI_REPORT_ID 0x02 #define USB_BATTERY_TIMEOUT_SEC 60 #define MAX_CONTACTS 16 @@ -114,6 +115,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, @@ -553,6 +566,154 @@ 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 + */ +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] != SPI_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) { @@ -743,6 +904,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) @@ -798,6 +1032,10 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) int feature_size; switch (hdev->product) { + case SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020: + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021: + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021: case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2: case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC: switch (hdev->vendor) { @@ -805,7 +1043,7 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) feature_size = sizeof(feature_mt_trackpad2_bt); feature = feature_mt_trackpad2_bt; break; - default: /* USB_VENDOR_ID_APPLE */ + default: /* USB_VENDOR_ID_APPLE || SPI_VENDOR_ID_APPLE */ feature_size = sizeof(feature_mt_trackpad2_usb); feature = feature_mt_trackpad2_usb; } @@ -904,14 +1142,25 @@ 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; @@ -971,11 +1220,17 @@ static int magicmouse_probe(struct hid_device *hdev, 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); + default: + switch (id->bus) { + case BUS_SPI: + report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_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) { @@ -1076,6 +1331,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); From 2018266c442b93a943726d0d6fca431ae02f4780 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:12:57 +0900 Subject: [PATCH 12/42] 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 | 63 +++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 4d21c2459a7f66..6437ad34b247ac 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -61,6 +61,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 MTP_REPORT_ID 0x75 #define USB_BATTERY_TIMEOUT_SEC 60 #define MAX_CONTACTS 16 @@ -602,25 +603,32 @@ struct tp_finger { } __attribute__((packed, aligned(2))); /** - * struct trackpad report + * struct 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]; +}; + +/** + * struct 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) @@ -650,7 +658,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); @@ -667,9 +675,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] != SPI_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; @@ -708,12 +713,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] != SPI_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) { @@ -1142,7 +1161,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; @@ -1154,7 +1173,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; } else { @@ -1222,6 +1244,9 @@ static int magicmouse_probe(struct hid_device *hdev, break; default: switch (id->bus) { + case BUS_HOST: + report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0); + break; case BUS_SPI: report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0); break; @@ -1240,6 +1265,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. @@ -1333,6 +1362,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); From fdc798c04a5f4e0ea4af15db329a5e046f558c39 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 22:56:16 +0100 Subject: [PATCH 13/42] 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 6437ad34b247ac..20c9ecbd6faaeb 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -1368,6 +1368,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, @@ -1378,6 +1388,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); From 78ab6a1a43b25b7552465e25c3c8c59503dfb221 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 30 Apr 2023 23:48:45 +0900 Subject: [PATCH 14/42] 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 | 108 ++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 21 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 20c9ecbd6faaeb..8060fdd1b4234f 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -61,6 +61,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_SEC 60 @@ -176,6 +177,50 @@ struct magicmouse_sc { struct magicmouse_input_ops input_ops; }; +static int magicmouse_enable_multitouch(struct hid_device *hdev); + +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 && ret < 0) { + hid_err(hdev, "unable to request touch data (%d)\n", ret); + return ret; + } + if (ret == -EIO && (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 || + hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) { + schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + } + + /* + * 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; @@ -722,12 +767,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] != SPI_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] != SPI_REPORT_ID || size < hdr_sz) return 0; return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz); @@ -920,10 +972,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; @@ -996,6 +1055,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) @@ -1062,7 +1140,7 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) feature_size = sizeof(feature_mt_trackpad2_bt); feature = feature_mt_trackpad2_bt; break; - default: /* USB_VENDOR_ID_APPLE || SPI_VENDOR_ID_APPLE */ + default: /* USB_VENDOR_ID_APPLE || SPI_VENDOR_ID_APPLE */ feature_size = sizeof(feature_mt_trackpad2_usb); feature = feature_mt_trackpad2_usb; } @@ -1175,7 +1253,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; @@ -1269,22 +1347,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 || - id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) { - 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; From 4868bd68bf0f065d7f8dd307a3e112612ef55cbc Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 3 Dec 2023 21:08:17 +0900 Subject: [PATCH 15/42] 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 | 104 +++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 24 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 8060fdd1b4234f..a770ceccabde6b 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -63,6 +63,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_SEC 60 #define MAX_CONTACTS 16 @@ -117,6 +118,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 @@ -140,6 +142,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. @@ -154,6 +157,7 @@ struct magicmouse_input_ops { struct magicmouse_sc { struct input_dev *input; unsigned long quirks; + bool query_dimensions; int ntouches; int scroll_accel; @@ -179,6 +183,11 @@ struct magicmouse_sc { static int magicmouse_enable_multitouch(struct hid_device *hdev); +static inline int le16_to_int(__le16 x) +{ + return (signed short)le16_to_cpu(x); +} + static int magicmouse_open(struct input_dev *dev) { struct hid_device *hdev = input_get_drvdata(dev); @@ -196,21 +205,69 @@ 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 && ret < 0) { - hid_err(hdev, "unable to request touch data (%d)\n", ret); - return ret; - } - if (ret == -EIO && (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 || - hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) { - schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + if (hdev->bus != BUS_HOST) { + ret = magicmouse_enable_multitouch(hdev); + if (ret != -EIO && ret < 0) { + hid_err(hdev, "unable to request touch data (%d)\n", ret); + return ret; + } + if (ret == -EIO && (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 || + hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) { + schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + } } /* - * 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; } @@ -676,11 +733,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) @@ -987,6 +1039,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); @@ -1052,6 +1105,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; } @@ -1062,15 +1127,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; } From 6c7a4de4da49c599210bed50327894c91361360a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 10 Dec 2021 19:38:43 +0100 Subject: [PATCH 16/42] 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 Contains "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. Co-developed-by: Hector Martin Signed-off-by: Hector Martin Signed-off-by: Janne Grunau --- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/spi-hid/Kconfig | 24 + drivers/hid/spi-hid/Makefile | 10 + drivers/hid/spi-hid/spi-hid-apple-core.c | 1194 ++++++++++++++++++++++ drivers/hid/spi-hid/spi-hid-apple-of.c | 153 +++ drivers/hid/spi-hid/spi-hid-apple.h | 35 + 7 files changed, 1420 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 a9037ccbcb9422..804904a7a6e186 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1473,4 +1473,6 @@ endif # HID source "drivers/hid/usbhid/Kconfig" +source "drivers/hid/spi-hid/Kconfig" + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0597fd6a4ffd32..88ea0830765266 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -177,6 +177,8 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ +obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig new file mode 100644 index 00000000000000..59076c6ebeed9b --- /dev/null +++ b/drivers/hid/spi-hid/Kconfig @@ -0,0 +1,24 @@ +# 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" + depends on INPUT && OF + select SPI_HID_APPLE_CORE + 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 + select HID + select CRC16 diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile new file mode 100644 index 00000000000000..f276ee12cb94fc --- /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 00000000000000..2ed909895391c8 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple-core.c @@ -0,0 +1,1194 @@ +/* + * 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]; + u8 reply_buf[SPIHID_DESC_MAX]; + u32 reply_len; + 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; + +#ifdef IRQ_WAKE_SUPPORT + bool irq_wake_enabled; +#endif +}; + +/** + * 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 + * @id: 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))); + + 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; + + 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); + int ret; + + 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: + 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; + 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, + .max_buffer_size = SPIHID_MAX_INPUT_REPORT_SIZE, +}; + +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_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) +{ + 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; + } + } + + if (hdr->unknown0 == 0x32) { + return spihid_process_iface_get_report(spihid, device, hdr->unknown1, payload, len); + } + + 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, device, 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; + char *suffix; + struct hid_device *hid; + + iface->id = device; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + /* + * 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)); + + 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 + * 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, 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( + &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); + +#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 new file mode 100644 index 00000000000000..b631212b836d30 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple-of.c @@ -0,0 +1,153 @@ +/* + * 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_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; + 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->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)) { + 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 = &spihid_apple_core_pm, + .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_DESCRIPTION("Apple SPI HID transport driver for OpenFirmware systems"); +MODULE_AUTHOR("Janne Grunau "); +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 00000000000000..9abecd1ba78028 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple.h @@ -0,0 +1,35 @@ +/* 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); + 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); + +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 */ From 6cdc77130db42878d2bcafd773a368ff89c400af Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:09:24 +0900 Subject: [PATCH 17/42] 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 | 9 + drivers/soc/apple/Makefile | 3 + drivers/soc/apple/dockchannel.c | 404 ++++++++++++++++++++++++++ include/linux/soc/apple/dockchannel.h | 26 ++ 4 files changed, 442 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 d0ff32182a2b4a..b44aa998f73bdf 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -4,6 +4,15 @@ if ARCH_APPLE || COMPILE_TEST menu "Apple SoC drivers" +config APPLE_DOCKCHANNEL + tristate "Apple DockChannel FIFO" + depends on ARCH_APPLE || COMPILE_TEST + 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 0b85ab61aefe13..b5ffadc16dc84d 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 00000000000000..3557b1e4e05585 --- /dev/null +++ b/drivers/soc/apple/dockchannel.c @@ -0,0 +1,404 @@ +// 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; + + 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); + + devm_of_platform_populate(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 00000000000000..0b7093935ddf47 --- /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 From 18cedb7453332da3eedd4c8390f1ebb812559432 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:11:21 +0900 Subject: [PATCH 18/42] 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 | 4 + drivers/hid/dockchannel-hid/Kconfig | 13 + drivers/hid/dockchannel-hid/Makefile | 6 + drivers/hid/dockchannel-hid/dockchannel-hid.c | 1213 +++++++++++++++++ 5 files changed, 1238 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 804904a7a6e186..18244ecf301614 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1475,4 +1475,6 @@ source "drivers/hid/usbhid/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 88ea0830765266..d2579e299b5e51 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -177,8 +177,12 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ +obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/ + obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/ +obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ diff --git a/drivers/hid/dockchannel-hid/Kconfig b/drivers/hid/dockchannel-hid/Kconfig new file mode 100644 index 00000000000000..254961ad15e19c --- /dev/null +++ b/drivers/hid/dockchannel-hid/Kconfig @@ -0,0 +1,13 @@ +# 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" + 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 00000000000000..7dba766b047fcc --- /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 00000000000000..9cab638b30c328 --- /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 (%hu > %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 (%hu > %zu)\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"); From f77f12b8ae2813d2a707ad6380ebc492db8b7c2b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 3 Jul 2022 23:33:37 +0900 Subject: [PATCH 19/42] 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 | 13 +++ drivers/soc/apple/Makefile | 3 + drivers/soc/apple/rtkit-helper.c | 151 +++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 drivers/soc/apple/rtkit-helper.c diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index b44aa998f73bdf..128dd2771dc894 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -37,6 +37,19 @@ 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 + 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 b5ffadc16dc84d..10eae75460a5b3 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -9,6 +9,9 @@ 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 00000000000000..080d083ed9bd2f --- /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"); From aeed96195bce4d0117143e7778c03b1d70b3fcd1 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Fri, 12 Jun 2026 20:36:03 +0200 Subject: [PATCH 20/42] power: supply: macsmc: Support macOS 27 SMC firmware The SMC firmware included in macOS 27 changed the size of BCF0 key from 4 to 1 bytes. This key is used for indicating that battery state is critically low. Reviewed-by: Sven Peter Signed-off-by: Sasha Finkelstein --- drivers/power/supply/macsmc-power.c | 35 ++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index d4a1f737ca6ba4..c2dc198ba2f588 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -86,6 +86,7 @@ struct macsmc_power { bool has_ch0i; /* Force discharge (Older firmware) */ bool has_ch0c; /* Inhibit charge (Older firmware) */ bool has_chte; /* Inhibit charge (Modern firmware) */ + bool bcf0_1byte; /* Battery critical key is 1 byte (Modern firmware) */ u8 num_cells; int nominal_voltage_mv; @@ -372,6 +373,19 @@ static int macsmc_battery_get_date(const char *s, int *out) return 0; } +static int macsmc_battery_read_bcf0(struct macsmc_power *power, u32 *val) +{ + u8 tval = 0; + int ret; + + if (!power->bcf0_1byte) + return apple_smc_read_u32(power->smc, SMC_KEY(BCF0), val); + + ret = apple_smc_read_u8(power->smc, SMC_KEY(BCF0), &tval); + *val = tval; + return ret; +} + static int macsmc_battery_get_capacity_level(struct macsmc_power *power) { bool flag; @@ -379,7 +393,7 @@ static int macsmc_battery_get_capacity_level(struct macsmc_power *power) int ret; /* Check for emergency shutdown condition */ - if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val) + if (macsmc_battery_read_bcf0(power, &val) >= 0 && val) return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; /* Check AC status for whether we could boot in this state */ @@ -700,7 +714,7 @@ static void macsmc_power_critical_work(struct work_struct *wrk) * Check if SMC flagged the battery as empty. * We trigger a graceful shutdown to let the OS save data. */ - if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0) == 0 && bcf0 != 0) { + if (macsmc_battery_read_bcf0(power, &bcf0) == 0 && bcf0 != 0) { power->orderly_shutdown_triggered = true; dev_crit(power->dev, "Battery critical (empty flag set). Triggering orderly shutdown.\n"); orderly_poweroff(true); @@ -744,6 +758,7 @@ static int macsmc_power_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); struct power_supply_config psy_cfg = {}; + struct apple_smc_key_info info; struct macsmc_power *power; bool has_battery = false; bool has_ac_adapter = false; @@ -842,6 +857,20 @@ static int macsmc_power_probe(struct platform_device *pdev) if (apple_smc_key_exists(smc, SMC_KEY(CH0I))) power->has_ch0i = true; + ret = apple_smc_get_key_info(power->smc, SMC_KEY(BCF0), &info); + if (ret) { + dev_err(&pdev->dev, "Failed to determine BCF0 key size\n"); + return ret; + } + if (info.size == 1) + power->bcf0_1byte = true; + else if (info.size == 4) + power->bcf0_1byte = false; + else { + dev_err(&pdev->dev, "Unexpected BCF0 key size %d\n", info.size); + return -EIO; + } + /* Reset "Optimised Battery Charging" flags to default state */ if (power->has_chte) apple_smc_write_u32(smc, SMC_KEY(CHTE), 0); @@ -894,7 +923,7 @@ static int macsmc_power_probe(struct platform_device *pdev) power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells; /* Enable critical shutdown notifications by reading status once */ - apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32); + macsmc_battery_read_bcf0(power, &val32); psy_cfg.drv_data = power; power->batt = devm_power_supply_register(dev, &power->batt_desc, &psy_cfg); From f710fc6548a76bb429821eb04e14865d6d71e1ea Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Tue, 7 Apr 2026 13:33:46 +0200 Subject: [PATCH 21/42] Bluetooth: Add Broadcom channel priority commands Certain Broadcom bluetooth chips (bcm4377/bcm4378/bcm438) need ACL streams carrying audio to be set as "high priority" using a vendor specific command to prevent 10-ish second-long dropouts whenever something does a device scan. This patch sends the command when the socket priority is set to TC_PRIO_INTERACTIVE, as BlueZ does for audio. Signed-off-by: Sasha Finkelstein --- MAINTAINERS | 2 ++ drivers/bluetooth/hci_bcm4377.c | 2 ++ include/net/bluetooth/bluetooth.h | 4 ++++ include/net/bluetooth/hci_core.h | 11 +++++++++++ net/bluetooth/Kconfig | 7 +++++++ net/bluetooth/Makefile | 1 + net/bluetooth/brcm.c | 29 +++++++++++++++++++++++++++++ net/bluetooth/brcm.h | 17 +++++++++++++++++ net/bluetooth/hci_conn.c | 27 +++++++++++++++++++++++++++ net/bluetooth/l2cap_sock.c | 13 +++++++++++++ 10 files changed, 113 insertions(+) create mode 100644 net/bluetooth/brcm.c create mode 100644 net/bluetooth/brcm.h diff --git a/MAINTAINERS b/MAINTAINERS index a24c0cb47aa5b8..1fa5182c49ac4f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2593,6 +2593,8 @@ F: include/dt-bindings/pinctrl/apple.h F: include/linux/mfd/macsmc.h F: include/linux/soc/apple/* F: include/uapi/drm/asahi_drm.h +F: net/bluetooth/brcm.c +F: net/bluetooth/brcm.h ARM/ARTPEC MACHINE SUPPORT M: Jesper Nilsson diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c index 925d0a6359453e..5f79920c030681 100644 --- a/drivers/bluetooth/hci_bcm4377.c +++ b/drivers/bluetooth/hci_bcm4377.c @@ -2397,6 +2397,8 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (bcm4377->hw->broken_le_ext_adv_report_phy) hci_set_quirk(hdev, HCI_QUIRK_FIXUP_LE_EXT_ADV_REPORT_PHY); + hci_set_brcm_capable(hdev); + pci_set_drvdata(pdev, bcm4377); hci_set_drvdata(hdev, bcm4377); SET_HCIDEV_DEV(hdev, &pdev->dev); diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index 3faea66b19799a..5d82944370e427 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -458,6 +458,7 @@ struct l2cap_ctrl { }; struct hci_dev; +struct hci_conn; typedef void (*hci_req_complete_t)(struct hci_dev *hdev, u8 status, u16 opcode); typedef void (*hci_req_complete_skb_t)(struct hci_dev *hdev, u8 status, @@ -470,6 +471,9 @@ void hci_req_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 status, int hci_ethtool_ts_info(unsigned int index, int sk_proto, struct kernel_ethtool_ts_info *ts_info); +int hci_conn_setsockopt(struct hci_conn *conn, struct sock *sk, int level, + int optname, sockptr_t optval, unsigned int optlen); + #define HCI_REQ_START BIT(0) #define HCI_REQ_SKB BIT(1) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index aa554c34f9ec35..a5f9251178cbf7 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -642,6 +642,10 @@ struct hci_dev { bool aosp_quality_report; #endif +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + bool brcm_capable; +#endif + int (*open)(struct hci_dev *hdev); int (*close)(struct hci_dev *hdev); int (*flush)(struct hci_dev *hdev); @@ -1792,6 +1796,13 @@ static inline void hci_set_aosp_capable(struct hci_dev *hdev) #endif } +static inline void hci_set_brcm_capable(struct hci_dev *hdev) +{ +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + hdev->brcm_capable = true; +#endif +} + static inline void hci_devcd_setup(struct hci_dev *hdev) { #ifdef CONFIG_DEV_COREDUMP diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig index ee6457d1a5eed3..b611942c7b8ffd 100644 --- a/net/bluetooth/Kconfig +++ b/net/bluetooth/Kconfig @@ -107,6 +107,13 @@ config BT_AOSPEXT This options enables support for the Android Open Source Project defined HCI vendor extensions. +config BT_BRCMEXT + bool "Enable Broadcom extensions" + depends on BT + help + This option enables support for the Broadcom defined HCI + vendor extensions. + config BT_DEBUGFS bool "Export Bluetooth internals in debugfs" depends on BT && DEBUG_FS diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index 41049b280887db..d402645dfb7da0 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -23,5 +23,6 @@ bluetooth-$(CONFIG_BT_LE) += iso.o bluetooth-$(CONFIG_BT_LEDS) += leds.o bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o bluetooth-$(CONFIG_BT_AOSPEXT) += aosp.o +bluetooth-$(CONFIG_BT_BRCMEXT) += brcm.o bluetooth-$(CONFIG_BT_DEBUGFS) += hci_debugfs.o bluetooth-$(CONFIG_BT_SELFTEST) += selftest.o diff --git a/net/bluetooth/brcm.c b/net/bluetooth/brcm.c new file mode 100644 index 00000000000000..9aa0a265ab3d6b --- /dev/null +++ b/net/bluetooth/brcm.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 The Asahi Linux Contributors + */ + +#include +#include + +#include "brcm.h" + +int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable) +{ + struct sk_buff *skb; + u8 cmd[3]; + + if (!hdev->brcm_capable) + return 0; + + cmd[0] = handle; + cmd[1] = handle >> 8; + cmd[2] = !!enable; + + skb = hci_cmd_sync(hdev, 0xfc57, sizeof(cmd), cmd, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + kfree_skb(skb); + return 0; +} diff --git a/net/bluetooth/brcm.h b/net/bluetooth/brcm.h new file mode 100644 index 00000000000000..fdaee63bd1d23c --- /dev/null +++ b/net/bluetooth/brcm.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2026 The Asahi Linux Contributors + */ + +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + +int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable); + +#else + +static inline int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable) +{ + return 0; +} + +#endif diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index eba4a548bef52a..f481c944061add 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -35,6 +35,7 @@ #include #include +#include "brcm.h" #include "smp.h" #include "eir.h" @@ -3156,6 +3157,32 @@ int hci_conn_set_phy(struct hci_conn *conn, u32 phys) } } +int hci_conn_setsockopt(struct hci_conn *conn, struct sock *sk, int level, + int optname, sockptr_t optval, unsigned int optlen) { + int val; + bool old_high, new_high, changed; + + if (level != SOL_SOCKET) + return 0; + + if (optname != SO_PRIORITY) + return 0; + + if (optlen < sizeof(int)) + return -EINVAL; + + if (copy_from_sockptr(&val, optval, sizeof(val))) + return -EFAULT; + + old_high = sk->sk_priority >= TC_PRIO_INTERACTIVE; + new_high = val >= TC_PRIO_INTERACTIVE; + changed = old_high != new_high; + if (!changed) + return 0; + + return brcm_set_high_priority(conn->hdev, conn->handle, new_high); +} + static int abort_conn_sync(struct hci_dev *hdev, void *data) { struct hci_conn *conn = data; diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index d8cbe278443779..0c86be41622e2b 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -897,6 +897,16 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, BT_DBG("sk %p", sk); + if (level == SOL_SOCKET) { + conn = chan->conn; + if (conn) + err = hci_conn_setsockopt(conn->hcon, sock->sk, level, + optname, optval, optlen); + if (err) + return err; + return sock_setsockopt(sock, level, optname, optval, optlen); + } + if (level == SOL_L2CAP) return l2cap_sock_setsockopt_old(sock, optname, optval, optlen); @@ -1976,6 +1986,9 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); + if (sock) + set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags); + /* The sock takes ownership of the caller's reference on chan. */ l2cap_pi(sk)->chan = chan; From 9c6f3ad0939b8e09bfccb8f444176806925b4027 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 21 May 2026 10:30:50 +0200 Subject: [PATCH 22/42] Fail the build on RUST=y and RUST_IS_AVAILABLE=n The current approach of silently disabling all rust drivers if the toolchain is missing results in users that try to compile their own kernels getting a "successful" build and then being confused about where did their drivers go. In comparison, missing openssl results in a build failure, not a disappearance of everything that depends on it. This also means that allyesconfig will depend on rust, but since the rust experiment concluded with "rust is here to stay", i believe that allyesconfig should be building rust drivers too. Signed-off-by: Sasha Finkelstein --- Documentation/rust/quick-start.rst | 6 +++--- init/Kconfig | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/rust/quick-start.rst b/Documentation/rust/quick-start.rst index a6ec3fa94d3307..764c81d0dd5926 100644 --- a/Documentation/rust/quick-start.rst +++ b/Documentation/rust/quick-start.rst @@ -321,9 +321,9 @@ Configuration ------------- ``Rust support`` (``CONFIG_RUST``) needs to be enabled in the ``General setup`` -menu. The option is only shown if a suitable Rust toolchain is found (see -above), as long as the other requirements are met. In turn, this will make -visible the rest of options that depend on Rust. +menu. In turn, this will make visible the rest of options that depend on Rust. +You can check the value of ``RUST_IS_AVAILABLE`` to determine if your toolchain +is configured correctly. Afterwards, go to:: diff --git a/init/Kconfig b/init/Kconfig index 826a7d768ca368..7beb3f1753b206 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -2190,7 +2190,6 @@ config PROFILING config RUST bool "Rust support" depends on HAVE_RUST - depends on RUST_IS_AVAILABLE select EXTENDED_MODVERSIONS if MODVERSIONS depends on !MODVERSIONS || GENDWARFKSYMS depends on !GCC_PLUGIN_RANDSTRUCT From 49e2583371803891bd2ad0764b5196989de3c346 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 9 May 2026 11:58:52 +0200 Subject: [PATCH 23/42] driver-core: Add error message to device_links_missing_supplier WARN() Signed-off-by: Janne Grunau --- drivers/base/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/base/core.c b/drivers/base/core.c index 3ee92a5b88c3a0..a9df82e5f6b5d9 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1076,6 +1076,7 @@ static void device_links_missing_supplier(struct device *dev) if (link->supplier->links.status == DL_DEV_DRIVER_BOUND) { WRITE_ONCE(link->status, DL_STATE_AVAILABLE); } else { + dev_err(dev, "devices misses supplier %s\n", dev_name(link->supplier)); WARN_ON(!device_link_test(link, DL_FLAG_SYNC_STATE_ONLY)); WRITE_ONCE(link->status, DL_STATE_DORMANT); } From 073f2948a5f9970bb7f7f2a798c88cea74605a89 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 30 May 2026 12:16:44 +0200 Subject: [PATCH 24/42] dt-bindings: gpio: apple,smc: Add compatible for 'gp00' keys Apple M3 Pro and Max devices are using 'gp00' keys for GPIO in addition to 'gP00' keys. Add a second compatible to handle this keys with an additional macsmc-gpio instance. Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml b/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml index 42b1bc0a10c97a..b4063a9dd1248c 100644 --- a/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml @@ -14,7 +14,9 @@ description: properties: compatible: - const: apple,smc-gpio + enum: + - apple,smc-gpio + - apple,smc-low-gpio gpio-controller: true From d02c8506e105ebae8cfb2dbf28867acaac245ad7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 29 May 2026 20:54:16 +0200 Subject: [PATCH 25/42] gpio: gpio-macsmc: Support 'gp00' GPIO keys Add support for SMC GPIO keys with a lower letter 'p' via the "apple,smc-low-gpio" compatible. This adds support for a second macsmc-gpio controller using 'gp00' keys. These keys are used on Apple M3 Pro and Max MacBooks in the controller for keyboard and trackpad and for the built-in DisplayPort to HDMI converter. Signed-off-by: Janne Grunau --- drivers/gpio/gpio-macsmc.c | 45 +++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c index b0952d066a9dd0..c3ca445a85ac9d 100644 --- a/drivers/gpio/gpio-macsmc.c +++ b/drivers/gpio/gpio-macsmc.c @@ -75,6 +75,7 @@ struct macsmc_gpio { struct gpio_chip gc; int first_index; + smc_key base_key; }; static int macsmc_gpio_nr(smc_key key) @@ -88,15 +89,15 @@ static int macsmc_gpio_nr(smc_key key) return low | (high << 4); } -static int macsmc_gpio_key(unsigned int offset) +static int macsmc_gpio_key(smc_key base_key, unsigned int offset) { - return _SMC_KEY("gP\0\0") | hex_asc_hi(offset) << 8 | hex_asc_lo(offset); + return base_key | hex_asc_hi(offset) << 8 | hex_asc_lo(offset); } static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp) { struct apple_smc *smc = smcgp->smc; - smc_key key = macsmc_gpio_key(0); + smc_key key = macsmc_gpio_key(smcgp->base_key, 0); smc_key first_key, last_key; int start, count, ret; @@ -143,7 +144,7 @@ static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp) static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); u32 val; int ret; @@ -163,7 +164,7 @@ static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); u32 cmd, val; int ret; @@ -186,7 +187,7 @@ static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset) static int macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); int ret; value |= CMD_OUTPUT; @@ -217,7 +218,7 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, if (ret < 0) return ret; - if (key > SMC_KEY(gPff)) + if (key > macsmc_gpio_key(smcgp->base_key, MAX_GPIO - 1)) break; gpio_nr = macsmc_gpio_nr(key); @@ -232,10 +233,15 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, return 0; } +struct macsmc_gpio_of_match_data { + smc_key base_key; +}; + static int macsmc_gpio_probe(struct platform_device *pdev) { struct macsmc_gpio *smcgp; struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + const struct macsmc_gpio_of_match_data *data = of_device_get_match_data(&pdev->dev); smc_key key; int ret; @@ -245,6 +251,7 @@ static int macsmc_gpio_probe(struct platform_device *pdev) smcgp->dev = &pdev->dev; smcgp->smc = smc; + smcgp->base_key = data ? data->base_key : _SMC_KEY("gP\0\0"); smcgp->first_index = macsmc_gpio_find_first_gpio_index(smcgp); if (smcgp->first_index < 0) @@ -254,12 +261,15 @@ static int macsmc_gpio_probe(struct platform_device *pdev) if (ret < 0) return ret; - if (key > macsmc_gpio_key(MAX_GPIO - 1)) + if (key > macsmc_gpio_key(smcgp->base_key, MAX_GPIO - 1)) return -ENODEV; dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key); - smcgp->gc.label = "macsmc-pmu-gpio"; + if (device_is_compatible(&pdev->dev, "apple,smc-low-gpio")) + smcgp->gc.label = "macsmc-pmu-low-gpio"; + else + smcgp->gc.label = "macsmc-pmu-gpio"; smcgp->gc.owner = THIS_MODULE; smcgp->gc.get = macsmc_gpio_get; smcgp->gc.set = macsmc_gpio_set; @@ -273,8 +283,23 @@ static int macsmc_gpio_probe(struct platform_device *pdev) return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp); } +static const struct macsmc_gpio_of_match_data macsmc_gpio_up_data = { + .base_key = _SMC_KEY("gP\0\0"), +}; + +static const struct macsmc_gpio_of_match_data macsmc_gpio_low_data = { + .base_key = _SMC_KEY("gp\0\0"), +}; + static const struct of_device_id macsmc_gpio_of_table[] = { - { .compatible = "apple,smc-gpio", }, + { + .compatible = "apple,smc-gpio", + .data = &macsmc_gpio_up_data, + }, + { + .compatible = "apple,smc-low-gpio", + .data = &macsmc_gpio_low_data, + }, {} }; MODULE_DEVICE_TABLE(of, macsmc_gpio_of_table); From 0abb5959b930ceeab87659b86f484a95e2f4c134 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 30 May 2026 12:20:39 +0200 Subject: [PATCH 26/42] mfd: macsmc: Add second gpio subdevice for 'gp00' keys Apple M3 Pro and Max devices are using 'gp00' keys for GPIO in addition to 'gP00' keys. These keys are handled by an additional macsmc-gpio instance using the "apple,smc-low-gpio" compatible. Signed-off-by: Janne Grunau --- drivers/mfd/macsmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 514cba7dc897ae..31540f67ddbd8a 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -48,6 +48,7 @@ static const struct mfd_cell apple_smc_devs[] = { MFD_CELL_NAME("macsmc-input"), MFD_CELL_NAME("macsmc-power"), MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"), + MFD_CELL_OF("macsmc-low-gpio", NULL, NULL, 0, 0, "apple,smc-low-gpio"), MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"), MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"), From ffff3d16f6b2bd42e98d735421de216b24a30d17 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 14 Jun 2026 22:34:12 +0200 Subject: [PATCH 27/42] phy: apple: atc: Handle dummy pipehandler transisions Signed-off-by: Janne Grunau --- drivers/phy/apple/atc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 4156fabad742cc..6d8888732a83fb 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1133,6 +1133,8 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) case ATCPHY_PIPEHANDLER_STATE_USB4: dev_warn(atcphy->dev, "ATCPHY_PIPEHANDLER_STATE_USB4 not implemented; falling back to USB2\n"); + fallthrough; + case ATCPHY_PIPEHANDLER_STATE_DUMMY: ret = atcphy_configure_pipehandler_dummy(atcphy); atcphy->pipehandler_up = false; break; From 28962a37ce0a0cd70d81781700f2bb2bddb38ec2 Mon Sep 17 00:00:00 2001 From: sofus Date: Sat, 18 Jul 2026 09:19:05 +0200 Subject: [PATCH 28/42] fixup! media: apple: avd: make get_ref_buf shared Signed-off-by: sofus --- drivers/media/platform/apple/avd/avd-drv.c | 2 +- drivers/media/platform/apple/avd/avd-hevc.c | 4 ++-- drivers/media/platform/apple/avd/avd-vp9.c | 6 +++--- drivers/media/platform/apple/avd/avd.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index 400cbe76cff8f7..2e826afb19b952 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -101,7 +101,7 @@ void avd_buf_free(struct avd_dev *avd, struct avd_buf *buf) } struct avd_decoded_buffer * -get_ref_buf(struct avd_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp) +avd_get_ref_buf(struct avd_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp) { struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q; diff --git a/drivers/media/platform/apple/avd/avd-hevc.c b/drivers/media/platform/apple/avd/avd-hevc.c index af0a4eabaf9f16..e7dbe65f3bd6b2 100644 --- a/drivers/media/platform/apple/avd/avd-hevc.c +++ b/drivers/media/platform/apple/avd/avd-hevc.c @@ -91,7 +91,7 @@ static void stream_refs(struct avd_ctx *ctx, struct avd_hevc_run *run) for (int i = 0; i < decode->num_active_dpb_entries; i++) { dpb = &decode->dpb[i]; - ref_buf = get_ref_buf(ctx, &dst->base.vb, dpb->timestamp); + ref_buf = avd_get_ref_buf(ctx, &dst->base.vb, dpb->timestamp); dma_addr_t rvra_addr = vb2_dma_contig_plane_dma_addr(&ref_buf->base.vb.vb2_buf, 0) @@ -503,7 +503,7 @@ static void stream_slice_mv(struct avd_ctx *ctx, struct avd_hevc_run *run, ? sl->ref_idx_l0 : sl->ref_idx_l1; dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf); - ref = get_ref_buf(ctx, &dst->base.vb, + ref = avd_get_ref_buf(ctx, &dst->base.vb, decode->dpb[ref_list[sl->collocated_ref_idx]].timestamp); ref_valid = diff --git a/drivers/media/platform/apple/avd/avd-vp9.c b/drivers/media/platform/apple/avd/avd-vp9.c index fb423616aa7726..77dd49de6cf140 100644 --- a/drivers/media/platform/apple/avd/avd-vp9.c +++ b/drivers/media/platform/apple/avd/avd-vp9.c @@ -163,9 +163,9 @@ static void set_refs(struct avd_ctx *ctx, struct avd_vp9_run *run) dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf); - ref_buf[0] = get_ref_buf(ctx, &dst->base.vb, frame->last_frame_ts); - ref_buf[1] = get_ref_buf(ctx, &dst->base.vb, frame->golden_frame_ts); - ref_buf[2] = get_ref_buf(ctx, &dst->base.vb, frame->alt_frame_ts); + ref_buf[0] = avd_get_ref_buf(ctx, &dst->base.vb, frame->last_frame_ts); + ref_buf[1] = avd_get_ref_buf(ctx, &dst->base.vb, frame->golden_frame_ts); + ref_buf[2] = avd_get_ref_buf(ctx, &dst->base.vb, frame->alt_frame_ts); push(INST_DMA3, "cm3_dma_config_7"); push(INST_DMA3, "cm3_dma_config_8"); diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h index a12b42e2a68461..82086ac3d9ca88 100644 --- a/drivers/media/platform/apple/avd/avd.h +++ b/drivers/media/platform/apple/avd/avd.h @@ -101,7 +101,7 @@ vb2_to_avd_decoded_buf(struct vb2_buffer *buf) return container_of(buf, struct avd_decoded_buffer, base.vb.vb2_buf); } struct avd_decoded_buffer * -get_ref_buf(struct avd_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp); +avd_get_ref_buf(struct avd_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp); struct avd_coded_fmt_ops { void (*adjust_decoded_fmt)(struct avd_ctx *ctx, From 6d9dfa12941509e7b256998cbcd3d2e16de15030 Mon Sep 17 00:00:00 2001 From: DesktopECHO <33142753+DesktopECHO@users.noreply.github.com> Date: Wed, 15 Jul 2026 22:46:35 -0300 Subject: [PATCH 29/42] drm/asahi: Clean up deferred BOs when dropping a VM Since immediate mode was enabled, unmapping a GPU VA can defer drm_gpuvm_bo destruction until drm_gpuvm_bo_deferred_cleanup() is called. The GEM bind and unbind paths drain that list, but Vm::drop() unmaps the remaining user ranges without doing so. Drain the deferred list after both teardown unmaps. Otherwise a deferred drm_gpuvm_bo retains the imported GEM and dma-buf after its DRM file is closed, leaving its backing pages pinned. Fixes: 2aeee2dd4a79 ("drm/asahi: Switch gpuvm to DRM_GPUVM_IMMEDIATE_MODE") Signed-off-by: DesktopECHO <33142753+DesktopECHO@users.noreply.github.com> --- drivers/gpu/drm/asahi/file.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs index 02a4ce874a92c8..061f289e40d933 100644 --- a/drivers/gpu/drm/asahi/file.rs +++ b/drivers/gpu/drm/asahi/file.rs @@ -87,6 +87,8 @@ impl Drop for Vm { { pr_err!("Vm::Drop: vm.unmap_range() failed\n"); } + + self.vm.bo_deferred_cleanup(); } } From a326e00f6d17ac093431a4a3aef75aa21cd95b36 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 25 Jul 2026 20:28:29 +0200 Subject: [PATCH 30/42] arm64: configs: asahi: Add new configs for v7.1 Add CONFIG_VIDEO_APPLE_AVD for AVD (Apple video decoder) support. Signed-off-by: Janne Grunau --- arch/arm64/configs/asahi.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index c76f2c727fb0dd..b7ff1bbad24dcb 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -24,6 +24,7 @@ CONFIG_POWER_RESET_MACSMC=m CONFIG_CHARGER_MACSMC=m CONFIG_SENSORS_MACSMC_HWMON=m CONFIG_APPLE_WATCHDOG=m +CONFIG_VIDEO_APPLE_AVD=m CONFIG_VIDEO_APPLE_ISP=m CONFIG_DRM=y CONFIG_DRM_ASAHI=m From 42762d102d3a58f699a8fd46be1df89f8540f275 Mon Sep 17 00:00:00 2001 From: sofus Date: Sat, 25 Jul 2026 22:06:52 +0200 Subject: [PATCH 31/42] fixup! media: apple: avd: add hevc support Signed-off-by: sofus --- drivers/media/platform/apple/avd/avd-hevc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/avd/avd-hevc.c b/drivers/media/platform/apple/avd/avd-hevc.c index e7dbe65f3bd6b2..478db2d9eca076 100644 --- a/drivers/media/platform/apple/avd/avd-hevc.c +++ b/drivers/media/platform/apple/avd/avd-hevc.c @@ -311,7 +311,7 @@ static void set_header(struct avd_ctx *ctx, struct avd_hevc_run *run) push(0, "cm3_mark_end_section"); - if (avd->variant->revision != 3) + if (!(avd->variant->quirks & AVD_QUIRK_LSR)) push(0, "cm3_mark_end_section"); bytesperline = ctx->decoded_fmt.fmt.pix_mp.plane_fmt[0].bytesperline; From e8efe09d4f378992c890d181d65e2ed8d8cb1194 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 8 Feb 2022 02:30:16 +0900 Subject: [PATCH 32/42] power: supply: macsmc_power: Add CHWA / CHLS charge thresholds This is a hardcoded charge threshold feature present in firmware 13.0 or newer. Userspace settings are rounded to one of the two possible behaviors. Since macOS Sequoia firmware, CHLS replaced CHWA and now allows an arbitrary end charge threshold to be configured. Prefer CHWA over CHLS since the SMC firmware from iBoot-10151.1.1 (macOS 14.0) is not compatible with our CHGLS usage. It was working with the SMC firmware from iBoot-10151.121.1 (macOS 14.5). Signed-off-by: Janne Grunau Co-developed-by: Janne Grunau Signed-off-by: Hector Martin --- drivers/power/supply/macsmc-power.c | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index c2dc198ba2f588..cfc57eb9a471b0 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -563,6 +563,24 @@ static int macsmc_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_MANUFACTURE_DAY: ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval); break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + if (power->has_chls) { + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); + val->intval = vu16 & 0xff; + if (val->intval < CHLS_MIN_END_THRESHOLD || val->intval >= 100) + val->intval = 100; + } else if (power->has_chwa) { + flag = false; + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag); + val->intval = flag ? CHWA_FIXED_END_THRESHOLD : 100; + } else { + return -EINVAL; + } + if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD && + ret >= 0 && val->intval < 100 && val->intval >= CHLS_MIN_END_THRESHOLD) + val->intval -= CHWA_CHLS_FIXED_START_OFFSET; + break; default: return -EINVAL; } @@ -579,6 +597,28 @@ static int macsmc_battery_set_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: return macsmc_battery_set_charge_behaviour(power, val->intval); + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + /* + * Ignore, we allow writes so userspace isn't confused but this is + * not configurable independently, it always is end - 5 or 100 depending + * on the end_threshold setting. + */ + return 0; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + if (power->has_chls) { + u16 kval = 0; + /* TODO: Make CHLS_FORCE_DISCHARGE configurable */ + if (val->intval < CHLS_MIN_END_THRESHOLD) + kval = CHLS_FORCE_DISCHARGE | CHLS_MIN_END_THRESHOLD; + else if (val->intval < 100) + kval = CHLS_FORCE_DISCHARGE | (val->intval & 0xff); + return apple_smc_write_u16(power->smc, SMC_KEY(CHLS), kval); + } else if (power->has_chwa) { + return apple_smc_write_flag(power->smc, SMC_KEY(CHWA), + val->intval <= CHWA_PROP_WRITE_THRESHOLD); + } else { + return -EINVAL; + } default: return -EINVAL; } @@ -587,9 +627,14 @@ static int macsmc_battery_set_property(struct power_supply *psy, static int macsmc_battery_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { + struct macsmc_power *power = power_supply_get_drvdata(psy); + switch (psp) { case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: return true; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + return power->has_chwa || power->has_chls; default: return false; } @@ -905,6 +950,11 @@ static int macsmc_power_probe(struct platform_device *pdev) else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0) power->has_chls = true; + if (power->has_chwa || power->has_chls) { + props[nprops++] = POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; + props[nprops++] = POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD; + } + if (nprops > MACSMC_MAX_BATT_PROPS) return -ENOMEM; From e25660512a0361dee7dd300df3f2e4eb1f822790 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Jul 2026 12:40:56 +0200 Subject: [PATCH 33/42] fixup! arm64: configs: asahi: Add new configs for v7.1 Signed-off-by: Janne Grunau --- arch/arm64/configs/asahi.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index b7ff1bbad24dcb..4aa7f499997e73 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -21,7 +21,7 @@ CONFIG_SPMI_APPLE=m CONFIG_PINCTRL_APPLE_GPIO=m CONFIG_GPIO_MACSMC=m CONFIG_POWER_RESET_MACSMC=m -CONFIG_CHARGER_MACSMC=m +CONFIG_MACSMC_POWER=m CONFIG_SENSORS_MACSMC_HWMON=m CONFIG_APPLE_WATCHDOG=m CONFIG_VIDEO_APPLE_AVD=m From 1668fc68de431a7c635c7ef801c29ffd330373db Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 9 Jul 2026 22:07:59 +0200 Subject: [PATCH 34/42] dt-bindings: dma: apple,admac: Add M3 generation ADMACs The admacs seen in M3-generation SoCs (t603x, t8122) need additional configuration writes and so are getting a new compatible chain. Signed-off-by: Sasha Finkelstein --- Documentation/devicetree/bindings/dma/apple,admac.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/dma/apple,admac.yaml b/Documentation/devicetree/bindings/dma/apple,admac.yaml index 6a200cbd7d0280..560fd19a4c6f1f 100644 --- a/Documentation/devicetree/bindings/dma/apple,admac.yaml +++ b/Documentation/devicetree/bindings/dma/apple,admac.yaml @@ -10,7 +10,7 @@ description: | Apple's Audio DMA Controller (ADMAC) is used to fetch and store audio samples on SoCs from the "Apple Silicon" family. - The controller has been seen with up to 24 channels. Even-numbered channels + The controller has been seen with up to 32 channels. Even-numbered channels are TX-only, odd-numbered are RX-only. Individual channels are coupled to fixed device endpoints. @@ -24,6 +24,7 @@ properties: compatible: oneOf: - items: + - const: apple,t8122-admac - const: apple,t6020-admac - const: apple,t8103-admac - items: @@ -33,6 +34,11 @@ properties: - apple,t8103-admac - apple,t8112-admac - const: apple,admac + - items: + - enum: + - apple,t6030-admac + - apple,t6031-admac + - const: apple,t8122-admac reg: maxItems: 1 @@ -43,7 +49,7 @@ properties: Clients specify a single cell with channel number. dma-channels: - maximum: 24 + maximum: 32 interrupts: minItems: 4 From 972c782db8bc107933d19937766ac232ee9a6ae1 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 9 Jul 2026 22:58:25 +0200 Subject: [PATCH 35/42] arm64: dts: apple: t8122: Add aop/admac nodes Signed-off-by: Sasha Finkelstein --- arch/arm64/boot/dts/apple/t8122-j504.dts | 21 ++++++++ arch/arm64/boot/dts/apple/t8122-j613.dts | 21 ++++++++ arch/arm64/boot/dts/apple/t8122-j615.dts | 21 ++++++++ arch/arm64/boot/dts/apple/t8122.dtsi | 64 +++++++++++++++++++++++- 4 files changed, 126 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t8122-j504.dts b/arch/arm64/boot/dts/apple/t8122-j504.dts index ef241ed635f7e3..d76a5d79437289 100644 --- a/arch/arm64/boot/dts/apple/t8122-j504.dts +++ b/arch/arm64/boot/dts/apple/t8122-j504.dts @@ -101,6 +101,27 @@ }; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + +&aop_audio { + apple,chassis-name = "J504"; + apple,machine-kind = "MacBook Pro"; +}; + / { speaker_sdz: fixed-regulator-sn012776-sdz { compatible = "regulator-fixed"; diff --git a/arch/arm64/boot/dts/apple/t8122-j613.dts b/arch/arm64/boot/dts/apple/t8122-j613.dts index ec8eeebfdce40c..1a02bb7c97bd2c 100644 --- a/arch/arm64/boot/dts/apple/t8122-j613.dts +++ b/arch/arm64/boot/dts/apple/t8122-j613.dts @@ -38,6 +38,27 @@ brcm,board-type = "apple,dnieper"; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + +&aop_audio { + apple,chassis-name = "J613"; + apple,machine-kind = "MacBook Air"; +}; + /* Virtual regulator representing the shared shutdown GPIO */ / { speaker_sdz: fixed-regulator-sn012776-sdz { diff --git a/arch/arm64/boot/dts/apple/t8122-j615.dts b/arch/arm64/boot/dts/apple/t8122-j615.dts index 9914c34055025d..e10e31bb2b5cda 100644 --- a/arch/arm64/boot/dts/apple/t8122-j615.dts +++ b/arch/arm64/boot/dts/apple/t8122-j615.dts @@ -30,6 +30,27 @@ }; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + +&aop_audio { + apple,chassis-name = "J615"; + apple,machine-kind = "MacBook Air"; +}; + &wifi0 { brcm,board-type = "apple,tuzla"; }; diff --git a/arch/arm64/boot/dts/apple/t8122.dtsi b/arch/arm64/boot/dts/apple/t8122.dtsi index 50b39bfe521952..a657784b8bea51 100644 --- a/arch/arm64/boot/dts/apple/t8122.dtsi +++ b/arch/arm64/boot/dts/apple/t8122.dtsi @@ -529,7 +529,7 @@ }; admac: dma-controller@2a3200000 { - compatible = "apple,t8122-admac", "apple,t8103-admac"; + compatible = "apple,t8122-admac"; reg = <0x2 0xa3200000 0x0 0x34000>; dma-channels = <24>; interrupts-extended = <0>, @@ -830,6 +830,68 @@ ; }; + aop_mbox: mbox@2f4408000 { + compatible = "apple,t8122-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0xf4408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + aop_dart: iommu@2f4818000 { + compatible = "apple,t8122-dart", "apple,t8110-dart"; + reg = <0x2 0xf4818000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + aop_admac: dma-controller@2f4980000 { + compatible = "apple,t8122-admac"; + reg = <0x2 0xf4980000 0x0 0x34000>; + #dma-cells = <1>; + dma-channels = <16>; + interrupts-extended = <0>, + <0>, + <&aic AIC_IRQ 412 IRQ_TYPE_LEVEL_HIGH>, + <0>; + iommus = <&aop_dart 10>; + status = "disabled"; + }; + + aop: aop@2f4c00000 { + compatible = "apple,t8122-aop", "apple,t6020-aop"; + reg = <0x2 0xf4c00000 0x0 0x1e0000>, + <0x2 0xf4400000 0x0 0x6c000>; + mboxes = <&aop_mbox>; + mbox-names = "mbox"; + iommus = <&aop_dart 0>; + + status = "disabled"; + + aop_audio: audio { + compatible = "apple,t8122-aop-audio", "apple,t6030-aop-audio"; + dmas = <&aop_admac 1>; + dma-names = "dma"; + }; + + aop_als: als { + compatible = "apple,t8122-aop-als", "apple,aop-als"; + // intentionally empty + }; + + las { + compatible = "apple,t8122-aop-las", "apple,aop-las"; + }; + }; + pinctrl_aop: pinctrl@2f4824000 { compatible = "apple,t8122-pinctrl", "apple,t8103-pinctrl"; reg = <0x2 0xf4824000 0x0 0x4000>; From e2bfaf79016888a9c79b1005037c218957676250 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 9 Jul 2026 22:59:34 +0200 Subject: [PATCH 36/42] dts: apple: t603x: Add aop/admac nodes Signed-off-by: Sasha Finkelstein --- arch/arm64/boot/dts/apple/t6030-j514s.dts | 5 ++ arch/arm64/boot/dts/apple/t6030-j516s.dts | 5 ++ arch/arm64/boot/dts/apple/t6030.dtsi | 65 ++++++++++++++++++- arch/arm64/boot/dts/apple/t6031-die0.dtsi | 65 ++++++++++++++++++- arch/arm64/boot/dts/apple/t6031-j514c.dts | 5 ++ arch/arm64/boot/dts/apple/t6031-j516c.dts | 5 ++ arch/arm64/boot/dts/apple/t6034-j514m.dts | 5 ++ arch/arm64/boot/dts/apple/t6034-j516m.dts | 5 ++ .../arm64/boot/dts/apple/t603x-j514-j516.dtsi | 16 +++++ 9 files changed, 174 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6030-j514s.dts b/arch/arm64/boot/dts/apple/t6030-j514s.dts index 4ba49debe47d78..106df3e83bb908 100644 --- a/arch/arm64/boot/dts/apple/t6030-j514s.dts +++ b/arch/arm64/boot/dts/apple/t6030-j514s.dts @@ -66,6 +66,11 @@ interrupts-extended = <&pinctrl_ap 100 IRQ_TYPE_LEVEL_LOW>; }; +&aop_audio { + apple,chassis-name = "J514"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j514-macaudio", "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J514"; diff --git a/arch/arm64/boot/dts/apple/t6030-j516s.dts b/arch/arm64/boot/dts/apple/t6030-j516s.dts index c679e21ade96da..838c2003419baa 100644 --- a/arch/arm64/boot/dts/apple/t6030-j516s.dts +++ b/arch/arm64/boot/dts/apple/t6030-j516s.dts @@ -66,6 +66,11 @@ interrupts-extended = <&pinctrl_ap 100 IRQ_TYPE_LEVEL_LOW>; }; +&aop_audio { + apple,chassis-name = "J516"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j516-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J516"; diff --git a/arch/arm64/boot/dts/apple/t6030.dtsi b/arch/arm64/boot/dts/apple/t6030.dtsi index 2c2c388524317d..20a93f25b885fc 100644 --- a/arch/arm64/boot/dts/apple/t6030.dtsi +++ b/arch/arm64/boot/dts/apple/t6030.dtsi @@ -582,7 +582,7 @@ }; admac: dma-controller@28b200000 { - compatible = "apple,t6030-admac", "apple,t8103-admac"; + compatible = "apple,t6030-admac", "apple,t8122-admac"; reg = <0x2 0x8b200000 0x0 0x34000>; #dma-cells = <1>; dma-channels = <24>; @@ -932,6 +932,69 @@ ; }; + aop_mbox: mbox@374408000 { + compatible = "apple,t6030-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x74408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + aop_dart: iommu@374818000 { + compatible = "apple,t6030-dart", "apple,t8110-dart"; + reg = <0x3 0x74818000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + // apple,dma-range = <0x100 0x0 0x300 0x0>; + }; + + aop_admac: dma-controller@374980000 { + compatible = "apple,t6030-admac", "apple,t8122-admac"; + reg = <0x3 0x74980000 0x0 0x34000>; + #dma-cells = <1>; + dma-channels = <16>; + interrupts-extended = <0>, + <0>, + <&aic AIC_IRQ 460 IRQ_TYPE_LEVEL_HIGH>, + <0>; + iommus = <&aop_dart 10>; + status = "disabled"; + }; + + aop: aop@374c00000 { + compatible = "apple,t6030-aop", "apple,t6020-aop"; + reg = <0x3 0x74c00000 0x0 0x1e0000>, + <0x3 0x74400000 0x0 0x6c000>; + mboxes = <&aop_mbox>; + mbox-names = "mbox"; + iommus = <&aop_dart 0>; + + status = "disabled"; + + aop_audio: audio { + compatible = "apple,t6030-aop-audio"; + dmas = <&aop_admac 1>; + dma-names = "dma"; + }; + + aop_als: als { + compatible = "apple,t6030-aop-als", "apple,t6020-aop-als", "apple,aop-als"; + // intentionally empty + }; + + las { + compatible = "apple,t6030-aop-las", "apple,t6020-aop-las", "apple,aop-las"; + }; + }; + pinctrl_aop: pinctrl@374824000 { compatible = "apple,t6030-pinctrl", "apple,t8103-pinctrl"; reg = <0x3 0x74824000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t6031-die0.dtsi b/arch/arm64/boot/dts/apple/t6031-die0.dtsi index 9591bace819be7..0ffe0dfdd113d9 100644 --- a/arch/arm64/boot/dts/apple/t6031-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t6031-die0.dtsi @@ -161,6 +161,69 @@ ; }; + aop_mbox: mbox@2a8408000 { + compatible = "apple,t6031-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0xa8408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + aop_dart: iommu@2a8818000 { + compatible = "apple,t6031-dart", "apple,t8110-dart"; + reg = <0x2 0xa8818000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + // apple,dma-range = <0x100 0x0 0x300 0x0>; + }; + + aop_admac: dma-controller@2a8980000 { + compatible = "apple,t6031-admac", "apple,t8122-admac"; + reg = <0x2 0xa8980000 0x0 0x34000>; + #dma-cells = <1>; + dma-channels = <16>; + interrupts-extended = <0>, + <0>, + <&aic AIC_IRQ 0 630 IRQ_TYPE_LEVEL_HIGH>, + <0>; + iommus = <&aop_dart 10>; + status = "disabled"; + }; + + aop: aop@2a8c00000 { + compatible = "apple,t6031-aop", "apple,t6020-aop"; + reg = <0x2 0xa8c00000 0x0 0x2a0000>, + <0x2 0xa8400000 0x0 0x6c000>; + mboxes = <&aop_mbox>; + mbox-names = "mbox"; + iommus = <&aop_dart 0>; + + status = "disabled"; + + aop_audio: audio { + compatible = "apple,t6031-aop-audio", "apple,t6030-aop-audio"; + dmas = <&aop_admac 1>; + dma-names = "dma"; + }; + + aop_als: als { + compatible = "apple,t6031-aop-als", "apple,aop-als"; + // intentionally empty + }; + + las { + compatible = "apple,t6031-aop-las", "apple,aop-las"; + }; + }; + mtp: mtp@2ac400000 { compatible = "apple,t6031-mtp", "apple,t8122-rtk-helper-asc4", "apple,mtp", "apple,rtk-helper-asc4"; reg = <0x2 0xac400000 0x0 0x4000>, @@ -457,7 +520,7 @@ }; admac: dma-controller@393200000 { - compatible = "apple,t6031-admac", "apple,t8103-admac"; + compatible = "apple,t6031-admac", "apple,t8122-admac"; reg = <0x3 0x93200000 0x0 0x34000>; #dma-cells = <1>; dma-channels = <24>; diff --git a/arch/arm64/boot/dts/apple/t6031-j514c.dts b/arch/arm64/boot/dts/apple/t6031-j514c.dts index 1bfb9f2c63923a..ec53b88aebc985 100644 --- a/arch/arm64/boot/dts/apple/t6031-j514c.dts +++ b/arch/arm64/boot/dts/apple/t6031-j514c.dts @@ -29,6 +29,11 @@ brcm,board-type = "apple,texa"; }; +&aop_audio { + apple,chassis-name = "J514"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j514-macaudio", "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J514"; diff --git a/arch/arm64/boot/dts/apple/t6031-j516c.dts b/arch/arm64/boot/dts/apple/t6031-j516c.dts index a199b0b11814f2..9e447741cba718 100644 --- a/arch/arm64/boot/dts/apple/t6031-j516c.dts +++ b/arch/arm64/boot/dts/apple/t6031-j516c.dts @@ -29,6 +29,11 @@ brcm,board-type = "apple,jura"; }; +&aop_audio { + apple,chassis-name = "J516"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j516-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J516"; diff --git a/arch/arm64/boot/dts/apple/t6034-j514m.dts b/arch/arm64/boot/dts/apple/t6034-j514m.dts index 7ef946c9ea27cc..f9c2b6241c9551 100644 --- a/arch/arm64/boot/dts/apple/t6034-j514m.dts +++ b/arch/arm64/boot/dts/apple/t6034-j514m.dts @@ -29,6 +29,11 @@ brcm,board-type = "apple,texa"; }; +&aop_audio { + apple,chassis-name = "J514"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j514-macaudio", "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J514"; diff --git a/arch/arm64/boot/dts/apple/t6034-j516m.dts b/arch/arm64/boot/dts/apple/t6034-j516m.dts index a2b5abed07473a..22ad5251eaecef 100644 --- a/arch/arm64/boot/dts/apple/t6034-j516m.dts +++ b/arch/arm64/boot/dts/apple/t6034-j516m.dts @@ -29,6 +29,11 @@ brcm,board-type = "apple,jura"; }; +&aop_audio { + apple,chassis-name = "J516"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j516-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J516"; diff --git a/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi b/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi index f868eeb8ace8d9..0adfe50b78e788 100644 --- a/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi +++ b/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi @@ -251,6 +251,22 @@ status = "okay"; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + / { sound: sound { /* compatible is set per machine */ From 12c43f3ddf89e8387a44d51c9945d345d6257633 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 9 Jul 2026 22:10:05 +0200 Subject: [PATCH 37/42] dmaengine: apple-admac: Add M3 generation ADMACs The admacs present on t8122 and t603x SoCs need additional writes in order to operate correctly. The exact purpose of this register is unknown Signed-off-by: Sasha Finkelstein --- drivers/dma/apple-admac.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/dma/apple-admac.c b/drivers/dma/apple-admac.c index 14a5ee14a48156..75b75081bccfef 100644 --- a/drivers/dma/apple-admac.c +++ b/drivers/dma/apple-admac.c @@ -39,10 +39,14 @@ #define FLAG_DESC_NOTIFY BIT(16) +#define T603X_UNK_28_VAL 0x200000 + #define REG_TX_START 0x0000 #define REG_TX_STOP 0x0004 #define REG_RX_START 0x0008 #define REG_RX_STOP 0x000c +#define REG_UNK_28 0x0028 +#define REG_UNK_2C 0x002c #define REG_IMPRINT 0x0090 #define REG_TX_SRAM_SIZE 0x0094 #define REG_RX_SRAM_SIZE 0x0098 @@ -127,6 +131,7 @@ struct admac_data { struct mutex cache_alloc_lock; struct admac_sram txcache, rxcache; + bool set_unk28; int irq; int irq_index; int nchannels; @@ -147,6 +152,10 @@ struct admac_tx { struct list_head node; }; +struct admac_hw { + bool set_unk28; +}; + static int admac_alloc_sram_carveout(struct admac_data *ad, enum dma_transfer_direction dir, u32 *out) @@ -747,6 +756,11 @@ static int admac_device_config(struct dma_chan *chan, u32 bus_width = readl_relaxed(ad->base + REG_BUS_WIDTH(adchan->no)) & ~(BUS_WIDTH_WORD_SIZE | BUS_WIDTH_FRAME_SIZE); + if (ad->set_unk28) { + writel_relaxed(T603X_UNK_28_VAL, ad->base + REG_UNK_28); + writel_relaxed(T603X_UNK_28_VAL, ad->base + REG_UNK_2C); + } + switch (is_tx ? config->dst_addr_width : config->src_addr_width) { case DMA_SLAVE_BUSWIDTH_1_BYTE: wordsize = 1; @@ -805,6 +819,7 @@ static int admac_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; struct admac_data *ad; struct dma_device *dma; + const struct admac_hw *hw; int nchannels; int err, irq, i; @@ -813,6 +828,9 @@ static int admac_probe(struct platform_device *pdev) dev_err(&pdev->dev, "missing or invalid dma-channels property\n"); return -EINVAL; } + hw = of_device_get_match_data(&pdev->dev); + if (!hw) + return -EINVAL; ad = devm_kzalloc(&pdev->dev, struct_size(ad, channels, nchannels), GFP_KERNEL); if (!ad) @@ -821,6 +839,7 @@ static int admac_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ad); ad->dev = &pdev->dev; ad->nchannels = nchannels; + ad->set_unk28 = hw->set_unk28; mutex_init(&ad->cache_alloc_lock); /* @@ -935,9 +954,18 @@ static void admac_remove(struct platform_device *pdev) reset_control_rearm(ad->rstc); } +static const struct admac_hw admac_t8013_hw = { + .set_unk28 = false, +}; + +static const struct admac_hw admac_t8122_hw = { + .set_unk28 = true, +}; + static const struct of_device_id admac_of_match[] = { - { .compatible = "apple,t8103-admac", }, - { .compatible = "apple,admac", }, + { .compatible = "apple,t8122-admac", .data = &admac_t8122_hw }, + { .compatible = "apple,t8103-admac", .data = &admac_t8013_hw }, + { .compatible = "apple,admac", .data = &admac_t8013_hw }, { } }; MODULE_DEVICE_TABLE(of, admac_of_match); From f3cf4b1d7caf06b15aea0e99e8b52c8b43e1b292 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 9 Jul 2026 22:14:13 +0200 Subject: [PATCH 38/42] ASoC: apple: aop: Add M3 generation built-in mics Those get a new "HF" decimator, and an extra component that needs to be attached Signed-off-by: Sasha Finkelstein --- sound/soc/apple/aop_audio.rs | 388 +++++++++++++++++++++++++++++------ 1 file changed, 329 insertions(+), 59 deletions(-) diff --git a/sound/soc/apple/aop_audio.rs b/sound/soc/apple/aop_audio.rs index 3dbd48d62e7ccf..6cb974a82e7859 100644 --- a/sound/soc/apple/aop_audio.rs +++ b/sound/soc/apple/aop_audio.rs @@ -63,9 +63,13 @@ impl fmt::Debug for U32 { const EPIC_SUBTYPE_WRAPPED_CALL: u16 = 0x20; const CALLTYPE_AUDIO_ATTACH_DEVICE: u32 = 0xc3000002; const CALLTYPE_AUDIO_SET_PROP: u32 = 0xc3000005; -const PDM_NUM_COEFFS: usize = 120; +const DECIM_NUM_COEFFS: usize = 120; +const DECIM_NUM_COEFFS_HF: usize = 1023; const DECIMATION_RATIOS: [u8; 3] = [0xf, 5, 2]; -const COEFFICIENTS: [u8; PDM_NUM_COEFFS * mem::size_of::()] = [ +const DECIMATION_RATIOS_HF: [u8; 3] = [10, 5, 3]; +const DECIM_LATENCY: U32 = U32(15); +const DECIM_LATENCY_HF: u32 = 36; +const COEFFICIENTS: [u8; 400] = [ 0x88, 0x03, 0x00, 0x00, 0x82, 0x08, 0x00, 0x00, 0x51, 0x12, 0x00, 0x00, 0x0a, 0x23, 0x00, 0x00, 0xce, 0x3d, 0x00, 0x00, 0x97, 0x66, 0x00, 0x00, 0x43, 0xa2, 0x00, 0x00, 0x9c, 0xf6, 0x00, 0x00, 0x53, 0x6a, 0x01, 0x00, 0xe6, 0x04, 0x02, 0x00, 0x7e, 0xce, 0x02, 0x00, 0xae, 0xcf, 0x03, 0x00, @@ -91,13 +95,204 @@ const COEFFICIENTS: [u8; PDM_NUM_COEFFS * mem::size_of::()] = [ 0x83, 0xb6, 0xa1, 0xff, 0xe2, 0xd5, 0xef, 0xff, 0x94, 0x9b, 0x76, 0x00, 0xf3, 0xd7, 0x25, 0x00, 0xff, 0xfc, 0x67, 0xff, 0xe3, 0xac, 0xb6, 0xff, 0x52, 0x1b, 0xcc, 0x00, 0x3c, 0x8a, 0x8b, 0x00, 0x9f, 0x0c, 0xcd, 0xfe, 0x5c, 0x68, 0xcc, 0xfe, 0x4d, 0xc5, 0x98, 0x02, 0x82, 0xcf, 0xfb, 0x06, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -const FILTER_LENGTHS: u32 = 0x542c47; +const COEFFICIENTS_HF: [u8; 3088] = [ + 0x6b, 0xae, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x95, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x47, 0xf1, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x72, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x6b, 0x6f, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0x46, 0xd3, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc2, 0x82, 0xc2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbd, 0xb2, 0x9e, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x4c, 0xa8, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x99, 0x69, 0x36, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x1d, 0xa3, 0xf6, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x4c, 0x45, 0xb7, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x64, 0xec, 0x82, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x9d, 0x67, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x79, 0xd7, 0x75, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xb5, 0xbe, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x7c, 0x3e, 0x51, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9b, 0xf3, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc9, 0x1d, 0x70, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x41, 0xf0, 0x02, 0x00, 0x00, 0x00, 0x00, + 0xed, 0x80, 0x9b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x43, 0x9c, 0x45, 0x06, 0x00, 0x00, 0x00, 0x00, + 0xaa, 0x3b, 0xb3, 0x07, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xfe, 0x9d, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x76, 0xbb, 0x08, 0x00, 0x00, 0x00, 0x00, 0x48, 0xc4, 0xc6, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x8b, 0x1a, 0x8c, 0x05, 0x00, 0x00, 0x00, 0x00, 0x48, 0xe3, 0xf4, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x89, 0xfc, 0x12, 0xfd, 0xff, 0xff, 0xff, 0xff, 0x5f, 0x39, 0x29, 0xf7, 0xff, 0xff, 0xff, 0xff, + 0x6d, 0x6a, 0xaf, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x64, 0x7b, 0x50, 0xea, 0xff, 0xff, 0xff, 0xff, + 0xef, 0xc6, 0xe1, 0xe4, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x8c, 0x53, 0xe1, 0xff, 0xff, 0xff, 0xff, + 0x0d, 0x64, 0x9b, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x09, 0x6d, 0x9a, 0xe3, 0xff, 0xff, 0xff, 0xff, + 0x19, 0xe0, 0x01, 0xeb, 0xff, 0xff, 0xff, 0xff, 0x17, 0x15, 0x39, 0xf7, 0xff, 0xff, 0xff, 0xff, + 0x19, 0x59, 0x48, 0x08, 0x00, 0x00, 0x00, 0x00, 0xe2, 0xa1, 0xcb, 0x1d, 0x00, 0x00, 0x00, 0x00, + 0x64, 0x81, 0xef, 0x36, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xa9, 0x79, 0x52, 0x00, 0x00, 0x00, 0x00, + 0x7f, 0x02, 0xdd, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x4a, 0xdf, 0x57, 0x8a, 0x00, 0x00, 0x00, 0x00, + 0xfb, 0x9f, 0x19, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x47, 0xeb, 0x6b, 0xb7, 0x00, 0x00, 0x00, 0x00, + 0x9d, 0x23, 0xdb, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x9d, 0x59, 0xcd, 0x00, 0x00, 0x00, 0x00, + 0x6f, 0x9d, 0x59, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x9d, 0x23, 0xdb, 0xc5, 0x00, 0x00, 0x00, 0x00, + 0x47, 0xeb, 0x6b, 0xb7, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x9f, 0x19, 0xa3, 0x00, 0x00, 0x00, 0x00, + 0x4a, 0xdf, 0x57, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x02, 0xdd, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0xe3, 0xa9, 0x79, 0x52, 0x00, 0x00, 0x00, 0x00, 0x64, 0x81, 0xef, 0x36, 0x00, 0x00, 0x00, 0x00, + 0xe2, 0xa1, 0xcb, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x19, 0x59, 0x48, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x17, 0x15, 0x39, 0xf7, 0xff, 0xff, 0xff, 0xff, 0x19, 0xe0, 0x01, 0xeb, 0xff, 0xff, 0xff, 0xff, + 0x09, 0x6d, 0x9a, 0xe3, 0xff, 0xff, 0xff, 0xff, 0x0d, 0x64, 0x9b, 0xe0, 0xff, 0xff, 0xff, 0xff, + 0xfb, 0x8c, 0x53, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xef, 0xc6, 0xe1, 0xe4, 0xff, 0xff, 0xff, 0xff, + 0x64, 0x7b, 0x50, 0xea, 0xff, 0xff, 0xff, 0xff, 0x6d, 0x6a, 0xaf, 0xf0, 0xff, 0xff, 0xff, 0xff, + 0x5f, 0x39, 0x29, 0xf7, 0xff, 0xff, 0xff, 0xff, 0x89, 0xfc, 0x12, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0x48, 0xe3, 0xf4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x1a, 0x8c, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x48, 0xc4, 0xc6, 0x07, 0x00, 0x00, 0x00, 0x00, 0x18, 0x76, 0xbb, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x4e, 0xfe, 0x9d, 0x08, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x3b, 0xb3, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x43, 0x9c, 0x45, 0x06, 0x00, 0x00, 0x00, 0x00, 0xed, 0x80, 0x9b, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x1e, 0x41, 0xf0, 0x02, 0x00, 0x00, 0x00, 0x00, 0xc9, 0x1d, 0x70, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x9b, 0xf3, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x3e, 0x51, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0xb5, 0xbe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x79, 0xd7, 0x75, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0x9d, 0x67, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x64, 0xec, 0x82, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x4c, 0x45, 0xb7, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x1d, 0xa3, 0xf6, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x99, 0x69, 0x36, 0xff, 0xff, 0xff, 0xff, 0xff, 0x4c, 0xa8, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xbd, 0xb2, 0x9e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x82, 0xc2, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x46, 0xd3, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6b, 0x6f, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x72, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0x47, 0xf1, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x95, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6b, 0xae, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x8f, 0xc2, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x98, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9f, 0x52, 0xe2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x35, 0x21, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x25, 0xe4, 0x97, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcd, 0xec, 0x7b, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x6b, 0x33, 0x94, 0xff, 0xff, 0xff, 0xff, 0xff, 0x4c, 0xd8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9a, 0x64, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x75, 0x22, 0x92, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x84, 0x8e, 0x2e, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x9d, 0x36, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x66, 0xc2, 0x04, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x3c, 0x0b, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x18, 0xf7, 0xe4, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x0e, 0x2e, 0xf6, 0xff, 0xff, 0xff, 0xff, + 0x72, 0xea, 0xf7, 0xef, 0xff, 0xff, 0xff, 0xff, 0x41, 0xcb, 0x2e, 0xed, 0xff, 0xff, 0xff, 0xff, + 0x17, 0x9c, 0xae, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xb1, 0xb5, 0xf1, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0xe1, 0x3a, 0xca, 0x0d, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xf4, 0xbe, 0x21, 0x00, 0x00, 0x00, 0x00, + 0x7e, 0x72, 0x94, 0x30, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x8a, 0x37, 0x32, 0x00, 0x00, 0x00, 0x00, + 0x75, 0x57, 0xb8, 0x20, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x52, 0x79, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0x8e, 0x8f, 0x60, 0xc9, 0xff, 0xff, 0xff, 0xff, 0x32, 0x60, 0xeb, 0x98, 0xff, 0xff, 0xff, 0xff, + 0x65, 0x2d, 0x8f, 0x7d, 0xff, 0xff, 0xff, 0xff, 0x67, 0xdd, 0xb5, 0x8a, 0xff, 0xff, 0xff, 0xff, + 0x26, 0x98, 0x96, 0xcd, 0xff, 0xff, 0xff, 0xff, 0x1d, 0xe5, 0xcf, 0x47, 0x00, 0x00, 0x00, 0x00, + 0xe1, 0x3c, 0xb2, 0xec, 0x00, 0x00, 0x00, 0x00, 0x66, 0x20, 0x81, 0xa2, 0x01, 0x00, 0x00, 0x00, + 0x2b, 0xb4, 0xbe, 0x47, 0x02, 0x00, 0x00, 0x00, 0x99, 0x16, 0x3e, 0xbb, 0x02, 0x00, 0x00, 0x00, + 0xd9, 0x88, 0xb5, 0xe4, 0x02, 0x00, 0x00, 0x00, 0x99, 0x16, 0x3e, 0xbb, 0x02, 0x00, 0x00, 0x00, + 0x2b, 0xb4, 0xbe, 0x47, 0x02, 0x00, 0x00, 0x00, 0x66, 0x20, 0x81, 0xa2, 0x01, 0x00, 0x00, 0x00, + 0xe1, 0x3c, 0xb2, 0xec, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xe5, 0xcf, 0x47, 0x00, 0x00, 0x00, 0x00, + 0x26, 0x98, 0x96, 0xcd, 0xff, 0xff, 0xff, 0xff, 0x67, 0xdd, 0xb5, 0x8a, 0xff, 0xff, 0xff, 0xff, + 0x65, 0x2d, 0x8f, 0x7d, 0xff, 0xff, 0xff, 0xff, 0x32, 0x60, 0xeb, 0x98, 0xff, 0xff, 0xff, 0xff, + 0x8e, 0x8f, 0x60, 0xc9, 0xff, 0xff, 0xff, 0xff, 0xd1, 0x52, 0x79, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0x75, 0x57, 0xb8, 0x20, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x8a, 0x37, 0x32, 0x00, 0x00, 0x00, 0x00, + 0x7e, 0x72, 0x94, 0x30, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xf4, 0xbe, 0x21, 0x00, 0x00, 0x00, 0x00, + 0xe1, 0x3a, 0xca, 0x0d, 0x00, 0x00, 0x00, 0x00, 0xb1, 0xb5, 0xf1, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0x17, 0x9c, 0xae, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x41, 0xcb, 0x2e, 0xed, 0xff, 0xff, 0xff, 0xff, + 0x72, 0xea, 0xf7, 0xef, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x0e, 0x2e, 0xf6, 0xff, 0xff, 0xff, 0xff, + 0x18, 0xf7, 0xe4, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x3c, 0x0b, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x66, 0xc2, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x9d, 0x36, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x84, 0x8e, 0x2e, 0x04, 0x00, 0x00, 0x00, 0x00, 0x75, 0x22, 0x92, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x9a, 0x64, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xd8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6b, 0x33, 0x94, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcd, 0xec, 0x7b, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x25, 0xe4, 0x97, 0xff, 0xff, 0xff, 0xff, 0xff, 0x35, 0x21, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9f, 0x52, 0xe2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x98, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x8f, 0xc2, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x79, 0x48, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x6e, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xb5, 0xec, 0xf4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbb, 0xbd, 0xf2, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9a, 0xd2, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x86, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x31, 0xdf, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xed, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe0, 0x97, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xa4, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc4, 0x9b, 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xf6, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0xad, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0x77, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf9, 0x42, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xc9, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfc, 0xbf, 0xf2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x94, 0x67, 0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x91, 0x9d, 0xd7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbe, 0x71, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf5, 0xde, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0xf3, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0xa5, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0xd1, 0xdc, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xaf, 0x6c, 0xa9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa8, 0x0e, 0xb7, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x65, 0x2b, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xed, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x92, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x3f, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd8, 0x9f, 0xa9, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3d, 0x9f, 0x5c, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x87, 0xfc, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x99, 0xe7, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8a, 0x5a, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x57, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xcd, 0x5a, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xd4, 0x43, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe0, 0x19, 0xe9, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x68, 0xa7, 0x71, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x73, 0xe3, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x3a, 0x57, 0x01, 0x00, 0x00, 0x00, 0x00, + 0xba, 0xa4, 0x19, 0x01, 0x00, 0x00, 0x00, 0x00, 0x96, 0x6b, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x35, 0x3d, 0x93, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x8d, 0xf7, 0x51, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x6e, 0x64, 0x7a, 0xff, 0xff, 0xff, 0xff, 0xff, 0x41, 0x7b, 0x3f, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x27, 0xc5, 0x2f, 0x02, 0x00, 0x00, 0x00, 0x00, 0xb8, 0xbe, 0x5e, 0x01, 0x00, 0x00, 0x00, 0x00, + 0xc2, 0x04, 0x44, 0xff, 0xff, 0xff, 0xff, 0xff, 0x62, 0xfc, 0x83, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0xbd, 0x4e, 0xac, 0xfd, 0xff, 0xff, 0xff, 0xff, 0x5f, 0x43, 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe9, 0x98, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xdc, 0x3e, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x68, 0xbb, 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x25, 0x1c, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x67, 0x17, 0x11, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x11, 0xe7, 0x25, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0xc8, 0xb5, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00, 0xed, 0xa6, 0x2c, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x8a, 0xb3, 0x5f, 0x04, 0x00, 0x00, 0x00, 0x00, 0x79, 0x42, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, 0x00, 0x3c, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3e, 0x3b, 0x50, 0xfa, 0xff, 0xff, 0xff, 0xff, + 0x0e, 0x98, 0x07, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xb5, 0xe7, 0x8e, 0x02, 0x00, 0x00, 0x00, 0x00, + 0xcc, 0xd9, 0x7b, 0x06, 0x00, 0x00, 0x00, 0x00, 0x35, 0xf4, 0x4c, 0x05, 0x00, 0x00, 0x00, 0x00, + 0xfd, 0x2b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x44, 0xf5, 0x8a, 0xf9, 0xff, 0xff, 0xff, 0xff, + 0x55, 0x2b, 0x7b, 0xf8, 0xff, 0xff, 0xff, 0xff, 0x21, 0x8f, 0xb0, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0x4b, 0x6c, 0x5a, 0x05, 0x00, 0x00, 0x00, 0x00, 0x36, 0x50, 0x35, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xd8, 0xa1, 0x05, 0x00, 0x00, 0x00, 0x00, 0xd5, 0x27, 0xf7, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xac, 0x6c, 0x13, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xdd, 0xf2, 0xf6, 0xff, 0xff, 0xff, 0xff, + 0x99, 0x6d, 0x89, 0xff, 0xff, 0xff, 0xff, 0xff, 0x68, 0x1a, 0x44, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x7e, 0x06, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0xe4, 0x04, 0x00, 0x00, 0x00, 0x00, + 0xd8, 0x30, 0x0a, 0xf9, 0xff, 0xff, 0xff, 0xff, 0x43, 0xe2, 0x0e, 0xf2, 0xff, 0xff, 0xff, 0xff, + 0x75, 0x3f, 0x39, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xab, 0xf0, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xe6, 0x34, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xd6, 0x71, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x52, 0xee, 0x96, 0x02, 0x00, 0x00, 0x00, 0x00, 0x51, 0x29, 0xa6, 0xf3, 0xff, 0xff, 0xff, 0xff, + 0x09, 0x4e, 0xe9, 0xed, 0xff, 0xff, 0xff, 0xff, 0x06, 0xef, 0xe0, 0xf6, 0xff, 0xff, 0xff, 0xff, + 0x88, 0x68, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x30, 0xdf, 0x13, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0xa3, 0xdb, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xde, 0xdb, 0x51, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x93, 0xba, 0xef, 0xec, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xb4, 0x38, 0xea, 0xff, 0xff, 0xff, 0xff, + 0xcd, 0x51, 0x75, 0xf9, 0xff, 0xff, 0xff, 0xff, 0x00, 0xbf, 0x2f, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x05, 0xc2, 0x19, 0x00, 0x00, 0x00, 0x00, 0x20, 0xed, 0xa0, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x3a, 0x63, 0xd8, 0xf7, 0xff, 0xff, 0xff, 0xff, 0x4b, 0xe2, 0x44, 0xe5, 0xff, 0xff, 0xff, 0xff, + 0x56, 0x89, 0xa8, 0xe7, 0xff, 0xff, 0xff, 0xff, 0x0e, 0x62, 0x60, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x85, 0x0c, 0xe2, 0x17, 0x00, 0x00, 0x00, 0x00, 0x91, 0x22, 0x3a, 0x1f, 0x00, 0x00, 0x00, 0x00, + 0xe7, 0x04, 0x34, 0x0d, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xac, 0x28, 0xef, 0xff, 0xff, 0xff, 0xff, + 0x2e, 0xba, 0x30, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x4c, 0x07, 0xdc, 0xe6, 0xff, 0xff, 0xff, 0xff, + 0x6d, 0x3e, 0xd5, 0x05, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x0f, 0xd5, 0x21, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0x57, 0x9f, 0x23, 0x00, 0x00, 0x00, 0x00, 0xae, 0x0a, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x58, 0xe7, 0x7c, 0xe4, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x39, 0x50, 0xd5, 0xff, 0xff, 0xff, 0xff, + 0xb2, 0x32, 0x4d, 0xe8, 0xff, 0xff, 0xff, 0xff, 0x2c, 0x38, 0xc6, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x50, 0xcc, 0x83, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x69, 0xb0, 0x69, 0x26, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x91, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x66, 0x3b, 0xd8, 0xff, 0xff, 0xff, 0xff, + 0x7d, 0xf0, 0x2a, 0xce, 0xff, 0xff, 0xff, 0xff, 0xb2, 0x0b, 0x2f, 0xec, 0xff, 0xff, 0xff, 0xff, + 0x2d, 0xad, 0xea, 0x1b, 0x00, 0x00, 0x00, 0x00, 0xee, 0xb9, 0x70, 0x37, 0x00, 0x00, 0x00, 0x00, + 0x74, 0xdc, 0x58, 0x27, 0x00, 0x00, 0x00, 0x00, 0xc5, 0xc2, 0x7c, 0xf6, 0xff, 0xff, 0xff, 0xff, + 0x0b, 0x05, 0xd6, 0xca, 0xff, 0xff, 0xff, 0xff, 0xc6, 0x46, 0x04, 0xc8, 0xff, 0xff, 0xff, 0xff, + 0x7e, 0x32, 0x57, 0xf2, 0xff, 0xff, 0xff, 0xff, 0x29, 0xa3, 0xd4, 0x29, 0x00, 0x00, 0x00, 0x00, + 0x62, 0x4d, 0x57, 0x42, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x2a, 0x9a, 0x26, 0x00, 0x00, 0x00, 0x00, + 0x9a, 0x8d, 0x6a, 0xea, 0xff, 0xff, 0xff, 0xff, 0x1c, 0xe2, 0x9c, 0xbc, 0xff, 0xff, 0xff, 0xff, + 0xb5, 0x9c, 0xa8, 0xc2, 0xff, 0xff, 0xff, 0xff, 0x04, 0x55, 0x2d, 0xfa, 0xff, 0xff, 0xff, 0xff, + 0xd5, 0x68, 0x10, 0x39, 0x00, 0x00, 0x00, 0x00, 0x17, 0xa8, 0x64, 0x4d, 0x00, 0x00, 0x00, 0x00, + 0xc2, 0xca, 0xef, 0x24, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xab, 0x16, 0xdd, 0xff, 0xff, 0xff, 0xff, + 0xec, 0x0e, 0x8c, 0xad, 0xff, 0xff, 0xff, 0xff, 0x63, 0x20, 0x34, 0xbd, 0xff, 0xff, 0xff, 0xff, + 0x62, 0xc0, 0x93, 0x02, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x38, 0x40, 0x49, 0x00, 0x00, 0x00, 0x00, + 0xc2, 0x28, 0x75, 0x59, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x57, 0xe8, 0x23, 0x00, 0x00, 0x00, 0x00, + 0x82, 0xda, 0x84, 0xcf, 0xff, 0xff, 0xff, 0xff, 0x2f, 0xeb, 0x20, 0x9d, 0xff, 0xff, 0xff, 0xff, + 0xb9, 0x6b, 0xc7, 0xb5, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x48, 0xa8, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x9c, 0x37, 0x15, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x74, 0x23, 0x55, 0x68, 0x00, 0x00, 0x00, 0x00, + 0x8b, 0xd6, 0x4a, 0x26, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe6, 0x59, 0xc3, 0xff, 0xff, 0xff, 0xff, + 0x3d, 0x3f, 0x56, 0x8a, 0xff, 0xff, 0xff, 0xff, 0xa4, 0xcf, 0x12, 0xa9, 0xff, 0xff, 0xff, 0xff, + 0x3a, 0xc5, 0x14, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xae, 0x04, 0xd9, 0x6a, 0x00, 0x00, 0x00, 0x00, + 0x27, 0xe7, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x83, 0x35, 0xfc, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x7c, 0xbf, 0xfb, 0xbb, 0xff, 0xff, 0xff, 0xff, 0x63, 0xba, 0x2d, 0x74, 0xff, 0xff, 0xff, 0xff, + 0x97, 0xe2, 0xab, 0x91, 0xff, 0xff, 0xff, 0xff, 0x57, 0xa5, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xf0, 0x0b, 0xae, 0x78, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x97, 0xf1, 0x9a, 0x00, 0x00, 0x00, 0x00, + 0xc8, 0x79, 0xb8, 0x4c, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x9a, 0xb7, 0xc1, 0xff, 0xff, 0xff, 0xff, + 0xe3, 0xf0, 0x39, 0x5c, 0xff, 0xff, 0xff, 0xff, 0xcb, 0x28, 0xc4, 0x67, 0xff, 0xff, 0xff, 0xff, + 0x79, 0xe2, 0xe5, 0xe1, 0xff, 0xff, 0xff, 0xff, 0x8c, 0x7a, 0x04, 0x79, 0x00, 0x00, 0x00, 0x00, + 0x84, 0xdc, 0x42, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x87, 0xa2, 0x1c, 0x88, 0x00, 0x00, 0x00, 0x00, + 0x26, 0x4e, 0xaa, 0xe9, 0xff, 0xff, 0xff, 0xff, 0x45, 0x8e, 0x53, 0x50, 0xff, 0xff, 0xff, 0xff, + 0xad, 0x81, 0xc2, 0x25, 0xff, 0xff, 0xff, 0xff, 0x16, 0x3c, 0x3e, 0x8d, 0xff, 0xff, 0xff, 0xff, + 0x5f, 0xde, 0x95, 0x49, 0x00, 0x00, 0x00, 0x00, 0x91, 0xc6, 0x7f, 0xe2, 0x00, 0x00, 0x00, 0x00, + 0xe8, 0x96, 0x73, 0xf1, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x44, 0x66, 0x64, 0x00, 0x00, 0x00, 0x00, + 0xb6, 0xf3, 0xb5, 0x8b, 0xff, 0xff, 0xff, 0xff, 0x2e, 0xbd, 0x92, 0xea, 0xfe, 0xff, 0xff, 0xff, + 0x06, 0x5a, 0xb8, 0xe7, 0xfe, 0xff, 0xff, 0xff, 0x2d, 0xca, 0x07, 0x8f, 0xff, 0xff, 0xff, 0xff, + 0xca, 0x3a, 0xeb, 0x88, 0x00, 0x00, 0x00, 0x00, 0xab, 0xac, 0x3a, 0x4b, 0x01, 0x00, 0x00, 0x00, + 0x4f, 0x55, 0xde, 0x65, 0x01, 0x00, 0x00, 0x00, 0xa5, 0x44, 0x63, 0xbf, 0x00, 0x00, 0x00, 0x00, + 0xc6, 0x47, 0x94, 0xa1, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x5c, 0xad, 0x92, 0xfe, 0xff, 0xff, 0xff, + 0x62, 0x2e, 0x32, 0x13, 0xfe, 0xff, 0xff, 0xff, 0xe9, 0xae, 0xe5, 0x63, 0xfe, 0xff, 0xff, 0xff, + 0xdd, 0x67, 0xd8, 0x6e, 0xff, 0xff, 0xff, 0xff, 0xbb, 0xab, 0x7c, 0xd9, 0x00, 0x00, 0x00, 0x00, + 0x69, 0x27, 0x26, 0x31, 0x02, 0x00, 0x00, 0x00, 0x43, 0x5c, 0xb1, 0x19, 0x03, 0x00, 0x00, 0x00, + 0x49, 0xaf, 0xf9, 0x69, 0x03, 0x00, 0x00, 0x00, 0x2d, 0x04, 0x67, 0x2e, 0x03, 0x00, 0x00, 0x00, + 0xee, 0x25, 0x09, 0x97, 0x02, 0x00, 0x00, 0x00, 0xea, 0x83, 0xfb, 0xdd, 0x01, 0x00, 0x00, 0x00, + 0xda, 0x7a, 0x51, 0x32, 0x01, 0x00, 0x00, 0x00, 0x1f, 0xcb, 0x0c, 0xae, 0x00, 0x00, 0x00, 0x00, + 0xba, 0xe8, 0xe9, 0x56, 0x00, 0x00, 0x00, 0x00, 0xb6, 0xe4, 0x85, 0x25, 0x00, 0x00, 0x00, 0x00, + 0xbd, 0xc0, 0x9e, 0x0d, 0x00, 0x00, 0x00, 0x00, 0xf7, 0x2a, 0xf5, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xb1, 0xdb, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x5f, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, +]; +const FILTER_LENGTHS: U32 = U32(0x542c47); +const FILTER_LENGTHS_HF: u32 = 0xd94960; const AUDIO_DEV_PDM0: u32 = from_fourcc(b"pdm0"); const AUDIO_DEV_LPAI: u32 = from_fourcc(b"lpai"); const AUDIO_DEV_HPAI: u32 = from_fourcc(b"hpai"); @@ -134,9 +329,9 @@ impl AudioAttachDevice { #[repr(C, packed(4))] #[derive(Clone, Copy, Default)] struct LpaiChannelConfig { - unk1: u32, - unk2: u32, - unk3: u32, + channels_enabled: u32, + channels_supported: u32, + voice_trigger_channel: u32, unk4: u32, } @@ -150,21 +345,15 @@ struct PDMConfig { slow_clock_speed: u32, fast_clock_speed: u32, channel_polarity_select: u32, - channel_phase_select: u32, - unk1: u32, - unk2: u16, - ratio1: u8, - ratio2: u8, - ratio3: u8, - _pad0: u8, - filter_lengths: U32, - coeff_bulk: U32, - coeffs: [u8; PDM_NUM_COEFFS * mem::size_of::()], - unk3: U32, + unk1: u8, + channel_phase_select: U32, + unk2: u8, + decim: DecimatorConfig, + mic_turn_on_time_valid: U32, mic_turn_on_time_ms: U32, _zero0: [u8; 8], _zero1: [u8; 8], - unk4: U32, + mic_settle_time_valid: U32, mic_settle_time_ms: U32, _zero2: [u8; 69], // ????? _pad_extra: u8, // extra padding to increase the struct size to multiple of mem::size_of::() @@ -172,13 +361,32 @@ struct PDMConfig { // PDMConfig is intended to use `#[repr(C, packed)]` but this // conflicts ith pin_init. Instead just ensure that it has the same size as if // it where packed. -static_assert!(mem::size_of::() == 36 + 14 + (120 * 4) + 32 + 69 + 1); +static_assert!(mem::size_of::() == 34 + mem::size_of::() + 32 + 69 + 1); unsafe impl Zeroable for PDMConfig {} #[repr(C, packed(4))] #[derive(Debug, Copy, Clone)] struct DecimatorConfig { + latency: U32, + ratio1: u8, + ratio2: u8, + ratio3: u8, + _pad0: u8, + filter_lengths: U32, + coeff_bulk: U32, + coeffs: [u8; DECIM_NUM_COEFFS * mem::size_of::()], +} +// DecimatorConfig is intended to use `#[repr(C, packed)]` but this +// conflicts ith pin_init. Instead just ensure that it has the same size as if +// it where packed. +static_assert!(mem::size_of::() == 16 + (DECIM_NUM_COEFFS * 4)); + +unsafe impl Zeroable for DecimatorConfig {} + +#[repr(C, packed(4))] +#[derive(Debug, Copy, Clone)] +struct DecimatorConfigHf { latency: u32, ratio1: u8, ratio2: u8, @@ -186,14 +394,14 @@ struct DecimatorConfig { _pad0: u8, filter_lengths: u32, coeff_bulk: u32, - coeffs: [u8; PDM_NUM_COEFFS * mem::size_of::()], + coeffs: [u8; DECIM_NUM_COEFFS_HF * mem::size_of::()], } -// DecimatorConfig is intended to use `#[repr(C, packed)]` but this +// DecimatorConfigHf is intended to use `#[repr(C, packed)]` but this // conflicts ith pin_init. Instead just ensure that it has the same size as if // it where packed. -static_assert!(mem::size_of::() == 16 + (120 * 4)); +static_assert!(mem::size_of::() == 16 + (DECIM_NUM_COEFFS_HF * 8)); -unsafe impl Zeroable for DecimatorConfig {} +unsafe impl Zeroable for DecimatorConfigHf {} #[repr(C, packed(4))] #[derive(Clone, Copy, Default, Debug)] @@ -291,12 +499,18 @@ impl AudioSetDeviceProp { } } +struct SndSocAopHwConfig { + bytes_per_sample: u32, + hf_decimator: bool, +} + struct SndSocAopData { dev: ARef, adata: Arc, service: EPICService, pstate_cookie: Atomic, fwnode: ARef, + hwdata: &'static SndSocAopHwConfig, } impl SndSocAopData { @@ -305,6 +519,7 @@ impl SndSocAopData { adata: Arc, service: EPICService, fwnode: ARef, + hwdata: &'static SndSocAopHwConfig, ) -> Result> { Ok(Arc::new( SndSocAopData { @@ -312,34 +527,43 @@ impl SndSocAopData { adata, service, fwnode, + hwdata, pstate_cookie: Atomic::new(1), }, GFP_KERNEL, )?) } + fn build_decimator_config(&self, decim: &mut DecimatorConfig) { + decim.latency = DECIM_LATENCY; + decim.ratio1 = DECIMATION_RATIOS[0]; + decim.ratio2 = DECIMATION_RATIOS[1]; + decim.ratio3 = DECIMATION_RATIOS[2]; + decim.filter_lengths = FILTER_LENGTHS; + decim.coeff_bulk = U32(DECIM_NUM_COEFFS as u32); + decim.coeffs[..COEFFICIENTS.len()].copy_from_slice(&COEFFICIENTS); + } fn set_pdm_config(&self) -> Result<()> { let pdm_cfg = init!(PDMConfig { - bytes_per_sample: 2, - clock_source: 0x706c6c20, // 'pll ' + bytes_per_sample: self.hwdata.bytes_per_sample, + clock_source: from_fourcc(b"pll "), pdm_frequency: 2400000, pdmc_frequency: 24000000, slow_clock_speed: 24000000, fast_clock_speed: 24000000, channel_polarity_select: 256, - channel_phase_select: 0, - unk1: 0xf7600, + channel_phase_select: U32(0), + unk1: 0, unk2: 0, - ratio1: DECIMATION_RATIOS[0], - ratio2: DECIMATION_RATIOS[1], - ratio3: DECIMATION_RATIOS[2], - filter_lengths: U32(FILTER_LENGTHS), - coeff_bulk: U32(PDM_NUM_COEFFS as u32), - coeffs: COEFFICIENTS, - unk3: U32(1), + mic_turn_on_time_valid: U32(1), mic_turn_on_time_ms: U32(20), - unk4: U32(1), + mic_settle_time_valid: U32(1), mic_settle_time_ms: U32(50), ..Zeroable::init_zeroed() + }).chain(|raw| { + if !self.hwdata.hf_decimator { + self.build_decimator_config(&mut raw.decim); + } + Ok(()) }); let set_prop = AudioSetDeviceProp::::try_init(AUDIO_DEV_PDM0, 200, pdm_cfg); let msg = KBox::try_init(set_prop, GFP_KERNEL)?; @@ -352,15 +576,9 @@ impl SndSocAopData { } } fn set_decimator_config(&self) -> Result<()> { - let pdm_cfg = init!(DecimatorConfig { - latency: 15, - ratio1: DECIMATION_RATIOS[0], - ratio2: DECIMATION_RATIOS[1], - ratio3: DECIMATION_RATIOS[2], - filter_lengths: FILTER_LENGTHS, - coeff_bulk: PDM_NUM_COEFFS as u32, - coeffs: COEFFICIENTS, - ..Zeroable::init_zeroed() + let pdm_cfg = DecimatorConfig::init_zeroed().chain(|raw| { + self.build_decimator_config(raw); + Ok(()) }); let set_prop = AudioSetDeviceProp::::try_init(AUDIO_DEV_PDM0, 210, pdm_cfg); @@ -377,11 +595,39 @@ impl SndSocAopData { Ok(()) } } + fn set_decimator_config_hf(&self) -> Result<()> { + let pdm_cfg = init!(DecimatorConfigHf { + latency: DECIM_LATENCY_HF, + ratio1: DECIMATION_RATIOS_HF[0], + ratio2: DECIMATION_RATIOS_HF[1], + ratio3: DECIMATION_RATIOS_HF[2], + filter_lengths: FILTER_LENGTHS_HF, + coeff_bulk: DECIM_NUM_COEFFS_HF as u32, + ..Zeroable::init_zeroed() + }).chain(|raw| { + raw.coeffs[..COEFFICIENTS_HF.len()].copy_from_slice(&COEFFICIENTS_HF); + Ok(()) + }); + let set_prop = + AudioSetDeviceProp::::try_init(AUDIO_DEV_PDM0, 212, pdm_cfg); + let msg = KBox::try_init(set_prop, GFP_KERNEL)?; + let ret = self.epic_wrapped_call(msg.as_ref())?; + if ret != 0 { + dev_err!( + self.dev, + "Unable to set high frequency decimator config, return code {}", + ret + ); + return Err(EIO); + } else { + Ok(()) + } + } fn set_lpai_channel_cfg(&self) -> Result<()> { let cfg = LpaiChannelConfig { - unk1: 7, - unk2: 7, - unk3: 1, + channels_enabled: 7, + channels_supported: 7, + voice_trigger_channel: 1, unk4: 7, }; let msg = AudioSetDeviceProp::new(AUDIO_DEV_LPAI, 301, cfg); @@ -693,19 +939,36 @@ impl Drop for SndSocAopDriver { unsafe impl Send for SndSocAopDriver {} unsafe impl Sync for SndSocAopDriver {} +const HW_CFG_T8013: SndSocAopHwConfig = SndSocAopHwConfig { + bytes_per_sample: 2, + hf_decimator: false, +}; + +const HW_CFG_T6030: SndSocAopHwConfig = SndSocAopHwConfig { + bytes_per_sample: 3, + hf_decimator: true, +}; + kernel::of_device_table!( OF_TABLE, MODULE_OF_TABLE, - (), - [(of::DeviceId::new(c_str!("apple,aop-audio")), ())] + ::IdInfo, + [ + (of::DeviceId::new(c_str!("apple,aop-audio")), &HW_CFG_T8013), + (of::DeviceId::new(c_str!("apple,t6030-aop-audio")), &HW_CFG_T6030), + ] ); impl platform::Driver for SndSocAopDriver { - type IdInfo = (); + type IdInfo = &'static SndSocAopHwConfig; - const OF_ID_TABLE: Option> = Some(&OF_TABLE); + const OF_ID_TABLE: Option> = Some(&OF_TABLE); - fn probe(pdev: &platform::Device, _info: Option<&()>) -> impl PinInit { + fn probe( + pdev: &platform::Device, + info: Option<&Self::IdInfo> + ) -> impl PinInit { + let info = info.ok_or(ENODEV)?; let dev = ARef::::from(pdev.as_ref()); let parent = pdev.as_ref().parent().unwrap(); // SAFETY: our parent is AOP, and AopDriver is repr(transparent) for Arc @@ -721,13 +984,20 @@ impl platform::Driver for SndSocAopDriver { if !audio && parent_fwnode.property_present(c_str!("apple,no-beamforming")) { return Err(ENODEV); } - let data = SndSocAopData::new(dev, adata, svc, fwnode)?; + let data = SndSocAopData::new(dev, adata, svc, fwnode, info)?; for dev in [AUDIO_DEV_PDM0, AUDIO_DEV_HPAI, AUDIO_DEV_LPAI] { data.audio_attach_device(dev)?; } + if info.hf_decimator { + data.audio_attach_device(from_fourcc(b"lilb"))?; + } data.set_lpai_channel_cfg()?; data.set_pdm_config()?; - data.set_decimator_config()?; + if info.hf_decimator { + data.set_decimator_config_hf()?; + } else { + data.set_decimator_config()?; + } Ok(Self::new(data)?) } } From 7ef0f7cc25647ba3a40cdad7ca5fac3758b6b329 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Aug 2026 10:51:35 +0200 Subject: [PATCH 39/42] fixup! dt-bindings: dma: apple,admac: Add M3 generation ADMACs Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/dma/apple,admac.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/dma/apple,admac.yaml b/Documentation/devicetree/bindings/dma/apple,admac.yaml index 560fd19a4c6f1f..5b4fd8348f99fc 100644 --- a/Documentation/devicetree/bindings/dma/apple,admac.yaml +++ b/Documentation/devicetree/bindings/dma/apple,admac.yaml @@ -24,7 +24,6 @@ properties: compatible: oneOf: - items: - - const: apple,t8122-admac - const: apple,t6020-admac - const: apple,t8103-admac - items: @@ -39,6 +38,7 @@ properties: - apple,t6030-admac - apple,t6031-admac - const: apple,t8122-admac + - const: apple,t8122-admac reg: maxItems: 1 From a79dc5bca4ad8c6ef3bd22a0653cd15291cfccd2 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Tue, 14 Jul 2026 10:52:30 +0200 Subject: [PATCH 40/42] media: apple: isp: add t6030 support Signed-off-by: Sasha Finkelstein --- drivers/media/platform/apple/isp/isp-drv.c | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index c8169a4f99125e..a2e6b71ebc14ba 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -541,6 +541,29 @@ static const struct apple_isp_hw apple_isp_hw_t6020 = { .meta_size = ISP_META_SIZE_T8112, }; +static const struct apple_isp_hw apple_isp_hw_t6030 = { + .gen = ISP_GEN_T6031, + .pmu_base = 0x350704000, + + .dsid_count = 1, + .dsid_clr_base0 = 0x200f14000, + .dsid_clr_range0 = 0x1000, + + .clock_scratch = 0x3503d0920, + .clock_base = 0x0, + .clock_bit = 0x0, + .clock_size = 0x8, + .bandwidth_scratch = 0x3503d0980, + .bandwidth_base = 0x0, + .bandwidth_bit = 0x0, + .bandwidth_size = 0x8, + .mbox_irq_enable = ISP_MBOX_IRQ_ENABLE_T6031, + + .scl1 = true, + .lpdp = false, + .meta_size = ISP_META_SIZE_T6031, +}; + static const struct apple_isp_hw apple_isp_hw_t6031 = { .gen = ISP_GEN_T6031, .pmu_base = 0x292284008, @@ -562,6 +585,7 @@ static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t8112-isp", .data = &apple_isp_hw_t8112 }, { .compatible = "apple,t6000-isp", .data = &apple_isp_hw_t6000 }, { .compatible = "apple,t6020-isp", .data = &apple_isp_hw_t6020 }, + { .compatible = "apple,t6030-isp", .data = &apple_isp_hw_t6030 }, { .compatible = "apple,t6031-isp", .data = &apple_isp_hw_t6031 }, {}, }; From ac8d3c4ebe3a2d14d03afddd7dedf939296dfbc7 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Fri, 17 Jul 2026 12:40:39 +0200 Subject: [PATCH 41/42] dts: apple: t6030: Add isp nodes Signed-off-by: Sasha Finkelstein --- arch/arm64/boot/dts/apple/t6030-j514s.dts | 4 ++ arch/arm64/boot/dts/apple/t6030-j516s.dts | 4 ++ arch/arm64/boot/dts/apple/t6030.dtsi | 50 ++++++++++++++++++- .../arm64/boot/dts/apple/t603x-j514-j516.dtsi | 2 - 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6030-j514s.dts b/arch/arm64/boot/dts/apple/t6030-j514s.dts index 106df3e83bb908..5891cfe035f83c 100644 --- a/arch/arm64/boot/dts/apple/t6030-j514s.dts +++ b/arch/arm64/boot/dts/apple/t6030-j514s.dts @@ -75,3 +75,7 @@ compatible = "apple,j514-macaudio", "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J514"; }; + +&isp { + apple,platform-id = <16>; +}; diff --git a/arch/arm64/boot/dts/apple/t6030-j516s.dts b/arch/arm64/boot/dts/apple/t6030-j516s.dts index 838c2003419baa..6b59a0f97b166c 100644 --- a/arch/arm64/boot/dts/apple/t6030-j516s.dts +++ b/arch/arm64/boot/dts/apple/t6030-j516s.dts @@ -75,3 +75,7 @@ compatible = "apple,j516-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J516"; }; + +&isp { + apple,platform-id = <16>; +}; diff --git a/arch/arm64/boot/dts/apple/t6030.dtsi b/arch/arm64/boot/dts/apple/t6030.dtsi index 20a93f25b885fc..6021c4642f150e 100644 --- a/arch/arm64/boot/dts/apple/t6030.dtsi +++ b/arch/arm64/boot/dts/apple/t6030.dtsi @@ -13,8 +13,6 @@ #include #include -#define NO_ISP_YET 1 - / { #address-cells = <2>; #size-cells = <2>; @@ -644,6 +642,54 @@ status = "disabled"; }; + isp: isp@2c8000000 { + compatible = "apple,t6030-isp"; + iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>; + reg-names = "coproc", "mbox", "gpio", "mbox2"; + reg = <0x2 0xc8000000 0x0 0x2000000>, + <0x2 0xca4c4000 0x0 0x100>, + <0x2 0xca4c41d0 0x0 0x100>, + <0x2 0xca4c4458 0x0 0x100>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>, <&ps_ispsens0>, + <&ps_isp_cpu>, <&ps_isp_fe>, <&ps_dprx>, + <&ps_isp_secure>, <&ps_isp_be>, <&ps_isp_clr>; + apple,dart-vm-size = <0x0 0xa0000000>; + + status = "disabled"; + }; + + isp_dart0: iommu@2ca4a8000 { + compatible = "apple,t6030-dart", "apple,t8110-dart"; + reg = <0x2 0xca4a8000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + isp_dart1: iommu@2ca4b4000 { + compatible = "apple,t6030-dart", "apple,t8110-dart"; + reg = <0x2 0xca4b4000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + isp_dart2: iommu@2ca4bc000 { + compatible = "apple,t6030-dart", "apple,t8110-dart"; + reg = <0x2 0xca4bc000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + avd: avd@30a000000 { compatible = "apple,t6030-avd", "apple,t8122-avd"; interrupt-parent = <&aic>; diff --git a/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi b/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi index 0adfe50b78e788..b2a9bd5f921cd4 100644 --- a/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi +++ b/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi @@ -304,13 +304,11 @@ }; }; -#ifndef NO_ISP_YET &isp { apple,platform-id = <9>; }; #include "isp-imx558-cfg0.dtsi" -#endif #include "spi1-nvram.dtsi" #include "hwmon-common.dtsi" From 922f0e157063a5aa20da5410fced6a68edf1e12a Mon Sep 17 00:00:00 2001 From: James A DellaMorte Date: Sat, 1 Aug 2026 10:05:13 -0400 Subject: [PATCH 42/42] power: supply: macsmc: add "auto-discharge" charge behaviour for CHLS When lowering the charge-control end threshold on machines that use the CHLS key, the driver unconditionally sets CHLS_FORCE_DISCHARGE, actively draining the battery down to the limit even on AC power. Most laptop charge-limit implementations instead just cap charging at the threshold and let the battery drain naturally through use. Add a new "auto-discharge" charge_behaviour value that opts into the active discharge, and make end-threshold writes preserve the current CHLS_FORCE_DISCHARGE bit instead of forcing it on. "auto" keeps its documented meaning of only respecting the thresholds. The selection lives in the CHLS key itself, so no state is kept in the driver, it persists across reboots, and systems upgrading from the old behaviour keep force-discharge enabled until they explicitly write "auto". Resolves the existing TODO. Co-Authored-By: Claude Fable 5 Signed-off-by: James A DellaMorte --- Documentation/ABI/testing/sysfs-class-power | 2 + drivers/power/supply/macsmc-power.c | 48 ++++++++++++++++++--- drivers/power/supply/power_supply_sysfs.c | 1 + include/linux/power_supply.h | 1 + 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 32697b926cc8c2..6b439c628b6ee1 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -513,6 +513,8 @@ Description: inhibit-charge: Do not charge while AC is attached inhibit-charge-awake: inhibit-charge only when device is awake force-discharge: Force discharge while AC is attached + auto-discharge: auto, but actively discharge down to + the charge control end threshold ===================== ======================================== What: /sys/class/power_supply//technology diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index cfc57eb9a471b0..0cbd747794710c 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -295,6 +295,7 @@ static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power) int ret; u8 val8; u8 chte_buf[4]; + u16 vu16; if (power->has_ch0i) { ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8); @@ -319,6 +320,14 @@ static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power) return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; } + if (power->has_chls) { + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); + if (ret) + return ret; + if (vu16 & CHLS_FORCE_DISCHARGE) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE; + } + return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; } @@ -327,6 +336,10 @@ static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int v int ret; switch (val) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE: + if (!power->has_chls) + return -EOPNOTSUPP; + fallthrough; case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: /* Reset all inhibitors to a known-good 'auto' state */ if (power->has_ch0i) { @@ -344,6 +357,20 @@ static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int v if (ret) return ret; } + + /* Set or clear force-discharge to the CHLS charge limit */ + if (power->has_chls) { + u16 vu16; + + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); + if (ret) + return ret; + if (val == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE) + vu16 |= CHLS_FORCE_DISCHARGE; + else + vu16 &= ~CHLS_FORCE_DISCHARGE; + return apple_smc_write_u16(power->smc, SMC_KEY(CHLS), vu16); + } return 0; case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: @@ -606,12 +633,18 @@ static int macsmc_battery_set_property(struct power_supply *psy, return 0; case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: if (power->has_chls) { - u16 kval = 0; - /* TODO: Make CHLS_FORCE_DISCHARGE configurable */ + u16 kval; + int ret; + + /* Preserve the force-discharge (auto-discharge) charge_behaviour */ + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &kval); + if (ret) + return ret; + kval &= CHLS_FORCE_DISCHARGE; if (val->intval < CHLS_MIN_END_THRESHOLD) - kval = CHLS_FORCE_DISCHARGE | CHLS_MIN_END_THRESHOLD; + kval |= CHLS_MIN_END_THRESHOLD; else if (val->intval < 100) - kval = CHLS_FORCE_DISCHARGE | (val->intval & 0xff); + kval |= val->intval & 0xff; return apple_smc_write_u16(power->smc, SMC_KEY(CHLS), kval); } else if (power->has_chwa) { return apple_smc_write_flag(power->smc, SMC_KEY(CHWA), @@ -945,10 +978,13 @@ static int macsmc_power_probe(struct platform_device *pdev) } /* Detect charge limit method (CHWA vs CHLS) */ - if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0) + if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0) { power->has_chwa = true; - else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0) + } else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0) { power->has_chls = true; + power->batt_desc.charge_behaviours |= + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE); + } if (power->has_chwa || power->has_chls) { props[nprops++] = POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index f30a7b9ccd5e93..d22541f4a4ecf6 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -146,6 +146,7 @@ static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = { [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge", [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE] = "inhibit-charge-awake", [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE] = "auto-discharge", }; static struct power_supply_attr power_supply_attrs[] __ro_after_init = { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 7a5e4c3242a01d..82215a75385010 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -221,6 +221,7 @@ enum power_supply_charge_behaviour { POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE, POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE, POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE, + POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE, }; enum power_supply_notifier_events {