5053 lines
143 KiB
Diff
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
|
|
|