summaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig12
-rw-r--r--drivers/hid/Makefile4
-rw-r--r--drivers/hid/dockchannel-hid/Kconfig14
-rw-r--r--drivers/hid/dockchannel-hid/Makefile6
-rw-r--r--drivers/hid/dockchannel-hid/dockchannel-hid.c1152
-rw-r--r--drivers/hid/hid-apple.c64
-rw-r--r--drivers/hid/hid-core.c6
-rw-r--r--drivers/hid/hid-ids.h8
-rw-r--r--drivers/hid/hid-magicmouse.c334
-rw-r--r--drivers/hid/spi-hid/Kconfig26
-rw-r--r--drivers/hid/spi-hid/Makefile10
-rw-r--r--drivers/hid/spi-hid/spi-hid-apple-core.c1029
-rw-r--r--drivers/hid/spi-hid/spi-hid-apple-of.c138
-rw-r--r--drivers/hid/spi-hid/spi-hid-apple.h31
14 files changed, 2822 insertions, 12 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 185a077d59cd..2197006033c4 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -122,7 +122,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.
@@ -648,11 +648,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"
@@ -1005,7 +1007,7 @@ config HID_SONY
* Guitar Hero PS3 and PC guitar dongles
config SONY_FF
- bool "Sony PS2/3/4 accessories force feedback support"
+ bool "Sony PS2/3/4 accessories force feedback support"
depends on HID_SONY
select INPUT_FF_MEMLESS
help
@@ -1290,4 +1292,8 @@ source "drivers/hid/amd-sfh-hid/Kconfig"
source "drivers/hid/surface-hid/Kconfig"
+source "drivers/hid/spi-hid/Kconfig"
+
+source "drivers/hid/dockchannel-hid/Kconfig"
+
endmenu
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e8014c1a2f8b..2fb1a40657ae 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -164,3 +164,7 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
+
+obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/
+
+obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/
diff --git a/drivers/hid/dockchannel-hid/Kconfig b/drivers/hid/dockchannel-hid/Kconfig
new file mode 100644
index 000000000000..8a81d551a83d
--- /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..7dba766b047f
--- /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..d9cec1276001
--- /dev/null
+++ b/drivers/hid/dockchannel-hid/dockchannel-hid.c
@@ -0,0 +1,1152 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0 OR MIT
+ *
+ * Apple DockChannel HID transport driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+#include <asm/unaligned.h>
+#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/slab.h>
+#include <linux/soc/apple/dockchannel.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;
+ __le16 length;
+ u8 seq;
+ u8 iface;
+ __le16 pad;
+} __packed;
+
+#define IFACE_COMM 0
+
+#define FLAGS_GROUP GENMASK(7, 6)
+#define FLAGS_REQ GENMASK(5, 0)
+
+#define GROUP_INPUT 0
+#define GROUP_OUTPUT 1
+#define GROUP_CMD 2
+
+#define REQ_SET_REPORT 0
+#define REQ_GET_REPORT 1
+
+struct dchid_subhdr {
+ u8 flags;
+ u8 unk;
+ __le16 length;
+ __le32 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];
+} __packed;
+
+#define INIT_HID_DESCRIPTOR 0
+#define INIT_GPIO_REQUEST 1
+#define INIT_TERMINATOR 2
+
+#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 {
+ __le16 type;
+ __le16 subtype;
+ __le16 length;
+} __packed;
+
+#define MAX_GPIO_NAME 32
+
+struct dchid_gpio_request {
+ __le16 unk;
+ __le16 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;
+ __le32 retcode;
+ u8 cmd[];
+} __packed;
+
+#define STM_REPORT_ID 0x10
+#define STM_REPORT_SERIAL 0x11
+#define STM_REPORT_KEYBTYPE 0x14
+
+#define KEYBOARD_TYPE_ANSI 0
+#define KEYBOARD_TYPE_ISO 1
+#define KEYBOARD_TYPE_JIS 2
+
+struct dchid_stm_id {
+ u8 unk;
+ __le16 vendor_id;
+ __le16 product_id;
+ __le16 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 {
+ __le32 magic;
+ __le32 version;
+ __le32 hdr_length;
+ __le32 data_length;
+ __le32 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;
+ 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;
+};
+
+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 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, GROUP_CMD, 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_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;
+
+ /* 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)
+{
+ return 0;
+};
+
+static void dchid_stop(struct hid_device *hdev)
+{
+ /* no-op, we don't know what the shutdown commands are, if any */
+}
+
+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, GROUP_CMD, 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, GROUP_OUTPUT, 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, GROUP_OUTPUT, 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")) {
+ hid->type = HID_TYPE_SPI_KEYBOARD;
+
+ /*
+ * These country codes match what earlier Apple HID keyboards did.
+ * Apple seems to allocate keyboard IDs in groups of 3 (for the 3
+ * layout groups), hence the % 3.
+ */
+ switch (dchid->device_id.keyboard_type % 3) {
+ case KEYBOARD_TYPE_ANSI:
+ hid->country = 33; // US-English
+ break;
+
+ case KEYBOARD_TYPE_ISO:
+ hid->country = 13; // ISO
+ break;
+
+ case KEYBOARD_TYPE_JIS:
+ hid->country = 15; // Japan
+ break;
+ }
+ }
+
+ 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;
+
+ /* 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_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_request_gpio(struct dchid_iface *iface, int id, const char *name)
+{
+ char prop_name[MAX_GPIO_NAME + 16];
+
+ dev_info(iface->dchid->dev, "Requesting GPIO %s#%d: %s\n", iface->name, id, name);
+
+ if (iface->gpio) {
+ dev_err(iface->dchid->dev, "Cannot request more than one GPIO per interface!\n");
+ return;
+ }
+
+ snprintf(prop_name, sizeof(prop_name), "apple,%s-gpios", name);
+
+ iface->gpio = devm_gpiod_get_from_of_node(iface->dchid->dev,
+ iface->of_node, prop_name, 0,
+ GPIOD_OUT_LOW, name);
+
+ if (IS_ERR_OR_NULL(iface->gpio)) {
+ dev_err(iface->dchid->dev, "Failed to request GPIO %s\n", prop_name);
+ iface->gpio = NULL;
+ return;
+ }
+
+ iface->gpio_id = id;
+}
+
+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)
+ return;
+ 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)
+ return;
+ dchid_request_gpio(iface, req->id, req->name);
+ break;
+ }
+
+ case INIT_TERMINATOR:
+ return;
+ }
+
+ data += blk->length + sizeof(*blk);
+ length -= blk->length + sizeof(*blk);
+ }
+}
+
+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 (!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 GROUP_INPUT:
+ 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;
+
+ 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 = gpiod_get_from_of_node(child, 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 int dockchannel_hid_remove(struct platform_device *pdev)
+{
+ BUG_ON(1);
+ return 0;
+}
+
+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");
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 6970797cdc56..266e7704523d 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -256,6 +256,50 @@ static const struct apple_key_translation apple_fn_keys[] = {
{ }
};
+static const struct apple_key_translation apple_fn_keys_spi[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_SEARCH, APPLE_FLAG_FKEY },
+ { KEY_F5, KEY_RECORD, APPLE_FLAG_FKEY },
+ { KEY_F6, KEY_SLEEP, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation apple_fn_keys_mbp13[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { KEY_1, KEY_F1 },
+ { KEY_2, KEY_F2 },
+ { KEY_3, KEY_F3 },
+ { KEY_4, KEY_F4 },
+ { KEY_5, KEY_F5 },
+ { KEY_6, KEY_F6 },
+ { KEY_7, KEY_F7 },
+ { KEY_8, KEY_F8 },
+ { KEY_9, KEY_F9 },
+ { KEY_0, KEY_F10 },
+ { KEY_MINUS, KEY_F11 },
+ { KEY_EQUAL, KEY_F12 },
+ { }
+};
+
static const struct apple_key_translation powerbook_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
@@ -425,6 +469,16 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
table = macbookair_fn_keys;
+ else if (hid->bus == BUS_HOST || hid->bus == BUS_SPI)
+ switch (hid->product) {
+ case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020:
+ case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022:
+ table = apple_fn_keys_mbp13;
+ break;
+ default:
+ table = apple_fn_keys_spi;
+ break;
+ }
else if (hid->product < 0x21d || hid->product >= 0x300)
table = powerbook_fn_keys;
else
@@ -632,6 +686,8 @@ static void apple_setup_input(struct input_dev *input)
/* Enable all needed keys */
apple_setup_key_translation(input, apple_fn_keys);
+ apple_setup_key_translation(input, apple_fn_keys_spi);
+ apple_setup_key_translation(input, apple_fn_keys_mbp13);
apple_setup_key_translation(input, powerbook_fn_keys);
apple_setup_key_translation(input, powerbook_numlock_keys);
apple_setup_key_translation(input, apple_iso_keyboard);
@@ -808,6 +864,10 @@ static int apple_probe(struct hid_device *hdev,
struct apple_sc *asc;
int ret;
+ if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && 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");
@@ -1049,6 +1109,10 @@ static const struct hid_device_id apple_devices[] = {
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = APPLE_HAS_FN },
+ { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE,
+ HID_ANY_ID), .driver_data = APPLE_HAS_FN },
{ }
};
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 9c1d31f63f85..2029480cc88d 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2221,6 +2221,12 @@ 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_HOST:
+ bus = "HOST";
+ break;
case BUS_VIRTUAL:
bus = "VIRTUAL";
break;
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index dad953f66996..d317aaa8bb38 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -89,6 +89,8 @@
#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
@@ -187,6 +189,12 @@
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021 0x029f
#define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102
#define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302
+#define SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020 0x0281
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020 0x0341
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021 0x0342
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021 0x0343
+#define HOST_DEVICE_ID_APPLE_MACBOOK_AIR13_2022 0x0351
+#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022 0x0354
#define USB_VENDOR_ID_ASUS 0x0486
#define USB_DEVICE_ID_ASUS_T91MT 0x0185
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index c9c968d4b36a..b22ab0f320c0 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -59,8 +59,12 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
#define MOUSE_REPORT_ID 0x29
#define MOUSE2_REPORT_ID 0x12
#define DOUBLE_REPORT_ID 0xf7
+#define SPI_REPORT_ID 0x02
+#define MTP_REPORT_ID 0x75
#define USB_BATTERY_TIMEOUT_MS 60000
+#define MAX_CONTACTS 16
+
/* 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,
@@ -111,6 +115,25 @@ 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,
+ 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.
@@ -129,9 +152,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;
@@ -139,12 +161,13 @@ 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;
struct timer_list battery_timer;
+ struct magicmouse_input_ops input_ops;
};
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -188,7 +211,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)
@@ -249,8 +272,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
@@ -374,6 +397,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;
@@ -502,6 +533,175 @@ static int magicmouse_raw_event(struct hid_device *hdev,
return 1;
}
+/**
+ * struct tp_finger - single trackpad finger structure, le16-aligned
+ *
+ * @unknown1: unknown
+ * @unknown2: unknown
+ * @abs_x: absolute x coordinate
+ * @abs_y: absolute y coordinate
+ * @rel_x: relative x coordinate
+ * @rel_y: relative y coordinate
+ * @tool_major: tool area, major axis
+ * @tool_minor: tool area, minor axis
+ * @orientation: 16384 when point, else 15 bit angle
+ * @touch_major: touch area, major axis
+ * @touch_minor: touch area, minor axis
+ * @unused: zeros
+ * @pressure: pressure on forcetouch touchpad
+ * @multi: one finger: varies, more fingers: constant
+ * @crc16: on last finger: crc over the whole message struct
+ * (i.e. message header + this struct) minus the last
+ * @crc16 field; unknown on all other fingers.
+ */
+struct tp_finger {
+ __le16 unknown1;
+ __le16 unknown2;
+ __le16 abs_x;
+ __le16 abs_y;
+ __le16 rel_x;
+ __le16 rel_y;
+ __le16 tool_major;
+ __le16 tool_minor;
+ __le16 orientation;
+ __le16 touch_major;
+ __le16 touch_minor;
+ __le16 unused[2];
+ __le16 pressure;
+ __le16 multi;
+} __attribute__((packed, aligned(2)));
+
+/**
+ * vendor trackpad report
+ *
+ * @num_fingers: the number of fingers being reported in @fingers
+ * @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];
+};
+
+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_mtp(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);
+
+ /* 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, tp_hdr->buttons & 1);
+
+ input_sync(input);
+ return 1;
+}
+
+static int magicmouse_raw_event_spi(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ const size_t hdr_sz = sizeof(struct tp_mouse_report);
+
+ if (size < hdr_sz)
+ return 0;
+
+ if (data[0] != TRACKPAD2_USB_REPORT_ID)
+ return 0;
+
+ return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
+}
+
static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
@@ -519,7 +719,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;
@@ -592,7 +802,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,
@@ -671,6 +881,79 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
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)
@@ -726,6 +1009,9 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev)
feature_size = sizeof(feature_mt_trackpad2_usb);
feature = feature_mt_trackpad2_usb;
}
+ } else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
+ feature_size = sizeof(feature_mt_trackpad2_usb);
+ feature = feature_mt_trackpad2_usb;
} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
feature_size = sizeof(feature_mt_mouse2);
feature = feature_mt_mouse2;
@@ -800,12 +1086,30 @@ static int magicmouse_probe(struct hid_device *hdev,
struct hid_report *report;
int ret;
+ if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE &&
+ hdev->type != HID_TYPE_SPI_MOUSE)
+ return -ENODEV;
+
msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
if (msc == NULL) {
hid_err(hdev, "can't alloc magicmouse descriptor\n");
return -ENOMEM;
}
+ // internal trackpad use a data format use input ops to avoid
+ // 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;
+ } 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 {
+ 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);
@@ -854,6 +1158,10 @@ static int magicmouse_probe(struct hid_device *hdev,
else /* USB_VENDOR_ID_APPLE */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD2_USB_REPORT_ID, 0);
+ } else if (id->bus == BUS_SPI) {
+ report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0);
+ } else if (id->bus == BUS_HOST) {
+ report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0);
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD_REPORT_ID, 0);
@@ -868,6 +1176,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.
@@ -948,6 +1260,10 @@ static const struct hid_device_id magic_mice[] = {
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .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);
diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
new file mode 100644
index 000000000000..8e37f0fec28a
--- /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..f276ee12cb94
--- /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..3b0bb65e357a
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple-core.c
@@ -0,0 +1,1029 @@
+/*
+ * 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 <asm/unaligned.h>
+#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/wait.h>
+
+#include "spi-hid-apple.h"
+
+#define SPIHID_DEF_WAIT msecs_to_jiffies(100)
+
+#define SPIHID_MAX_INPUT_REPORT_SIZE 0x800
+
+/* support only keyboard, trackpad and management dev for now */
+#define SPIHID_MAX_DEVICES 3
+
+#define SPIHID_DEVICE_ID_MNGT 0x0
+#define SPIHID_DEVICE_ID_KBD 0x1
+#define SPIHID_DEVICE_ID_TP 0x2
+#define SPIHID_DEVICE_ID_INFO 0xd0
+
+#define SPIHID_READ_PACKET 0x20
+#define SPIHID_WRITE_PACKET 0x40
+
+#define SPIHID_DESC_MAX 512
+
+#define SPIHID_SET_LEDS 0x0151 /* caps lock */
+
+#define SPI_RW_CHG_DELAY_US 200 /* 'Inter Stage Us'? */
+
+static const u8 spi_hid_apple_booted[4] = { 0xa0, 0x80, 0x00, 0x00 };
+static const u8 spi_hid_apple_status_ok[4] = { 0xac, 0x27, 0x68, 0xd5 };
+
+struct spihid_interface {
+ struct hid_device *hid;
+ u8 *hid_desc;
+ u32 hid_desc_len;
+ u32 id;
+ unsigned country;
+ u32 max_control_report_len;
+ u32 max_input_report_len;
+ u32 max_output_report_len;
+ u8 name[32];
+ bool ready;
+};
+
+struct spihid_input_report {
+ u8 *buf;
+ u32 length;
+ u32 offset;
+ u8 device;
+ u8 flags;
+};
+
+struct spihid_apple {
+ struct spi_device *spidev;
+
+ struct spihid_apple_ops *ops;
+
+ struct spihid_interface mngt;
+ struct spihid_interface kbd;
+ struct spihid_interface tp;
+
+ wait_queue_head_t wait;
+ struct mutex tx_lock; //< protects against concurrent SPI writes
+
+ struct spi_message rx_msg;
+ struct spi_message tx_msg;
+ struct spi_transfer rx_transfer;
+ struct spi_transfer tx_transfer;
+ struct spi_transfer status_transfer;
+
+ u8 *rx_buf;
+ u8 *tx_buf;
+ u8 *status_buf;
+
+ u8 vendor[32];
+ u8 product[64];
+ u8 serial[32];
+
+ u32 num_devices;
+
+ u32 vendor_id;
+ u32 product_id;
+ u32 version_number;
+
+ u8 msg_id;
+
+ /* fragmented HID report */
+ struct spihid_input_report report;
+
+ /* state tracking flags */
+ bool status_booted;
+};
+
+/**
+ * struct spihid_msg_hdr - common header of protocol messages.
+ *
+ * Each message begins with fixed header, followed by a message-type specific
+ * payload, and ends with a 16-bit crc. Because of the varying lengths of the
+ * payload, the crc is defined at the end of each payload struct, rather than
+ * in this struct.
+ *
+ * @unknown0: request type? output, input (0x10), feature, protocol
+ * @unknown1: maybe report id?
+ * @unknown2: mostly zero, in info request maybe device num
+ * @msgid: incremented on each message, rolls over after 255; there is a
+ * separate counter for each message type.
+ * @rsplen: response length (the exact nature of this field is quite
+ * speculative). On a request/write this is often the same as
+ * @length, though in some cases it has been seen to be much larger
+ * (e.g. 0x400); on a response/read this the same as on the
+ * request; for reads that are not responses it is 0.
+ * @length: length of the remainder of the data in the whole message
+ * structure (after re-assembly in case of being split over
+ * multiple spi-packets), minus the trailing crc. The total size
+ * of a message is therefore @length + 10.
+ */
+
+struct spihid_msg_hdr {
+ u8 unknown0;
+ u8 unknown1;
+ u8 unknown2;
+ u8 id;
+ __le16 rsplen;
+ __le16 length;
+};
+
+/**
+ * struct spihid_transfer_packet - a complete spi packet; always 256 bytes. This carries
+ * the (parts of the) message in the data. But note that this does not
+ * necessarily contain a complete message, as in some cases (e.g. many
+ * fingers pressed) the message is split over multiple packets (see the
+ * @offset, @remain, and @length fields). In general the data parts in
+ * spihid_transfer_packet's are concatenated until @remaining is 0, and the
+ * result is an message.
+ *
+ * @flags: 0x40 = write (to device), 0x20 = read (from device); note that
+ * the response to a write still has 0x40.
+ * @device: 1 = keyboard, 2 = touchpad
+ * @offset: specifies the offset of this packet's data in the complete
+ * message; i.e. > 0 indicates this is a continuation packet (in
+ * the second packet for a message split over multiple packets
+ * this would then be the same as the @length in the first packet)
+ * @remain: number of message bytes remaining in subsequents packets (in
+ * the first packet of a message split over two packets this would
+ * then be the same as the @length in the second packet)
+ * @length: length of the valid data in the @data in this packet
+ * @data: all or part of a message
+ * @crc16: crc over this whole structure minus this @crc16 field. This
+ * covers just this packet, even on multi-packet messages (in
+ * contrast to the crc in the message).
+ */
+struct spihid_transfer_packet {
+ u8 flags;
+ u8 device;
+ __le16 offset;
+ __le16 remain;
+ __le16 length;
+ u8 data[246];
+ __le16 crc16;
+};
+
+/*
+ * how HID is mapped onto the protocol is not fully clear. This are the known
+ * reports/request:
+ *
+ * pkt.flags pkt.dev? msg.u0 msg.u1 msg.u2
+ * info 0x40 0xd0 0x20 0x01 0xd0
+ *
+ * info mngt: 0x40 0xd0 0x20 0x10 0x00
+ * info kbd: 0x40 0xd0 0x20 0x10 0x01
+ * info tp: 0x40 0xd0 0x20 0x10 0x02
+ *
+ * desc kbd: 0x40 0xd0 0x20 0x10 0x01
+ * desc trackpad: 0x40 0xd0 0x20 0x10 0x02
+ *
+ * mt mode: 0x40 0x02 0x52 0x02 0x00 set protocol?
+ * capslock led 0x40 0x01 0x51 0x01 0x00 output report
+ *
+ * report kbd: 0x20 0x01 0x10 0x01 0x00 input report
+ * report tp: 0x20 0x02 0x10 0x02 0x00 input report
+ *
+ */
+
+
+static int spihid_apple_request(struct spihid_apple *spihid, u8 target, u8 unk0,
+ u8 unk1, u8 unk2, u16 resp_len, u8 *buf,
+ size_t len)
+{
+ struct spihid_transfer_packet *pkt;
+ struct spihid_msg_hdr *hdr;
+ u16 crc;
+ int err;
+
+ /* know reports are small enoug to fit in a single packet */
+ if (len > sizeof(pkt->data) - sizeof(*hdr) - sizeof(__le16))
+ return -EINVAL;
+
+ err = mutex_lock_interruptible(&spihid->tx_lock);
+ if (err < 0)
+ return err;
+
+ pkt = (struct spihid_transfer_packet *)spihid->tx_buf;
+
+ memset(pkt, 0, sizeof(*pkt));
+ pkt->flags = SPIHID_WRITE_PACKET;
+ pkt->device = target;
+ pkt->length = cpu_to_le16(sizeof(*hdr) + len + sizeof(__le16));
+
+ hdr = (struct spihid_msg_hdr *)&pkt->data[0];
+ hdr->unknown0 = unk0;
+ hdr->unknown1 = unk1;
+ hdr->unknown2 = unk2;
+ hdr->id = spihid->msg_id++;
+ hdr->rsplen = cpu_to_le16(resp_len);
+ hdr->length = cpu_to_le16(len);
+
+ if (len)
+ memcpy(pkt->data + sizeof(*hdr), buf, len);
+ crc = crc16(0, &pkt->data[0], sizeof(*hdr) + len);
+ put_unaligned_le16(crc, pkt->data + sizeof(*hdr) + len);
+
+ pkt->crc16 = cpu_to_le16(crc16(0, spihid->tx_buf,
+ offsetof(struct spihid_transfer_packet, crc16)));
+
+ err = spi_sync(spihid->spidev, &spihid->tx_msg);
+ mutex_unlock(&spihid->tx_lock);
+ if (err < 0)
+ return err;
+
+ return (int)len;
+}
+
+static struct spihid_apple *spihid_get_data(struct spihid_interface *idev)
+{
+ switch (idev->id) {
+ case SPIHID_DEVICE_ID_KBD:
+ return container_of(idev, struct spihid_apple, kbd);
+ case SPIHID_DEVICE_ID_TP:
+ return container_of(idev, struct spihid_apple, tp);
+ default:
+ return NULL;
+ }
+}
+
+static int apple_ll_start(struct hid_device *hdev)
+{
+ /* no-op SPI transport is already setup */
+ return 0;
+};
+
+static void apple_ll_stop(struct hid_device *hdev)
+{
+ /* no-op, devices will be desstroyed on driver destruction */
+}
+
+static int apple_ll_open(struct hid_device *hdev)
+{
+ struct spihid_apple *spihid;
+ struct spihid_interface *idev = hdev->driver_data;
+
+ if (idev->hid_desc_len == 0) {
+ spihid = spihid_get_data(idev);
+ dev_warn(&spihid->spidev->dev,
+ "HID descriptor missing for dev %u", idev->id);
+ } else
+ idev->ready = true;
+
+ return 0;
+}
+
+static void apple_ll_close(struct hid_device *hdev)
+{
+ struct spihid_interface *idev = hdev->driver_data;
+ idev->ready = false;
+}
+
+static int apple_ll_parse(struct hid_device *hdev)
+{
+ struct spihid_interface *idev = hdev->driver_data;
+
+ return hid_parse_report(hdev, idev->hid_desc, idev->hid_desc_len);
+}
+
+static int apple_ll_raw_request(struct hid_device *hdev,
+ unsigned char reportnum, __u8 *buf, size_t len,
+ unsigned char rtype, int reqtype)
+{
+ struct spihid_interface *idev = hdev->driver_data;
+ struct spihid_apple *spihid = spihid_get_data(idev);
+
+ dev_dbg(&spihid->spidev->dev,
+ "apple_ll_raw_request: device:%u reportnum:%hhu rtype:%hhu",
+ idev->id, reportnum, rtype);
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ return -EINVAL; // spihid_get_raw_report();
+ case HID_REQ_SET_REPORT:
+ if (buf[0] != reportnum)
+ return -EINVAL;
+ if (reportnum != idev->id) {
+ dev_warn(&spihid->spidev->dev,
+ "device:%u reportnum:"
+ "%hhu mismatch",
+ idev->id, reportnum);
+ return -EINVAL;
+ }
+ return spihid_apple_request(spihid, idev->id, 0x52, reportnum, 0x00, 2, buf, len);
+ default:
+ return -EIO;
+ }
+}
+
+static int apple_ll_output_report(struct hid_device *hdev, __u8 *buf,
+ size_t len)
+{
+ struct spihid_interface *idev = hdev->driver_data;
+ struct spihid_apple *spihid = spihid_get_data(idev);
+ if (!spihid)
+ return -1;
+
+ dev_dbg(&spihid->spidev->dev,
+ "apple_ll_output_report: device:%u len:%zu:",
+ idev->id, len);
+ // second idev->id should maybe be buf[0]?
+ return spihid_apple_request(spihid, idev->id, 0x51, idev->id, 0x00, 0, buf, len);
+}
+
+static struct hid_ll_driver apple_hid_ll = {
+ .start = &apple_ll_start,
+ .stop = &apple_ll_stop,
+ .open = &apple_ll_open,
+ .close = &apple_ll_close,
+ .parse = &apple_ll_parse,
+ .raw_request = &apple_ll_raw_request,
+ .output_report = &apple_ll_output_report,
+};
+
+static struct spihid_interface *spihid_get_iface(struct spihid_apple *spihid,
+ u32 iface)
+{
+ switch (iface) {
+ case SPIHID_DEVICE_ID_MNGT:
+ return &spihid->mngt;
+ case SPIHID_DEVICE_ID_KBD:
+ return &spihid->kbd;
+ case SPIHID_DEVICE_ID_TP:
+ return &spihid->tp;
+ default:
+ return NULL;
+ }
+}
+
+static int spihid_verify_msg(struct spihid_apple *spihid, u8 *buf, size_t len)
+{
+ u16 msg_crc, crc;
+ struct device *dev = &spihid->spidev->dev;
+
+ crc = crc16(0, buf, len - sizeof(__le16));
+ msg_crc = get_unaligned_le16(buf + len - sizeof(__le16));
+ if (crc != msg_crc) {
+ dev_warn_ratelimited(dev, "Read message crc mismatch\n");
+ return 0;
+ }
+ return 1;
+}
+
+static bool spihid_status_report(struct spihid_apple *spihid, u8 *pl,
+ size_t len)
+{
+ struct device *dev = &spihid->spidev->dev;
+ dev_dbg(dev, "%s: len: %zu", __func__, len);
+ if (len == 5 && pl[0] == 0xe0)
+ return true;
+
+ return false;
+}
+
+static bool spihid_process_input_report(struct spihid_apple *spihid, u32 device,
+ struct spihid_msg_hdr *hdr, u8 *payload,
+ size_t len)
+{
+ //dev_dbg(&spihid>spidev->dev, "input report: req:%hx iface:%u ", hdr->unknown0, device);
+ if (hdr->unknown0 != 0x10)
+ return false;
+
+ /* HID device as well but Vendor usage only, handle it internally for now */
+ if (device == 0) {
+ if (hdr->unknown1 == 0xe0) {
+ return spihid_status_report(spihid, payload, len);
+ }
+ } else if (device < SPIHID_MAX_DEVICES) {
+ struct spihid_interface *iface =
+ spihid_get_iface(spihid, device);
+ if (iface && iface->hid && iface->ready) {
+ hid_input_report(iface->hid, HID_INPUT_REPORT, payload,
+ len, 1);
+ return true;
+ }
+ } else
+ dev_dbg(&spihid->spidev->dev,
+ "unexpected iface:%u for input report", device);
+
+ return false;
+}
+
+struct spihid_device_info {
+ __le16 u0[2];
+ __le16 num_devices;
+ __le16 vendor_id;
+ __le16 product_id;
+ __le16 version_number;
+ __le16 vendor_str[2]; //< offset and string length
+ __le16 product_str[2]; //< offset and string length
+ __le16 serial_str[2]; //< offset and string length
+};
+
+static bool spihid_process_device_info(struct spihid_apple *spihid, u32 iface,
+ u8 *payload, size_t len)
+{
+ struct device *dev = &spihid->spidev->dev;
+
+ if (iface != SPIHID_DEVICE_ID_INFO)
+ return false;
+
+ if (spihid->vendor_id == 0 &&
+ len >= sizeof(struct spihid_device_info)) {
+ struct spihid_device_info *info =
+ (struct spihid_device_info *)payload;
+ u16 voff, vlen, poff, plen, soff, slen;
+ u32 num_devices;
+
+ num_devices = __le16_to_cpu(info->num_devices);
+
+ if (num_devices < SPIHID_MAX_DEVICES) {
+ dev_err(dev,
+ "Device info reports %u devices, expecting at least 3",
+ num_devices);
+ return false;
+ }
+ spihid->num_devices = num_devices;
+
+ if (spihid->num_devices > SPIHID_MAX_DEVICES) {
+ dev_info(
+ dev,
+ "limiting the number of devices to mngt, kbd and mouse");
+ spihid->num_devices = SPIHID_MAX_DEVICES;
+ }
+
+ spihid->vendor_id = __le16_to_cpu(info->vendor_id);
+ spihid->product_id = __le16_to_cpu(info->product_id);
+ spihid->version_number = __le16_to_cpu(info->version_number);
+
+ voff = __le16_to_cpu(info->vendor_str[0]);
+ vlen = __le16_to_cpu(info->vendor_str[1]);
+
+ if (voff < len && vlen <= len - voff &&
+ vlen < sizeof(spihid->vendor)) {
+ memcpy(spihid->vendor, payload + voff, vlen);
+ spihid->vendor[vlen] = '\0';
+ }
+
+ poff = __le16_to_cpu(info->product_str[0]);
+ plen = __le16_to_cpu(info->product_str[1]);
+
+ if (poff < len && plen <= len - poff &&
+ plen < sizeof(spihid->product)) {
+ memcpy(spihid->product, payload + poff, plen);
+ spihid->product[plen] = '\0';
+ }
+
+ soff = __le16_to_cpu(info->serial_str[0]);
+ slen = __le16_to_cpu(info->serial_str[1]);
+
+ if (soff < len && slen <= len - soff &&
+ slen < sizeof(spihid->serial)) {
+ memcpy(spihid->vendor, payload + soff, slen);
+ spihid->serial[slen] = '\0';
+ }
+
+ wake_up_interruptible(&spihid->wait);
+ }
+ return true;
+}
+
+struct spihid_iface_info {
+ u8 u_0;
+ u8 interface_num;
+ u8 u_2;
+ u8 u_3;
+ u8 u_4;
+ u8 country_code;
+ __le16 max_input_report_len;
+ __le16 max_output_report_len;
+ __le16 max_control_report_len;
+ __le16 name_offset;
+ __le16 name_length;
+};
+
+static bool spihid_process_iface_info(struct spihid_apple *spihid, u32 num,
+ u8 *payload, size_t len)
+{
+ struct spihid_iface_info *info;
+ struct spihid_interface *iface = spihid_get_iface(spihid, num);
+ u32 name_off, name_len;
+
+ if (!iface)
+ return false;
+
+ if (!iface->max_input_report_len) {
+ if (len < sizeof(*info))
+ return false;
+
+ info = (struct spihid_iface_info *)payload;
+
+ iface->max_input_report_len =
+ le16_to_cpu(info->max_input_report_len);
+ iface->max_output_report_len =
+ le16_to_cpu(info->max_output_report_len);
+ iface->max_control_report_len =
+ le16_to_cpu(info->max_control_report_len);
+ iface->country = info->country_code;
+
+ name_off = le16_to_cpu(info->name_offset);
+ name_len = le16_to_cpu(info->name_length);
+
+ if (name_off < len && name_len <= len - name_off &&
+ name_len < sizeof(iface->name)) {
+ memcpy(iface->name, payload + name_off, name_len);
+ iface->name[name_len] = '\0';
+ }
+
+ dev_dbg(&spihid->spidev->dev, "Info for %s, country code: 0x%x",
+ iface->name, iface->country);
+
+ wake_up_interruptible(&spihid->wait);
+ }
+
+ return true;
+}
+
+static int spihid_register_hid_device(struct spihid_apple *spihid,
+ struct spihid_interface *idev, u8 device);
+
+static bool spihid_process_iface_hid_report_desc(struct spihid_apple *spihid,
+ u32 num, u8 *payload,
+ size_t len)
+{
+ struct spihid_interface *iface = spihid_get_iface(spihid, num);
+
+ if (!iface)
+ return false;
+
+ if (iface->hid_desc_len == 0) {
+ if (len > SPIHID_DESC_MAX)
+ return false;
+ memcpy(iface->hid_desc, payload, len);
+ iface->hid_desc_len = len;
+
+ /* do not register the mngt iface as HID device */
+ if (num > 0)
+ spihid_register_hid_device(spihid, iface, num);
+
+ wake_up_interruptible(&spihid->wait);
+ }
+ return true;
+}
+
+static bool spihid_process_response(struct spihid_apple *spihid,
+ struct spihid_msg_hdr *hdr, u8 *payload,
+ size_t len)
+{
+ if (hdr->unknown0 == 0x20) {
+ switch (hdr->unknown1) {
+ case 0x01:
+ return spihid_process_device_info(spihid, hdr->unknown2,
+ payload, len);
+ case 0x02:
+ return spihid_process_iface_info(spihid, hdr->unknown2,
+ payload, len);
+ case 0x10:
+ return spihid_process_iface_hid_report_desc(
+ spihid, hdr->unknown2, payload, len);
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+static void spihid_process_message(struct spihid_apple *spihid, u8 *data,
+ size_t length, u8 device, u8 flags)
+{
+ struct device *dev = &spihid->spidev->dev;
+ struct spihid_msg_hdr *hdr;
+ bool handled = false;
+ u8 *payload;
+
+ if (!spihid_verify_msg(spihid, data, length))
+ return;
+
+ hdr = (struct spihid_msg_hdr *)data;
+
+ if (hdr->length == 0)
+ return;
+
+ payload = data + sizeof(struct spihid_msg_hdr);
+
+ switch (flags) {
+ case SPIHID_READ_PACKET:
+ handled = spihid_process_input_report(spihid, device, hdr,
+ payload, le16_to_cpu(hdr->length));
+ break;
+ case SPIHID_WRITE_PACKET:
+ handled = spihid_process_response(spihid, hdr, payload,
+ le16_to_cpu(hdr->length));
+ 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_meesage(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_debug("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_meesage(spihid, pkt);
+ }
+}
+
+static void spihid_read_packet_sync(struct spihid_apple *spihid)
+{
+ int err;
+
+ err = spi_sync(spihid->spidev, &spihid->rx_msg);
+ if (!err) {
+ spihid_process_read(spihid);
+ } else {
+ dev_warn(&spihid->spidev->dev, "RX failed: %d\n", err);
+ }
+}
+
+irqreturn_t spihid_apple_core_irq(int irq, void *data)
+{
+ struct spi_device *spi = data;
+ struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+ spihid_read_packet_sync(spihid);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_irq);
+
+static void spihid_apple_setup_spi_msgs(struct spihid_apple *spihid)
+{
+ memset(&spihid->rx_transfer, 0, sizeof(spihid->rx_transfer));
+
+ spihid->rx_transfer.rx_buf = spihid->rx_buf;
+ spihid->rx_transfer.len = sizeof(struct spihid_transfer_packet);
+
+ spi_message_init(&spihid->rx_msg);
+ spi_message_add_tail(&spihid->rx_transfer, &spihid->rx_msg);
+
+ memset(&spihid->tx_transfer, 0, sizeof(spihid->rx_transfer));
+ memset(&spihid->status_transfer, 0, sizeof(spihid->status_transfer));
+
+ spihid->tx_transfer.tx_buf = spihid->tx_buf;
+ spihid->tx_transfer.len = sizeof(struct spihid_transfer_packet);
+ spihid->tx_transfer.delay.unit = SPI_DELAY_UNIT_USECS;
+ spihid->tx_transfer.delay.value = SPI_RW_CHG_DELAY_US;
+
+ spihid->status_transfer.rx_buf = spihid->status_buf;
+ spihid->status_transfer.len = sizeof(spi_hid_apple_status_ok);
+
+ spi_message_init(&spihid->tx_msg);
+ spi_message_add_tail(&spihid->tx_transfer, &spihid->tx_msg);
+ spi_message_add_tail(&spihid->status_transfer, &spihid->tx_msg);
+}
+
+static int spihid_apple_setup_spi(struct spihid_apple *spihid)
+{
+ spihid_apple_setup_spi_msgs(spihid);
+
+ return spihid->ops->power_on(spihid->ops);
+}
+
+static int spihid_register_hid_device(struct spihid_apple *spihid,
+ struct spihid_interface *iface, u8 device)
+{
+ int ret;
+ struct hid_device *hid;
+
+ iface->id = device;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return PTR_ERR(hid);
+
+ strscpy(hid->name, spihid->product, sizeof(hid->name));
+ snprintf(hid->phys, sizeof(hid->phys), "%s (%hhx)",
+ dev_name(&spihid->spidev->dev), device);
+ strscpy(hid->uniq, spihid->serial, sizeof(hid->uniq));
+
+ hid->ll_driver = &apple_hid_ll;
+ hid->bus = BUS_SPI;
+ hid->vendor = spihid->vendor_id;
+ hid->product = spihid->product_id;
+ hid->version = spihid->version_number;
+
+ if (device == SPIHID_DEVICE_ID_KBD)
+ hid->type = HID_TYPE_SPI_KEYBOARD;
+ else if (device == SPIHID_DEVICE_ID_TP)
+ hid->type = HID_TYPE_SPI_MOUSE;
+
+ hid->country = iface->country;
+ hid->dev.parent = &spihid->spidev->dev;
+ hid->driver_data = iface;
+
+ ret = hid_add_device(hid);
+ if (ret < 0) {
+ hid_destroy_device(hid);
+ dev_warn(&spihid->spidev->dev,
+ "Failed to register hid device %hhu", device);
+ return ret;
+ }
+
+ iface->hid = hid;
+
+ return 0;
+}
+
+static void spihid_destroy_hid_device(struct spihid_interface *iface)
+{
+ if (iface->hid) {
+ hid_destroy_device(iface->hid);
+ iface->hid = NULL;
+ }
+ iface->ready = false;
+}
+
+int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops)
+{
+ struct device *dev = &spi->dev;
+ struct spihid_apple *spihid;
+ int err, i;
+
+ if (!ops || !ops->power_on || !ops->power_off || !ops->enable_irq || !ops->disable_irq)
+ return -EINVAL;
+
+ spihid = devm_kzalloc(dev, sizeof(*spihid), GFP_KERNEL);
+ if (!spihid)
+ return -ENOMEM;
+
+ spihid->ops = ops;
+ spihid->spidev = spi;
+
+ // init spi
+ spi_set_drvdata(spi, spihid);
+
+ /* allocate SPI buffers */
+ spihid->rx_buf = devm_kmalloc(
+ &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL);
+ spihid->tx_buf = devm_kmalloc(
+ &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL);
+ spihid->status_buf = devm_kmalloc(
+ &spi->dev, sizeof(spi_hid_apple_status_ok), GFP_KERNEL);
+
+ if (!spihid->rx_buf || !spihid->tx_buf || !spihid->status_buf)
+ return -ENOMEM;
+
+ spihid->report.buf =
+ devm_kmalloc(dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL);
+
+ spihid->kbd.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL);
+ spihid->tp.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL);
+
+ if (!spihid->report.buf || !spihid->kbd.hid_desc ||
+ !spihid->tp.hid_desc)
+ return -ENOMEM;
+
+ init_waitqueue_head(&spihid->wait);
+
+ mutex_init(&spihid->tx_lock);
+
+ /* Init spi transfer buffers and power device on */
+ err = spihid_apple_setup_spi(spihid);
+ if (err < 0)
+ goto error;
+
+ /* enable HID irq */
+ spihid->ops->enable_irq(spihid->ops);
+
+ // wait for boot message
+ err = wait_event_interruptible_timeout(spihid->wait,
+ spihid->status_booted,
+ msecs_to_jiffies(1000));
+ if (err == 0)
+ err = -ENODEV;
+ if (err < 0) {
+ dev_err(dev, "waiting for device boot failed: %d", err);
+ goto error;
+ }
+
+ /* request device information */
+ dev_dbg(dev, "request device info");
+ spihid_apple_request(spihid, 0xd0, 0x20, 0x01, 0xd0, 0, NULL, 0);
+ err = wait_event_interruptible_timeout(spihid->wait, spihid->vendor_id,
+ SPIHID_DEF_WAIT);
+ if (err == 0)
+ err = -ENODEV;
+ if (err < 0) {
+ dev_err(dev, "waiting for device info failed: %d", err);
+ goto error;
+ }
+
+ /* request interface information */
+ for (i = 0; i < spihid->num_devices; i++) {
+ struct spihid_interface *iface = spihid_get_iface(spihid, i);
+ if (!iface)
+ continue;
+ dev_dbg(dev, "request interface info 0x%02x", i);
+ spihid_apple_request(spihid, 0xd0, 0x20, 0x02, i,
+ SPIHID_DESC_MAX, NULL, 0);
+ err = wait_event_interruptible_timeout(
+ spihid->wait, iface->max_input_report_len,
+ SPIHID_DEF_WAIT);
+ }
+
+ /* request HID report descriptors */
+ for (i = 1; i < spihid->num_devices; i++) {
+ struct spihid_interface *iface = spihid_get_iface(spihid, i);
+ if (!iface)
+ continue;
+ dev_dbg(dev, "request hid report desc 0x%02x", i);
+ spihid_apple_request(spihid, 0xd0, 0x20, 0x10, i,
+ SPIHID_DESC_MAX, NULL, 0);
+ wait_event_interruptible_timeout(
+ spihid->wait, iface->hid_desc_len, SPIHID_DEF_WAIT);
+ }
+
+ return 0;
+error:
+ return err;
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_probe);
+
+void spihid_apple_core_remove(struct spi_device *spi)
+{
+ struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+ /* destroy input devices */
+
+ spihid_destroy_hid_device(&spihid->tp);
+ spihid_destroy_hid_device(&spihid->kbd);
+
+ /* disable irq */
+ spihid->ops->disable_irq(spihid->ops);
+
+ /* power SPI device down */
+ spihid->ops->power_off(spihid->ops);
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_remove);
+
+void spihid_apple_core_shutdown(struct spi_device *spi)
+{
+ struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+ /* disable irq */
+ spihid->ops->disable_irq(spihid->ops);
+
+ /* power SPI device down */
+ spihid->ops->power_off(spihid->ops);
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_shutdown);
+
+MODULE_DESCRIPTION("Apple SPI HID transport driver");
+MODULE_AUTHOR("Janne Grunau <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..db76774eea7e
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple-of.c
@@ -0,0 +1,138 @@
+/*
+ * 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;
+};
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+int spihid_apple_of_disable_irq(struct spihid_apple_ops *ops)
+{
+ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+ disable_irq(sh_of->irq);
+
+ return 0;
+}
+
+static int spihid_apple_of_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct spihid_apple_of *spihid_of;
+ int err;
+
+ dev_warn(dev, "%s:%d", __func__, __LINE__);
+
+ spihid_of = devm_kzalloc(dev, sizeof(*spihid_of), GFP_KERNEL);
+ if (!spihid_of)
+ return -ENOMEM;
+
+ spihid_of->ops.power_on = spihid_apple_of_power_on;
+ spihid_of->ops.power_off = spihid_apple_of_power_off;
+ spihid_of->ops.enable_irq = spihid_apple_of_enable_irq;
+ spihid_of->ops.disable_irq = spihid_apple_of_disable_irq;
+
+ spihid_of->enable_gpio = devm_gpiod_get_index(dev, "spien", 0, 0);
+ if (IS_ERR(spihid_of->enable_gpio)) {
+ err = PTR_ERR(spihid_of->enable_gpio);
+ dev_err(dev, "failed to get 'spien' gpio pin: %d", err);
+ return err;
+ }
+
+ spihid_of->irq = of_irq_get(dev->of_node, 0);
+ if (spihid_of->irq < 0) {
+ err = spihid_of->irq;
+ dev_err(dev, "failed to get 'extended-irq': %d", err);
+ return err;
+ }
+ err = devm_request_threaded_irq(dev, spihid_of->irq, NULL,
+ spihid_apple_core_irq, IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "spi-hid-apple-irq", spi);
+ if (err < 0) {
+ dev_err(dev, "failed to request extended-irq %d: %d",
+ spihid_of->irq, err);
+ return err;
+ }
+
+ return spihid_apple_core_probe(spi, &spihid_of->ops);
+}
+
+static const struct of_device_id spihid_apple_of_match[] = {
+ { .compatible = "apple,spi-hid-transport" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, spihid_apple_of_match);
+
+static struct spi_device_id spihid_apple_of_id[] = {
+ { "spi-hid-transport", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, spihid_apple_of_id);
+
+static struct spi_driver spihid_apple_of_driver = {
+ .driver = {
+ .name = "spi-hid-apple-of",
+ //.pm = &spi_hid_apple_of_pm,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(spihid_apple_of_match),
+ },
+
+ .id_table = spihid_apple_of_id,
+ .probe = spihid_apple_of_probe,
+ .remove = spihid_apple_core_remove,
+ .shutdown = spihid_apple_core_shutdown,
+};
+
+module_spi_driver(spihid_apple_of_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-apple.h b/drivers/hid/spi-hid/spi-hid-apple.h
new file mode 100644
index 000000000000..2d9554e8a5f8
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
+
+#ifndef SPI_HID_APPLE_H
+#define SPI_HID_APPLE_H
+
+#include <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);
+};
+
+irqreturn_t spihid_apple_core_irq(int irq, void *data);
+
+int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops);
+void spihid_apple_core_remove(struct spi_device *spi);
+void spihid_apple_core_shutdown(struct spi_device *spi);
+
+#endif /* SPI_HID_APPLE_H */