armbian-build/patch/kernel/archive/uefi-x86-6.18/4001-asahi-trackpad.patch
2026-02-24 15:01:53 +01:00

5053 lines
143 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 12 Dec 2021 20:40:04 +0100
Subject: 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 <j@jannau.net>
---
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 111111111111..222222222222 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2316,6 +2316,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
case BUS_I2C:
bus = "I2C";
break;
+ case BUS_SPI:
+ bus = "SPI";
+ break;
case BUS_SDW:
bus = "SOUNDWIRE";
break;
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 111111111111..222222222222 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -93,6 +93,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
@@ -197,6 +198,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 111111111111..222222222222 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 {
@@ -786,6 +788,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)
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 00:29:43 +0900
Subject: 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 <marcan@marcan.st>
---
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 111111111111..222222222222 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -94,6 +94,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
@@ -202,6 +203,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
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:06:15 +0900
Subject: HID: core: Handle HOST bus type when announcing devices
Signed-off-by: Hector Martin <marcan@marcan.st>
---
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 111111111111..222222222222 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2319,6 +2319,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_SDW:
bus = "SOUNDWIRE";
break;
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 10 Apr 2023 22:44:44 +0900
Subject: 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 <marcan@marcan.st>
---
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 111111111111..222222222222 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -468,7 +468,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;
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 21:15:31 +0100
Subject: 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 <j@jannau.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-apple.c | 15 ++++++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -140,7 +140,7 @@ config HID_APPLE
tristate "Apple {i,Power,Mac}Books"
depends on LEDS_CLASS
depends on NEW_LEDS
- default !EXPERT
+ default !EXPERT || SPI_HID_APPLE
help
Support for some Apple devices which less or more break
HID specification.
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 111111111111..222222222222 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -518,6 +518,15 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
table = macbookair_fn_keys;
else if (hid->product < 0x21d || hid->product >= 0x300)
table = powerbook_fn_keys;
+ else if (hid->bus == 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;
+ }
else
table = apple_fn_keys;
}
@@ -938,6 +947,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");
@@ -1216,6 +1229,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_2024),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT),
.driver_data = APPLE_MAGIC_BACKLIGHT },
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:12:24 +0900
Subject: HID: apple: Bind to HOST devices for MTP
We use BUS_HOST for MTP HID subdevices
Signed-off-by: Hector Martin <marcan@marcan.st>
---
drivers/hid/hid-apple.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 111111111111..222222222222 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -518,9 +518,10 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
table = macbookair_fn_keys;
else if (hid->product < 0x21d || hid->product >= 0x300)
table = powerbook_fn_keys;
- else if (hid->bus == BUS_SPI)
+ else if (hid->bus == BUS_HOST || hid->bus == BUS_SPI)
switch (hid->product) {
case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020:
+ case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022:
table = macbookpro_dedicated_esc_fn_keys;
break;
default:
@@ -947,7 +948,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;
@@ -1231,6 +1232,8 @@ static const struct hid_device_id apple_devices[] = {
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT),
.driver_data = APPLE_MAGIC_BACKLIGHT },
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 00:10:51 +0100
Subject: HID: magicmouse: use a define of the max number of touch contacts
Signed-off-by: Janne Grunau <j@jannau.net>
---
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 111111111111..222222222222 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;
@@ -615,7 +617,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,
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 00:12:35 +0100
Subject: HID: magicmouse: use struct input_mt_pos for X/Y
Signed-off-by: Janne Grunau <j@jannau.net>
---
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 111111111111..222222222222 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
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 00:15:30 +0100
Subject: HID: magicmouse: use ops function pointers for input functionality
Will be used for supporting MacBook trackpads connected via SPI.
Signed-off-by: Janne Grunau <j@jannau.net>
---
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 111111111111..222222222222 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;
@@ -538,7 +555,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;
@@ -860,6 +887,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);
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 01:17:48 +0100
Subject: 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 <j@jannau.net>
---
drivers/hid/Kconfig | 4 +-
drivers/hid/hid-magicmouse.c | 266 +++++++++-
2 files changed, 266 insertions(+), 4 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -726,11 +726,13 @@ config LOGIWHEELS_FF
config HID_MAGICMOUSE
tristate "Apple Magic Mouse/Trackpad multi-touch support"
+ default SPI_HID_APPLE
help
Support for the Apple Magic Mouse/Trackpad multi-touch.
Say Y here if you want support for the multi-touch features of the
- Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad.
+ Apple Wireless "Magic" Mouse, the Apple Wireless "Magic" Trackpad and
+ force touch Trackpads in Macbooks starting from 2015.
config HID_MALTRON
tristate "Maltron L90 keyboard"
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 111111111111..222222222222 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,
@@ -537,6 +550,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)
{
@@ -727,6 +888,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)
@@ -777,6 +1011,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) {
@@ -784,7 +1022,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;
}
@@ -881,14 +1119,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;
@@ -948,6 +1197,15 @@ static int magicmouse_probe(struct hid_device *hdev,
TRACKPAD2_USB_REPORT_ID, 0);
}
break;
+ case HID_ANY_ID:
+ switch (id->bus) {
+ case BUS_SPI:
+ report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0);
+ break;
+ default:
+ break;
+ }
+ break;
default: /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD_REPORT_ID, 0);
@@ -1055,6 +1313,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);
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:12:57 +0900
Subject: 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 <marcan@marcan.st>
---
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 111111111111..222222222222 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
@@ -586,25 +587,32 @@ struct tp_finger {
} __attribute__((packed, aligned(2)));
/**
- * struct trackpad report
+ * vendor trackpad report
*
- * @report_id: reportid
- * @buttons: HID Usage Buttons 3 1-bit reports
* @num_fingers: the number of fingers being reported in @fingers
- * @clicked: same as @buttons
+ * @buttons: same as HID buttons
*/
struct tp_header {
+ // HID vendor part, up to 1751 bytes
+ u8 unknown[22];
+ u8 num_fingers;
+ u8 buttons;
+ u8 unknown3[14];
+};
+
+/**
+ * standard HID mouse report
+ *
+ * @report_id: reportid
+ * @buttons: HID Usage Buttons 3 1-bit reports
+ */
+struct tp_mouse_report {
// HID mouse report
u8 report_id;
u8 buttons;
u8 rel_x;
u8 rel_y;
u8 padding[4];
- // HID vendor part, up to 1751 bytes
- u8 unknown[22];
- u8 num_fingers;
- u8 clicked;
- u8 unknown3[14];
};
static inline int le16_to_int(__le16 x)
@@ -634,7 +642,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);
@@ -651,9 +659,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;
@@ -692,12 +697,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)
{
@@ -1119,7 +1138,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;
@@ -1131,7 +1150,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 {
@@ -1199,6 +1221,9 @@ static int magicmouse_probe(struct hid_device *hdev,
break;
case HID_ANY_ID:
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;
@@ -1220,6 +1245,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.
@@ -1315,6 +1344,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);
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 22:56:16 +0100
Subject: HID: magicmouse: Add .reset_resume for SPI trackpads
The trackpad has to request multi touch reports during resume.
Signed-off-by: Janne Grunau <j@jannau.net>
---
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 111111111111..222222222222 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -1350,6 +1350,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,
@@ -1360,6 +1370,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);
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 30 Apr 2023 23:48:45 +0900
Subject: 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 <marcan@marcan.st>
---
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 111111111111..222222222222 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;
@@ -706,12 +751,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);
@@ -904,10 +956,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;
@@ -980,6 +1039,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)
@@ -1041,7 +1119,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;
}
@@ -1152,7 +1230,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;
@@ -1249,22 +1327,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;
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 3 Dec 2023 21:08:17 +0900
Subject: 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 <marcan@marcan.st>
---
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 111111111111..222222222222 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;
}
@@ -660,11 +717,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)
@@ -971,6 +1023,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);
@@ -1036,6 +1089,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;
}
@@ -1046,15 +1111,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;
}
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 10 Dec 2021 19:38:43 +0100
Subject: 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 <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Janne Grunau <j@jannau.net>
---
drivers/hid/Kconfig | 2 +
drivers/hid/Makefile | 2 +
drivers/hid/spi-hid/Kconfig | 26 +
drivers/hid/spi-hid/Makefile | 10 +
drivers/hid/spi-hid/spi-hid-apple-core.c | 1194 ++++++++++
drivers/hid/spi-hid/spi-hid-apple-of.c | 153 ++
drivers/hid/spi-hid/spi-hid-apple.h | 35 +
7 files changed, 1422 insertions(+)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1447,4 +1447,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 111111111111..222222222222 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -173,6 +173,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 000000000000..111111111111
--- /dev/null
+++ b/drivers/hid/spi-hid/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "SPI HID support"
+ depends on SPI
+
+config SPI_HID_APPLE_OF
+ tristate "HID over SPI transport layer for Apple Silicon SoCs"
+ default ARCH_APPLE
+ depends on SPI && INPUT && OF
+ help
+ Say Y here if you use Apple Silicon based laptop. The keyboard and
+ touchpad are HID based devices connected via SPI.
+
+ If unsure, say N.
+
+ This support is also available as a module. If so, the module
+ will be called spi-hid-apple-of. It will also build/depend on the
+ module spi-hid-apple.
+
+endmenu
+
+config SPI_HID_APPLE_CORE
+ tristate
+ default y if SPI_HID_APPLE_OF=y
+ default m if SPI_HID_APPLE_OF=m
+ select HID
+ select CRC16
diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile
new file mode 100644
index 000000000000..111111111111
--- /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 000000000000..111111111111
--- /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 <linux/crc16.h>
+#include <linux/delay.h>
+#include <linux/device/driver.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+#include <linux/wait.h>
+
+#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 <j@jannau.net>");
+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 000000000000..111111111111
--- /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 <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+
+#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 <j@jannau.net>");
+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 000000000000..111111111111
--- /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 <linux/interrupt.h>
+#include <linux/spi/spi.h>
+
+/**
+ * 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 */
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:09:24 +0900
Subject: 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 <marcan@marcan.st>
---
drivers/soc/apple/Kconfig | 10 +
drivers/soc/apple/Makefile | 3 +
drivers/soc/apple/dockchannel.c | 406 ++++++++++
include/linux/soc/apple/dockchannel.h | 26 +
4 files changed, 445 insertions(+)
diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/soc/apple/Kconfig
+++ b/drivers/soc/apple/Kconfig
@@ -4,6 +4,16 @@ if ARCH_APPLE || COMPILE_TEST
menu "Apple SoC drivers"
+config APPLE_DOCKCHANNEL
+ tristate "Apple DockChannel FIFO"
+ depends on ARCH_APPLE || COMPILE_TEST
+ default ARCH_APPLE
+ help
+ DockChannel is a simple FIFO used on Apple SoCs for debug and inter-processor
+ communications.
+
+ Say 'y' here if you have an Apple SoC.
+
config APPLE_MAILBOX
tristate "Apple SoC mailboxes"
depends on PM
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
index 111111111111..222222222222 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 000000000000..111111111111
--- /dev/null
+++ b/drivers/soc/apple/dockchannel.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple DockChannel FIFO driver
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/platform_device.h>
+#include <linux/soc/apple/dockchannel.h>
+#include <linux/unaligned.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+
+#define DOCKCHANNEL_MAX_IRQ 32
+
+#define DOCKCHANNEL_TX_TIMEOUT_MS 1000
+#define DOCKCHANNEL_RX_TIMEOUT_MS 1000
+
+#define IRQ_MASK 0x0
+#define IRQ_FLAG 0x4
+
+#define IRQ_TX BIT(0)
+#define IRQ_RX BIT(1)
+
+#define CONFIG_TX_THRESH 0x0
+#define CONFIG_RX_THRESH 0x4
+
+#define DATA_TX8 0x4
+#define DATA_TX16 0x8
+#define DATA_TX24 0xc
+#define DATA_TX32 0x10
+#define DATA_TX_FREE 0x14
+#define DATA_RX8 0x1c
+#define DATA_RX16 0x20
+#define DATA_RX24 0x24
+#define DATA_RX32 0x28
+#define DATA_RX_COUNT 0x2c
+
+struct dockchannel {
+ struct device *dev;
+ int tx_irq;
+ int rx_irq;
+
+ void __iomem *config_base;
+ void __iomem *data_base;
+
+ u32 fifo_size;
+ bool awaiting;
+ struct completion tx_comp;
+ struct completion rx_comp;
+
+ void *cookie;
+ void (*data_available)(void *cookie, size_t avail);
+};
+
+struct dockchannel_common {
+ struct device *dev;
+ struct irq_domain *domain;
+ int irq;
+
+ void __iomem *irq_base;
+};
+
+/* Dockchannel FIFO functions */
+
+static irqreturn_t dockchannel_tx_irq(int irq, void *data)
+{
+ struct dockchannel *dockchannel = data;
+
+ disable_irq_nosync(irq);
+ complete(&dockchannel->tx_comp);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dockchannel_rx_irq(int irq, void *data)
+{
+ struct dockchannel *dockchannel = data;
+
+ disable_irq_nosync(irq);
+
+ if (dockchannel->awaiting) {
+ return IRQ_WAKE_THREAD;
+ } else {
+ complete(&dockchannel->rx_comp);
+ return IRQ_HANDLED;
+ }
+}
+
+static irqreturn_t dockchannel_rx_irq_thread(int irq, void *data)
+{
+ struct dockchannel *dockchannel = data;
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT);
+
+ dockchannel->awaiting = false;
+ dockchannel->data_available(dockchannel->cookie, avail);
+
+ return IRQ_HANDLED;
+}
+
+int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count)
+{
+ size_t left = count;
+ const u8 *p = buf;
+
+ while (left > 0) {
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_TX_FREE);
+ size_t block = min(left, avail);
+
+ if (avail == 0) {
+ size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left);
+
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_TX_THRESH);
+ reinit_completion(&dockchannel->tx_comp);
+ enable_irq(dockchannel->tx_irq);
+
+ if (!wait_for_completion_timeout(&dockchannel->tx_comp,
+ msecs_to_jiffies(DOCKCHANNEL_TX_TIMEOUT_MS))) {
+ disable_irq(dockchannel->tx_irq);
+ return -ETIMEDOUT;
+ }
+
+ continue;
+ }
+
+ while (block >= 4) {
+ writel_relaxed(get_unaligned_le32(p), dockchannel->data_base + DATA_TX32);
+ p += 4;
+ left -= 4;
+ block -= 4;
+ }
+ while (block > 0) {
+ writeb_relaxed(*p++, dockchannel->data_base + DATA_TX8);
+ left--;
+ block--;
+ }
+ }
+
+ return count;
+}
+EXPORT_SYMBOL(dockchannel_send);
+
+int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count)
+{
+ size_t left = count;
+ u8 *p = buf;
+
+ while (left > 0) {
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT);
+ size_t block = min(left, avail);
+
+ if (avail == 0) {
+ size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left);
+
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH);
+ reinit_completion(&dockchannel->rx_comp);
+ enable_irq(dockchannel->rx_irq);
+
+ if (!wait_for_completion_timeout(&dockchannel->rx_comp,
+ msecs_to_jiffies(DOCKCHANNEL_RX_TIMEOUT_MS))) {
+ disable_irq(dockchannel->rx_irq);
+ return -ETIMEDOUT;
+ }
+
+ continue;
+ }
+
+ while (block >= 4) {
+ put_unaligned_le32(readl_relaxed(dockchannel->data_base + DATA_RX32), p);
+ p += 4;
+ left -= 4;
+ block -= 4;
+ }
+ while (block > 0) {
+ *p++ = readl_relaxed(dockchannel->data_base + DATA_RX8) >> 8;
+ left--;
+ block--;
+ }
+ }
+
+ return count;
+}
+EXPORT_SYMBOL(dockchannel_recv);
+
+int dockchannel_await(struct dockchannel *dockchannel,
+ void (*callback)(void *cookie, size_t avail),
+ void *cookie, size_t count)
+{
+ size_t threshold = min((size_t)dockchannel->fifo_size, count);
+
+ if (!count) {
+ dockchannel->awaiting = false;
+ disable_irq(dockchannel->rx_irq);
+ return 0;
+ }
+
+ dockchannel->data_available = callback;
+ dockchannel->cookie = cookie;
+ dockchannel->awaiting = true;
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH);
+ enable_irq(dockchannel->rx_irq);
+
+ return threshold;
+}
+EXPORT_SYMBOL(dockchannel_await);
+
+struct dockchannel *dockchannel_init(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dockchannel *dockchannel;
+ int ret;
+
+ dockchannel = devm_kzalloc(dev, sizeof(*dockchannel), GFP_KERNEL);
+ if (!dockchannel)
+ return ERR_PTR(-ENOMEM);
+
+ dockchannel->dev = dev;
+ dockchannel->config_base = devm_platform_ioremap_resource_byname(pdev, "config");
+ if (IS_ERR(dockchannel->config_base))
+ return (__force void *)dockchannel->config_base;
+
+ dockchannel->data_base = devm_platform_ioremap_resource_byname(pdev, "data");
+ if (IS_ERR(dockchannel->data_base))
+ return (__force void *)dockchannel->data_base;
+
+ ret = of_property_read_u32(dev->of_node, "apple,fifo-size", &dockchannel->fifo_size);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret, "Missing apple,fifo-size property"));
+
+ init_completion(&dockchannel->tx_comp);
+ init_completion(&dockchannel->rx_comp);
+
+ dockchannel->tx_irq = platform_get_irq_byname(pdev, "tx");
+ if (dockchannel->tx_irq <= 0) {
+ return ERR_PTR(dev_err_probe(dev, dockchannel->tx_irq,
+ "Failed to get TX IRQ"));
+ }
+
+ dockchannel->rx_irq = platform_get_irq_byname(pdev, "rx");
+ if (dockchannel->rx_irq <= 0) {
+ return ERR_PTR(dev_err_probe(dev, dockchannel->rx_irq,
+ "Failed to get RX IRQ"));
+ }
+
+ ret = devm_request_irq(dev, dockchannel->tx_irq, dockchannel_tx_irq, IRQF_NO_AUTOEN,
+ "apple-dockchannel-tx", dockchannel);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret, "Failed to request TX IRQ"));
+
+ ret = devm_request_threaded_irq(dev, dockchannel->rx_irq, dockchannel_rx_irq,
+ dockchannel_rx_irq_thread, IRQF_NO_AUTOEN,
+ "apple-dockchannel-rx", dockchannel);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret, "Failed to request RX IRQ"));
+
+ return dockchannel;
+}
+EXPORT_SYMBOL(dockchannel_init);
+
+
+/* Dockchannel IRQchip */
+
+static void dockchannel_irq(struct irq_desc *desc)
+{
+ unsigned int irq = irq_desc_get_irq(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct dockchannel_common *dcc = irq_get_handler_data(irq);
+ unsigned long flags = readl_relaxed(dcc->irq_base + IRQ_FLAG);
+ int bit;
+
+ chained_irq_enter(chip, desc);
+
+ for_each_set_bit(bit, &flags, DOCKCHANNEL_MAX_IRQ)
+ generic_handle_domain_irq(dcc->domain, bit);
+
+ chained_irq_exit(chip, desc);
+}
+
+static void dockchannel_irq_ack(struct irq_data *data)
+{
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+ unsigned int hwirq = data->hwirq;
+
+ writel_relaxed(BIT(hwirq), dcc->irq_base + IRQ_FLAG);
+}
+
+static void dockchannel_irq_mask(struct irq_data *data)
+{
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+ unsigned int hwirq = data->hwirq;
+ u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK);
+
+ writel_relaxed(val & ~BIT(hwirq), dcc->irq_base + IRQ_MASK);
+}
+
+static void dockchannel_irq_unmask(struct irq_data *data)
+{
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+ unsigned int hwirq = data->hwirq;
+ u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK);
+
+ writel_relaxed(val | BIT(hwirq), dcc->irq_base + IRQ_MASK);
+}
+
+static const struct irq_chip dockchannel_irqchip = {
+ .name = "dockchannel-irqc",
+ .irq_ack = dockchannel_irq_ack,
+ .irq_mask = dockchannel_irq_mask,
+ .irq_unmask = dockchannel_irq_unmask,
+};
+
+static int dockchannel_irq_domain_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ irq_set_chip_data(virq, d->host_data);
+ irq_set_chip_and_handler(virq, &dockchannel_irqchip, handle_level_irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops dockchannel_irq_domain_ops = {
+ .xlate = irq_domain_xlate_twocell,
+ .map = dockchannel_irq_domain_map,
+};
+
+static int dockchannel_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dockchannel_common *dcc;
+ struct device_node *child;
+
+ dcc = devm_kzalloc(dev, sizeof(*dcc), GFP_KERNEL);
+ if (!dcc)
+ return -ENOMEM;
+
+ dcc->dev = dev;
+ platform_set_drvdata(pdev, dcc);
+
+ dcc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq");
+ if (IS_ERR(dcc->irq_base))
+ return PTR_ERR(dcc->irq_base);
+
+ writel_relaxed(0, dcc->irq_base + IRQ_MASK);
+ writel_relaxed(~0, dcc->irq_base + IRQ_FLAG);
+
+ dcc->domain = irq_domain_add_linear(dev->of_node, DOCKCHANNEL_MAX_IRQ,
+ &dockchannel_irq_domain_ops, dcc);
+ if (!dcc->domain)
+ return -ENOMEM;
+
+ dcc->irq = platform_get_irq(pdev, 0);
+ if (dcc->irq <= 0)
+ return dev_err_probe(dev, dcc->irq, "Failed to get IRQ");
+
+ irq_set_handler_data(dcc->irq, dcc);
+ irq_set_chained_handler(dcc->irq, dockchannel_irq);
+
+ for_each_child_of_node(dev->of_node, child)
+ of_platform_device_create(child, NULL, dev);
+
+ return 0;
+}
+
+static void dockchannel_remove(struct platform_device *pdev)
+{
+ struct dockchannel_common *dcc = platform_get_drvdata(pdev);
+ int hwirq;
+
+ device_for_each_child(&pdev->dev, NULL, of_platform_device_destroy);
+
+ irq_set_chained_handler_and_data(dcc->irq, NULL, NULL);
+
+ for (hwirq = 0; hwirq < DOCKCHANNEL_MAX_IRQ; hwirq++)
+ irq_dispose_mapping(irq_find_mapping(dcc->domain, hwirq));
+
+ irq_domain_remove(dcc->domain);
+
+ writel_relaxed(0, dcc->irq_base + IRQ_MASK);
+ writel_relaxed(~0, dcc->irq_base + IRQ_FLAG);
+}
+
+static const struct of_device_id dockchannel_of_match[] = {
+ { .compatible = "apple,dockchannel" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dockchannel_of_match);
+
+static struct platform_driver dockchannel_driver = {
+ .driver = {
+ .name = "dockchannel",
+ .of_match_table = dockchannel_of_match,
+ },
+ .probe = dockchannel_probe,
+ .remove = dockchannel_remove,
+};
+module_platform_driver(dockchannel_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+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 000000000000..111111111111
--- /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 <linux/device.h>
+#include <linux/types.h>
+#include <linux/of_platform.h>
+
+#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
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:11:21 +0900
Subject: 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 <marcan@marcan.st>
---
drivers/hid/Kconfig | 2 +
drivers/hid/Makefile | 4 +
drivers/hid/dockchannel-hid/Kconfig | 14 +
drivers/hid/dockchannel-hid/Makefile | 6 +
drivers/hid/dockchannel-hid/dockchannel-hid.c | 1213 ++++++++++
5 files changed, 1239 insertions(+)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1449,4 +1449,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 111111111111..222222222222 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -173,8 +173,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 000000000000..111111111111
--- /dev/null
+++ b/drivers/hid/dockchannel-hid/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+menu "DockChannel HID support"
+ depends on APPLE_DOCKCHANNEL
+
+config HID_DOCKCHANNEL
+ tristate "HID over DockChannel transport layer for Apple Silicon SoCs"
+ default ARCH_APPLE
+ depends on APPLE_DOCKCHANNEL && INPUT && OF && HID
+ help
+ Say Y here if you use an M2 or later Apple Silicon based laptop.
+ The keyboard and touchpad are HID based devices connected via the
+ proprietary DockChannel interface.
+
+endmenu
diff --git a/drivers/hid/dockchannel-hid/Makefile b/drivers/hid/dockchannel-hid/Makefile
new file mode 100644
index 000000000000..111111111111
--- /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 000000000000..111111111111
--- /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 <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/hid.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/soc/apple/dockchannel.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+#include <linux/of.h>
+#include "../hid-ids.h"
+
+#define COMMAND_TIMEOUT_MS 1000
+#define START_TIMEOUT_MS 2000
+
+#define MAX_INTERFACES 16
+
+/* Data + checksum */
+#define MAX_PKT_SIZE (0xffff + 4)
+
+#define DCHID_CHANNEL_CMD 0x11
+#define DCHID_CHANNEL_REPORT 0x12
+
+struct dchid_hdr {
+ u8 hdr_len;
+ u8 channel;
+ u16 length;
+ u8 seq;
+ u8 iface;
+ u16 pad;
+} __packed;
+
+#define IFACE_COMM 0
+
+#define FLAGS_GROUP GENMASK(7, 6)
+#define FLAGS_REQ GENMASK(5, 0)
+
+#define REQ_SET_REPORT 0
+#define REQ_GET_REPORT 1
+
+struct dchid_subhdr {
+ u8 flags;
+ u8 unk;
+ u16 length;
+ u32 retcode;
+} __packed;
+
+#define EVENT_GPIO_CMD 0xa0
+#define EVENT_INIT 0xf0
+#define EVENT_READY 0xf1
+
+struct dchid_init_hdr {
+ u8 type;
+ u8 unk1;
+ u8 unk2;
+ u8 iface;
+ char name[16];
+ u8 more_packets;
+ u8 unkpad;
+} __packed;
+
+#define INIT_HID_DESCRIPTOR 0
+#define INIT_GPIO_REQUEST 1
+#define INIT_TERMINATOR 2
+#define INIT_PRODUCT_NAME 7
+
+#define CMD_RESET_INTERFACE 0x40
+#define CMD_SEND_FIRMWARE 0x95
+#define CMD_ENABLE_INTERFACE 0xb4
+#define CMD_ACK_GPIO_CMD 0xa1
+
+struct dchid_init_block_hdr {
+ u16 type;
+ u16 length;
+} __packed;
+
+#define MAX_GPIO_NAME 32
+
+struct dchid_gpio_request {
+ u16 unk;
+ u16 id;
+ char name[MAX_GPIO_NAME];
+} __packed;
+
+struct dchid_gpio_cmd {
+ u8 type;
+ u8 iface;
+ u8 gpio;
+ u8 unk;
+ u8 cmd;
+} __packed;
+
+struct dchid_gpio_ack {
+ u8 type;
+ u32 retcode;
+ u8 cmd[];
+} __packed;
+
+#define STM_REPORT_ID 0x10
+#define STM_REPORT_SERIAL 0x11
+#define STM_REPORT_KEYBTYPE 0x14
+
+struct dchid_stm_id {
+ u8 unk;
+ u16 vendor_id;
+ u16 product_id;
+ u16 version_number;
+ u8 unk2;
+ u8 unk3;
+ u8 keyboard_type;
+ u8 serial_length;
+ /* Serial follows, but we grab it with a different report. */
+} __packed;
+
+#define FW_MAGIC 0x46444948
+#define FW_VER 1
+
+struct fw_header {
+ u32 magic;
+ u32 version;
+ u32 hdr_length;
+ u32 data_length;
+ u32 iface_offset;
+} __packed;
+
+struct dchid_work {
+ struct work_struct work;
+ struct dchid_iface *iface;
+
+ struct dchid_hdr hdr;
+ u8 data[];
+};
+
+struct dchid_iface {
+ struct dockchannel_hid *dchid;
+ struct hid_device *hid;
+ struct workqueue_struct *wq;
+
+ bool creating;
+ struct work_struct create_work;
+
+ int index;
+ const char *name;
+ const struct device_node *of_node;
+
+ uint8_t tx_seq;
+ bool deferred;
+ bool starting;
+ bool open;
+ struct completion ready;
+
+ void *hid_desc;
+ size_t hid_desc_len;
+
+ struct gpio_desc *gpio;
+ char gpio_name[MAX_GPIO_NAME];
+ int gpio_id;
+
+ struct mutex out_mutex;
+ u32 out_flags;
+ int out_report;
+ u32 retcode;
+ void *resp_buf;
+ size_t resp_size;
+ struct completion out_complete;
+
+ u32 keyboard_layout_id;
+};
+
+struct dockchannel_hid {
+ struct device *dev;
+ struct dockchannel *dc;
+ struct device_link *helper_link;
+
+ bool id_ready;
+ struct dchid_stm_id device_id;
+ char serial[64];
+
+ struct dchid_iface *comm;
+ struct dchid_iface *ifaces[MAX_INTERFACES];
+
+ u8 pkt_buf[MAX_PKT_SIZE];
+
+ /* Workqueue to asynchronously create HID devices */
+ struct workqueue_struct *new_iface_wq;
+};
+
+static ssize_t apple_layout_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct dchid_iface *iface = hdev->driver_data;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", iface->keyboard_layout_id);
+}
+
+static DEVICE_ATTR_RO(apple_layout_id);
+
+static struct dchid_iface *
+dchid_get_interface(struct dockchannel_hid *dchid, int index, const char *name)
+{
+ struct dchid_iface *iface;
+
+ if (index >= MAX_INTERFACES) {
+ dev_err(dchid->dev, "Interface index %d out of range\n", index);
+ return NULL;
+ }
+
+ if (dchid->ifaces[index])
+ return dchid->ifaces[index];
+
+ iface = devm_kzalloc(dchid->dev, sizeof(struct dchid_iface), GFP_KERNEL);
+ if (!iface)
+ return NULL;
+
+ iface->index = index;
+ iface->name = devm_kstrdup(dchid->dev, name, GFP_KERNEL);
+ iface->dchid = dchid;
+ iface->out_report= -1;
+ init_completion(&iface->out_complete);
+ init_completion(&iface->ready);
+ mutex_init(&iface->out_mutex);
+ iface->wq = alloc_ordered_workqueue("dchid-%s", WQ_MEM_RECLAIM, iface->name);
+ if (!iface->wq)
+ return NULL;
+
+ /* Comm is not a HID subdevice */
+ if (!strcmp(name, "comm")) {
+ dchid->ifaces[index] = iface;
+ return iface;
+ }
+
+ iface->of_node = of_get_child_by_name(dchid->dev->of_node, name);
+ if (!iface->of_node) {
+ dev_warn(dchid->dev, "No OF node for subdevice %s, ignoring.", name);
+ return NULL;
+ }
+
+ dchid->ifaces[index] = iface;
+ return iface;
+}
+
+static u32 dchid_checksum(void *p, size_t length)
+{
+ u32 sum = 0;
+
+ while (length >= 4) {
+ sum += get_unaligned_le32(p);
+ p += 4;
+ length -= 4;
+ }
+
+ WARN_ON_ONCE(length);
+ return sum;
+}
+
+static int dchid_send(struct dchid_iface *iface, u32 flags, void *msg, size_t size)
+{
+ u32 checksum = 0xffffffff;
+ size_t wsize = round_down(size, 4);
+ size_t tsize = size - wsize;
+ int ret;
+ struct {
+ struct dchid_hdr hdr;
+ struct dchid_subhdr sub;
+ } __packed h;
+
+ memset(&h, 0, sizeof(h));
+ h.hdr.hdr_len = sizeof(h.hdr);
+ h.hdr.channel = DCHID_CHANNEL_CMD;
+ h.hdr.length = round_up(size, 4) + sizeof(h.sub);
+ h.hdr.seq = iface->tx_seq;
+ h.hdr.iface = iface->index;
+ h.sub.flags = flags;
+ h.sub.length = size;
+
+ ret = dockchannel_send(iface->dchid->dc, &h, sizeof(h));
+ if (ret < 0)
+ return ret;
+ checksum -= dchid_checksum(&h, sizeof(h));
+
+ ret = dockchannel_send(iface->dchid->dc, msg, wsize);
+ if (ret < 0)
+ return ret;
+ checksum -= dchid_checksum(msg, wsize);
+
+ if (tsize) {
+ u8 tail[4] = {0, 0, 0, 0};
+
+ memcpy(tail, msg + wsize, tsize);
+ ret = dockchannel_send(iface->dchid->dc, tail, sizeof(tail));
+ if (ret < 0)
+ return ret;
+ checksum -= dchid_checksum(tail, sizeof(tail));
+ }
+
+ ret = dockchannel_send(iface->dchid->dc, &checksum, sizeof(checksum));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req,
+ void *data, size_t size, void *resp_buf, size_t resp_size)
+{
+ int ret;
+ int report_id = *(u8*)data;
+
+ mutex_lock(&iface->out_mutex);
+
+ WARN_ON(iface->out_report != -1);
+ iface->out_report = report_id;
+ iface->out_flags = FIELD_PREP(FLAGS_GROUP, type) | FIELD_PREP(FLAGS_REQ, req);
+ iface->resp_buf = resp_buf;
+ iface->resp_size = resp_size;
+ reinit_completion(&iface->out_complete);
+
+ ret = dchid_send(iface, iface->out_flags, data, size);
+ if (ret < 0)
+ goto done;
+
+ if (!wait_for_completion_timeout(&iface->out_complete, msecs_to_jiffies(COMMAND_TIMEOUT_MS))) {
+ dev_err(iface->dchid->dev, "output report 0x%x to iface %d (%s) timed out\n",
+ report_id, iface->index, iface->name);
+ ret = -ETIMEDOUT;
+ goto done;
+ }
+
+ ret = iface->resp_size;
+ if (iface->retcode) {
+ dev_err(iface->dchid->dev,
+ "output report 0x%x to iface %d (%s) failed with err 0x%x\n",
+ report_id, iface->index, iface->name, iface->retcode);
+ ret = -EIO;
+ }
+
+done:
+ iface->tx_seq++;
+ iface->out_report = -1;
+ iface->out_flags = 0;
+ iface->resp_buf = NULL;
+ iface->resp_size = 0;
+ mutex_unlock(&iface->out_mutex);
+ return ret;
+}
+
+static int dchid_comm_cmd(struct dockchannel_hid *dchid, void *cmd, size_t size)
+{
+ return dchid_cmd(dchid->comm, HID_FEATURE_REPORT, REQ_SET_REPORT, cmd, size, NULL, 0);
+}
+
+static int dchid_enable_interface(struct dchid_iface *iface)
+{
+ u8 msg[] = { CMD_ENABLE_INTERFACE, iface->index };
+
+ return dchid_comm_cmd(iface->dchid, msg, sizeof(msg));
+}
+
+static int dchid_reset_interface(struct dchid_iface *iface, int state)
+{
+ u8 msg[] = { CMD_RESET_INTERFACE, 1, iface->index, state };
+
+ return dchid_comm_cmd(iface->dchid, msg, sizeof(msg));
+}
+
+static int dchid_send_firmware(struct dchid_iface *iface, void *firmware, size_t size)
+{
+ struct {
+ u8 cmd;
+ u8 unk1;
+ u8 unk2;
+ u8 iface;
+ u64 addr;
+ u32 size;
+ } __packed msg = {
+ .cmd = CMD_SEND_FIRMWARE,
+ .unk1 = 2,
+ .unk2 = 0,
+ .iface = iface->index,
+ .size = size,
+ };
+ dma_addr_t addr;
+ void *buf = dmam_alloc_coherent(iface->dchid->dev, size, &addr, GFP_KERNEL);
+
+ if (IS_ERR_OR_NULL(buf))
+ return buf ? PTR_ERR(buf) : -ENOMEM;
+
+ msg.addr = addr;
+ memcpy(buf, firmware, size);
+ wmb();
+
+ return dchid_comm_cmd(iface->dchid, &msg, sizeof(msg));
+}
+
+static int dchid_get_firmware(struct dchid_iface *iface, void **firmware, size_t *size)
+{
+ int ret;
+ const char *fw_name;
+ const struct firmware *fw;
+ struct fw_header *hdr;
+ u8 *fw_data;
+
+ ret = of_property_read_string(iface->of_node, "firmware-name", &fw_name);
+ if (ret) {
+ /* Firmware is only for some devices */
+ *firmware = NULL;
+ *size = 0;
+ return 0;
+ }
+
+ ret = request_firmware(&fw, fw_name, iface->dchid->dev);
+ if (ret)
+ return ret;
+
+ hdr = (struct fw_header *)fw->data;
+
+ if (hdr->magic != FW_MAGIC || hdr->version != FW_VER ||
+ hdr->hdr_length < sizeof(*hdr) || hdr->hdr_length > fw->size ||
+ (hdr->hdr_length + (size_t)hdr->data_length) > fw->size ||
+ hdr->iface_offset >= hdr->data_length) {
+ dev_warn(iface->dchid->dev, "%s: invalid firmware header\n",
+ fw_name);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ fw_data = devm_kmemdup(iface->dchid->dev, fw->data + hdr->hdr_length,
+ hdr->data_length, GFP_KERNEL);
+ if (!fw_data) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ if (hdr->iface_offset)
+ fw_data[hdr->iface_offset] = iface->index;
+
+ *firmware = fw_data;
+ *size = hdr->data_length;
+
+done:
+ release_firmware(fw);
+ return ret;
+}
+
+static int dchid_request_gpio(struct dchid_iface *iface)
+{
+ char prop_name[MAX_GPIO_NAME + 16];
+
+ if (iface->gpio)
+ return 0;
+
+ dev_info(iface->dchid->dev, "Requesting GPIO %s#%d: %s\n",
+ iface->name, iface->gpio_id, iface->gpio_name);
+
+ snprintf(prop_name, sizeof(prop_name), "apple,%s", iface->gpio_name);
+
+ iface->gpio = devm_gpiod_get_index(iface->dchid->dev, prop_name, 0, GPIOD_OUT_LOW);
+
+ if (IS_ERR_OR_NULL(iface->gpio)) {
+ dev_err(iface->dchid->dev, "Failed to request GPIO %s-gpios\n", prop_name);
+ iface->gpio = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dchid_start_interface(struct dchid_iface *iface)
+{
+ void *fw;
+ size_t size;
+ int ret;
+
+ if (iface->starting) {
+ dev_warn(iface->dchid->dev, "Interface %s is already starting", iface->name);
+ return -EINPROGRESS;
+ }
+
+ dev_info(iface->dchid->dev, "Starting interface %s\n", iface->name);
+
+ iface->starting = true;
+
+ /* Look to see if we need firmware */
+ ret = dchid_get_firmware(iface, &fw, &size);
+ if (ret < 0)
+ goto err;
+
+ /* If we need a GPIO, make sure we have it. */
+ if (iface->gpio_id) {
+ ret = dchid_request_gpio(iface);
+ if (ret < 0)
+ goto err;
+ }
+
+ /* Only multi-touch has firmware */
+ if (fw && size) {
+
+ /* Send firmware to the device */
+ dev_info(iface->dchid->dev, "Sending firmware for %s\n", iface->name);
+ ret = dchid_send_firmware(iface, fw, size);
+ if (ret < 0) {
+ dev_err(iface->dchid->dev, "Failed to send %s firmwareS", iface->name);
+ goto err;
+ }
+
+ /* After loading firmware, multi-touch needs a reset */
+ dev_info(iface->dchid->dev, "Resetting %s\n", iface->name);
+ dchid_reset_interface(iface, 0);
+ dchid_reset_interface(iface, 2);
+ }
+
+ return 0;
+
+err:
+ iface->starting = false;
+ return ret;
+}
+
+static int dchid_start(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ if (iface->keyboard_layout_id) {
+ int ret = device_create_file(&hdev->dev, &dev_attr_apple_layout_id);
+ if (ret) {
+ dev_warn(iface->dchid->dev, "Failed to create apple_layout_id: %d", ret);
+ iface->keyboard_layout_id = 0;
+ }
+ }
+
+ return 0;
+};
+
+static void dchid_stop(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ if (iface->keyboard_layout_id)
+ device_remove_file(&hdev->dev, &dev_attr_apple_layout_id);
+}
+
+static int dchid_open(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+ int ret;
+
+ if (!completion_done(&iface->ready)) {
+ ret = dchid_start_interface(iface);
+ if (ret < 0)
+ return ret;
+
+ if (!wait_for_completion_timeout(&iface->ready, msecs_to_jiffies(START_TIMEOUT_MS))) {
+ dev_err(iface->dchid->dev, "iface %s start timed out\n", iface->name);
+ return -ETIMEDOUT;
+ }
+ }
+
+ iface->open = true;
+ return 0;
+}
+
+static void dchid_close(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ iface->open = false;
+}
+
+static int dchid_parse(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ return hid_parse_report(hdev, iface->hid_desc, iface->hid_desc_len);
+}
+
+/* Note: buf excludes report number! For ease of fetching strings/etc. */
+static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum, void *buf, size_t len)
+{
+ int ret = dchid_cmd(iface, HID_FEATURE_REPORT, REQ_GET_REPORT, &reportnum, 1, buf, len);
+
+ return ret <= 0 ? ret : ret - 1;
+}
+
+/* Note: buf includes report number! */
+static int dchid_set_report(struct dchid_iface *iface, void *buf, size_t len)
+{
+ return dchid_cmd(iface, HID_OUTPUT_REPORT, REQ_SET_REPORT, buf, len, NULL, 0);
+}
+
+static int dchid_raw_request(struct hid_device *hdev,
+ unsigned char reportnum, __u8 *buf, size_t len,
+ unsigned char rtype, int reqtype)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ buf[0] = reportnum;
+ return dchid_cmd(iface, rtype, REQ_GET_REPORT, &reportnum, 1, buf + 1, len - 1);
+ case HID_REQ_SET_REPORT:
+ return dchid_set_report(iface, buf, len);
+ default:
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static struct hid_ll_driver dchid_ll = {
+ .start = &dchid_start,
+ .stop = &dchid_stop,
+ .open = &dchid_open,
+ .close = &dchid_close,
+ .parse = &dchid_parse,
+ .raw_request = &dchid_raw_request,
+};
+
+static void dchid_create_interface_work(struct work_struct *ws)
+{
+ struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work);
+ struct dockchannel_hid *dchid = iface->dchid;
+ struct hid_device *hid;
+ int ret;
+
+ if (iface->hid) {
+ dev_warn(dchid->dev, "Interface %s already created!\n",
+ iface->name);
+ return;
+ }
+
+ dev_info(dchid->dev, "New interface %s\n", iface->name);
+
+ /* Start the interface. This is not the entire init process, as firmware is loaded later on device open. */
+ ret = dchid_enable_interface(iface);
+ if (ret < 0) {
+ dev_warn(dchid->dev, "Failed to enable %s: %d\n", iface->name, ret);
+ return;
+ }
+
+ iface->deferred = false;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return;
+
+ snprintf(hid->name, sizeof(hid->name), "Apple MTP %s", iface->name);
+ snprintf(hid->phys, sizeof(hid->phys), "%s.%d (%s)",
+ dev_name(dchid->dev), iface->index, iface->name);
+ strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq));
+
+ hid->ll_driver = &dchid_ll;
+ hid->bus = BUS_HOST;
+ hid->vendor = dchid->device_id.vendor_id;
+ hid->product = dchid->device_id.product_id;
+ hid->version = dchid->device_id.version_number;
+ hid->type = HID_TYPE_OTHER;
+ if (!strcmp(iface->name, "multi-touch")) {
+ hid->type = HID_TYPE_SPI_MOUSE;
+ } else if (!strcmp(iface->name, "keyboard")) {
+ u32 country_code = 0;
+
+ hid->type = HID_TYPE_SPI_KEYBOARD;
+
+ /*
+ * We have to get the country code from the device tree, since the
+ * device provides no reliable way to get this info.
+ */
+ if (!of_property_read_u32(iface->of_node, "hid-country-code", &country_code))
+ hid->country = country_code;
+
+ of_property_read_u32(iface->of_node, "apple,keyboard-layout-id",
+ &iface->keyboard_layout_id);
+ }
+
+ hid->dev.parent = iface->dchid->dev;
+ hid->driver_data = iface;
+
+ iface->hid = hid;
+
+ ret = hid_add_device(hid);
+ if (ret < 0) {
+ iface->hid = NULL;
+ hid_destroy_device(hid);
+ dev_warn(iface->dchid->dev, "Failed to register hid device %s", iface->name);
+ }
+}
+
+static int dchid_create_interface(struct dchid_iface *iface)
+{
+ if (iface->creating)
+ return -EBUSY;
+
+ iface->creating = true;
+ INIT_WORK(&iface->create_work, dchid_create_interface_work);
+ return queue_work(iface->dchid->new_iface_wq, &iface->create_work);
+}
+
+static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc, size_t desc_len)
+{
+ if (iface->hid) {
+ dev_warn(iface->dchid->dev, "Tried to initialize already started interface %s!\n",
+ iface->name);
+ return;
+ }
+
+ iface->hid_desc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len, GFP_KERNEL);
+ if (!iface->hid_desc)
+ return;
+
+ iface->hid_desc_len = desc_len;
+}
+
+static void dchid_handle_ready(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+ struct dchid_iface *iface;
+ u8 *pkt = data;
+ u8 index;
+ int i, ret;
+
+ if (length < 2) {
+ dev_err(dchid->dev, "Bad length for ready message: %zu\n", length);
+ return;
+ }
+
+ index = pkt[1];
+
+ if (index >= MAX_INTERFACES) {
+ dev_err(dchid->dev, "Got ready notification for bad iface %d\n", index);
+ return;
+ }
+
+ iface = dchid->ifaces[index];
+ if (!iface) {
+ dev_err(dchid->dev, "Got ready notification for unknown iface %d\n", index);
+ return;
+ }
+
+ dev_info(dchid->dev, "Interface %s is now ready\n", iface->name);
+ complete_all(&iface->ready);
+
+ /* When STM is ready, grab global device info */
+ if (!strcmp(iface->name, "stm")) {
+ ret = dchid_get_report_cmd(iface, STM_REPORT_ID, &dchid->device_id,
+ sizeof(dchid->device_id));
+ if (ret < sizeof(dchid->device_id)) {
+ dev_warn(iface->dchid->dev, "Failed to get device ID from STM!\n");
+ /* Fake it and keep going. Things might still work... */
+ memset(&dchid->device_id, 0, sizeof(dchid->device_id));
+ dchid->device_id.vendor_id = HOST_VENDOR_ID_APPLE;
+ }
+ ret = dchid_get_report_cmd(iface, STM_REPORT_SERIAL, dchid->serial,
+ sizeof(dchid->serial) - 1);
+ if (ret < 0) {
+ dev_warn(iface->dchid->dev, "Failed to get serial from STM!\n");
+ dchid->serial[0] = 0;
+ }
+
+ dchid->id_ready = true;
+ for (i = 0; i < MAX_INTERFACES; i++) {
+ if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred)
+ continue;
+ dchid_create_interface(dchid->ifaces[i]);
+ }
+ }
+}
+
+static void dchid_handle_init(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+ struct dchid_init_hdr *hdr = data;
+ struct dchid_iface *iface;
+ struct dchid_init_block_hdr *blk;
+
+ if (length < sizeof(*hdr))
+ return;
+
+ iface = dchid_get_interface(dchid, hdr->iface, hdr->name);
+ if (!iface)
+ return;
+
+ data += sizeof(*hdr);
+ length -= sizeof(*hdr);
+
+ while (length >= sizeof(*blk)) {
+ blk = data;
+ data += sizeof(*blk);
+ length -= sizeof(*blk);
+
+ if (blk->length > length)
+ break;
+
+ switch (blk->type) {
+ case INIT_HID_DESCRIPTOR:
+ dchid_handle_descriptor(iface, data, blk->length);
+ break;
+
+ case INIT_GPIO_REQUEST: {
+ struct dchid_gpio_request *req = data;
+
+ if (sizeof(*req) > length)
+ break;
+
+ if (iface->gpio_id) {
+ dev_err(dchid->dev,
+ "Cannot request more than one GPIO per interface!\n");
+ break;
+ }
+
+ strscpy(iface->gpio_name, req->name, MAX_GPIO_NAME);
+ iface->gpio_id = req->id;
+ break;
+ }
+
+ case INIT_TERMINATOR:
+ break;
+
+ case INIT_PRODUCT_NAME: {
+ char *product = data;
+
+ if (product[blk->length - 1] != 0) {
+ dev_warn(dchid->dev, "Unterminated product name for %s\n",
+ iface->name);
+ } else {
+ dev_info(dchid->dev, "Product name for %s: %s\n",
+ iface->name, product);
+ }
+ break;
+ }
+
+ default:
+ dev_warn(dchid->dev, "Unknown init packet %d for %s\n",
+ blk->type, iface->name);
+ break;
+ }
+
+ data += blk->length;
+ length -= blk->length;
+
+ if (blk->type == INIT_TERMINATOR)
+ break;
+ }
+
+ if (hdr->more_packets)
+ return;
+
+ /* We need to enable STM first, since it'll give us the device IDs */
+ if (iface->dchid->id_ready || !strcmp(iface->name, "stm")) {
+ dchid_create_interface(iface);
+ } else {
+ iface->deferred = true;
+ }
+}
+
+static void dchid_handle_gpio(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+ struct dchid_gpio_cmd *cmd = data;
+ struct dchid_iface *iface;
+ u32 retcode = 0xe000f00d; /* Give it a random Apple-style error code */
+ struct dchid_gpio_ack *ack;
+
+ if (length < sizeof(*cmd))
+ return;
+
+ if (cmd->iface >= MAX_INTERFACES || !(iface = dchid->ifaces[cmd->iface])) {
+ dev_err(dchid->dev, "Got GPIO command for bad inteface %d\n", cmd->iface);
+ goto err;
+ }
+
+ if (dchid_request_gpio(iface) < 0)
+ goto err;
+
+ if (!iface->gpio || cmd->gpio != iface->gpio_id) {
+ dev_err(dchid->dev, "Got GPIO command for bad GPIO %s#%d\n",
+ iface->name, cmd->gpio);
+ goto err;
+ }
+
+ dev_info(dchid->dev, "GPIO command: %s#%d: %d\n", iface->name, cmd->gpio, cmd->cmd);
+
+ switch (cmd->cmd) {
+ case 3:
+ /* Pulse. */
+ gpiod_set_value_cansleep(iface->gpio, 1);
+ msleep(10); /* Random guess... */
+ gpiod_set_value_cansleep(iface->gpio, 0);
+ retcode = 0;
+ break;
+ default:
+ dev_err(dchid->dev, "Unknown GPIO command %d\n", cmd->cmd );
+ break;
+ }
+
+err:
+ /* Ack it */
+ ack = kzalloc(sizeof(*ack) + length, GFP_KERNEL);
+ if (!ack)
+ return;
+
+ ack->type = CMD_ACK_GPIO_CMD;
+ ack->retcode = retcode;
+ memcpy(ack->cmd, data, length);
+
+ if (dchid_comm_cmd(dchid, ack, sizeof(*ack) + length) < 0)
+ dev_err(dchid->dev, "Failed to ACK GPIO command\n");
+
+ kfree(ack);
+}
+
+static void dchid_handle_event(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+ u8 *p = data;
+ switch (*p) {
+ case EVENT_INIT:
+ dchid_handle_init(dchid, data, length);
+ break;
+ case EVENT_READY:
+ dchid_handle_ready(dchid, data, length);
+ break;
+ case EVENT_GPIO_CMD:
+ dchid_handle_gpio(dchid, data, length);
+ break;
+ }
+}
+
+static void dchid_handle_report(struct dchid_iface *iface, void *data, size_t length)
+{
+ struct dockchannel_hid *dchid = iface->dchid;
+
+ if (!iface->hid) {
+ dev_warn(dchid->dev, "Report received but %s is not initialized!\n", iface->name);
+ return;
+ }
+
+ if (!iface->open)
+ return;
+
+ hid_input_report(iface->hid, HID_INPUT_REPORT, data, length, 1);
+}
+
+static void dchid_packet_work(struct work_struct *ws)
+{
+ struct dchid_work *work = container_of(ws, struct dchid_work, work);
+ struct dchid_subhdr *shdr = (void *)work->data;
+ struct dockchannel_hid *dchid = work->iface->dchid;
+ int type = FIELD_GET(FLAGS_GROUP, shdr->flags);
+ u8 *payload = work->data + sizeof(*shdr);
+
+ if (shdr->length + sizeof(*shdr) > work->hdr.length) {
+ dev_err(dchid->dev, "Bad sub header length (%d > %zu)\n",
+ shdr->length, work->hdr.length - sizeof(*shdr));
+ return;
+ }
+
+ switch (type) {
+ case HID_INPUT_REPORT:
+ if (work->hdr.iface == IFACE_COMM)
+ dchid_handle_event(dchid, payload, shdr->length);
+ else
+ dchid_handle_report(work->iface, payload, shdr->length);
+ break;
+ default:
+ dev_err(dchid->dev, "Received unknown packet type %d\n", type);
+ break;
+ }
+
+ kfree(work);
+}
+
+static void dchid_handle_ack(struct dchid_iface *iface, struct dchid_hdr *hdr, void *data)
+{
+ struct dchid_subhdr *shdr = (void *)data;
+ u8 *payload = data + sizeof(*shdr);
+
+ if (shdr->length + sizeof(*shdr) > hdr->length) {
+ dev_err(iface->dchid->dev, "Bad sub header length (%d > %ld)\n",
+ shdr->length, hdr->length - sizeof(*shdr));
+ return;
+ }
+ if (shdr->flags != iface->out_flags) {
+ dev_err(iface->dchid->dev,
+ "Received unexpected flags 0x%x on ACK channel (expFected 0x%x)\n",
+ shdr->flags, iface->out_flags);
+ return;
+ }
+
+ if (shdr->length < 1) {
+ dev_err(iface->dchid->dev, "Received length 0 output report ack\n");
+ return;
+ }
+ if (iface->tx_seq != hdr->seq) {
+ dev_err(iface->dchid->dev, "Received ACK with bad seq (expected %d, got %d)\n",
+ iface->tx_seq, hdr->seq);
+ return;
+ }
+ if (iface->out_report != payload[0]) {
+ dev_err(iface->dchid->dev, "Received ACK with bad report (expected %d, got %d\n",
+ iface->out_report, payload[0]);
+ return;
+ }
+
+ if (iface->resp_buf && iface->resp_size)
+ memcpy(iface->resp_buf, payload + 1, min((size_t)shdr->length - 1, iface->resp_size));
+
+ iface->resp_size = shdr->length;
+ iface->out_report = -1;
+ iface->retcode = shdr->retcode;
+ complete(&iface->out_complete);
+}
+
+static void dchid_handle_packet(void *cookie, size_t avail)
+{
+ struct dockchannel_hid *dchid = cookie;
+ struct dchid_hdr hdr;
+ struct dchid_work *work;
+ struct dchid_iface *iface;
+ u32 checksum;
+
+ if (dockchannel_recv(dchid->dc, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+ dev_err(dchid->dev, "Read failed (header)\n");
+ return;
+ }
+
+ if (hdr.hdr_len != sizeof(hdr)) {
+ dev_err(dchid->dev, "Bad header length %d\n", hdr.hdr_len);
+ goto done;
+ }
+
+ if (dockchannel_recv(dchid->dc, dchid->pkt_buf, hdr.length + 4) != (hdr.length + 4)) {
+ dev_err(dchid->dev, "Read failed (body)\n");
+ goto done;
+ }
+
+ checksum = dchid_checksum(&hdr, sizeof(hdr));
+ checksum += dchid_checksum(dchid->pkt_buf, hdr.length + 4);
+
+ if (checksum != 0xffffffff) {
+ dev_err(dchid->dev, "Checksum mismatch (iface %d): 0x%08x != 0xffffffff\n",
+ hdr.iface, checksum);
+ goto done;
+ }
+
+
+ if (hdr.iface >= MAX_INTERFACES) {
+ dev_err(dchid->dev, "Bad iface %d\n", hdr.iface);
+ }
+
+ iface = dchid->ifaces[hdr.iface];
+
+ if (!iface) {
+ dev_err(dchid->dev, "Received packet for uninitialized iface %d\n", hdr.iface);
+ goto done;
+ }
+
+ switch (hdr.channel) {
+ case DCHID_CHANNEL_CMD:
+ dchid_handle_ack(iface, &hdr, dchid->pkt_buf);
+ goto done;
+ case DCHID_CHANNEL_REPORT:
+ break;
+ default:
+ dev_warn(dchid->dev, "Unknown channel 0x%x, treating as report...\n",
+ hdr.channel);
+ break;
+ }
+
+ work = kzalloc(sizeof(*work) + hdr.length, GFP_KERNEL);
+ if (!work)
+ return;
+
+ work->hdr = hdr;
+ work->iface = iface;
+ memcpy(work->data, dchid->pkt_buf, hdr.length);
+ INIT_WORK(&work->work, dchid_packet_work);
+
+ queue_work(iface->wq, &work->work);
+
+done:
+ dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr));
+}
+
+static int dockchannel_hid_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dockchannel_hid *dchid;
+ struct device_node *child, *helper;
+ struct platform_device *helper_pdev;
+ struct property *prop;
+ int ret;
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ if (ret)
+ return ret;
+
+ dchid = devm_kzalloc(dev, sizeof(*dchid), GFP_KERNEL);
+ if (!dchid) {
+ return -ENOMEM;
+ }
+
+ dchid->dev = dev;
+
+ /*
+ * First make sure all the GPIOs are available, in cased we need to defer.
+ * This is necessary because MTP will request them by name later, and by then
+ * it's too late to defer the probe.
+ */
+
+ for_each_child_of_node(dev->of_node, child) {
+ for_each_property_of_node(child, prop) {
+ size_t len = strlen(prop->name);
+ struct gpio_desc *gpio;
+
+ if (len < 12 || strncmp("apple,", prop->name, 6) ||
+ strcmp("-gpios", prop->name + len - 6))
+ continue;
+
+ gpio = fwnode_gpiod_get_index(&child->fwnode, prop->name, 0, GPIOD_ASIS,
+ prop->name);
+ if (IS_ERR_OR_NULL(gpio)) {
+ if (PTR_ERR(gpio) == -EPROBE_DEFER) {
+ of_node_put(child);
+ return -EPROBE_DEFER;
+ }
+ } else {
+ gpiod_put(gpio);
+ }
+ }
+ }
+
+ /*
+ * Make sure we also have the MTP coprocessor available, and
+ * defer probe if the helper hasn't probed yet.
+ */
+ helper = of_parse_phandle(dev->of_node, "apple,helper-cpu", 0);
+ if (!helper) {
+ dev_err(dev, "Missing apple,helper-cpu property");
+ return -EINVAL;
+ }
+
+ helper_pdev = of_find_device_by_node(helper);
+ of_node_put(helper);
+ if (!helper_pdev) {
+ dev_err(dev, "Failed to find helper device");
+ return -EINVAL;
+ }
+
+ dchid->helper_link = device_link_add(dev, &helper_pdev->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ put_device(&helper_pdev->dev);
+ if (!dchid->helper_link) {
+ dev_err(dev, "Failed to link to helper device");
+ return -EINVAL;
+ }
+
+ if (dchid->helper_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
+ return -EPROBE_DEFER;
+
+ /* Now it is safe to begin initializing */
+ dchid->dc = dockchannel_init(pdev);
+ if (IS_ERR_OR_NULL(dchid->dc)) {
+ return PTR_ERR(dchid->dc);
+ }
+ dchid->new_iface_wq = alloc_workqueue("dchid-new", WQ_MEM_RECLAIM, 0);
+ if (!dchid->new_iface_wq)
+ return -ENOMEM;
+
+ dchid->comm = dchid_get_interface(dchid, IFACE_COMM, "comm");
+ if (!dchid->comm) {
+ dev_err(dchid->dev, "Failed to initialize comm interface");
+ return -EIO;
+ }
+
+ dev_info(dchid->dev, "Initialized, awaiting packets\n");
+ dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr));
+
+ return 0;
+}
+
+static void dockchannel_hid_remove(struct platform_device *pdev)
+{
+ BUG_ON(1);
+}
+
+static const struct of_device_id dockchannel_hid_of_match[] = {
+ { .compatible = "apple,dockchannel-hid" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dockchannel_hid_of_match);
+MODULE_FIRMWARE("apple/tpmtfw-*.bin");
+
+static struct platform_driver dockchannel_hid_driver = {
+ .driver = {
+ .name = "dockchannel-hid",
+ .of_match_table = dockchannel_hid_of_match,
+ },
+ .probe = dockchannel_hid_probe,
+ .remove = dockchannel_hid_remove,
+};
+module_platform_driver(dockchannel_hid_driver);
+
+MODULE_DESCRIPTION("Apple DockChannel HID transport driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 3 Jul 2022 23:33:37 +0900
Subject: 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 <marcan@marcan.st>
---
drivers/soc/apple/Kconfig | 14 +
drivers/soc/apple/Makefile | 3 +
drivers/soc/apple/rtkit-helper.c | 151 ++++++++++
3 files changed, 168 insertions(+)
diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/soc/apple/Kconfig
+++ b/drivers/soc/apple/Kconfig
@@ -38,6 +38,20 @@ config APPLE_RTKIT
Say 'y' here if you have an Apple SoC.
+config APPLE_RTKIT_HELPER
+ tristate "Apple Generic RTKit helper co-processor"
+ depends on APPLE_RTKIT
+ depends on ARCH_APPLE || COMPILE_TEST
+ default ARCH_APPLE
+ help
+ Apple SoCs such as the M1 come with various co-processors running
+ their proprietary RTKit operating system. This option enables support
+ for a generic co-processor that does not implement any additional
+ in-band communications. It can be used for testing purposes, or for
+ coprocessors such as MTP that communicate over a different interface.
+
+ Say 'y' here if you have an Apple SoC.
+
config APPLE_SART
tristate "Apple SART DMA address filter"
depends on ARCH_APPLE || COMPILE_TEST
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
index 111111111111..222222222222 100644
--- a/drivers/soc/apple/Makefile
+++ b/drivers/soc/apple/Makefile
@@ -9,5 +9,8 @@ apple-mailbox-y = mailbox.o
obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o
apple-rtkit-y = rtkit.o rtkit-crashlog.o
+obj-$(CONFIG_APPLE_RTKIT_HELPER) += apple-rtkit-helper.o
+apple-rtkit-helper-y = rtkit-helper.o
+
obj-$(CONFIG_APPLE_SART) += apple-sart.o
apple-sart-y = sart.o
diff --git a/drivers/soc/apple/rtkit-helper.c b/drivers/soc/apple/rtkit-helper.c
new file mode 100644
index 000000000000..111111111111
--- /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 <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/soc/apple/rtkit.h>
+
+#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 <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple RTKit helper driver");
--
Armbian