lede/target/linux/silicon/patches-5.19/0133-dockchanel-hid-Send-interface-firmware-lazily.patch

394 lines
11 KiB
Diff

From e478ad5114c801719d37cac1a2d53941ed124936 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 17 Jul 2022 20:47:37 +0900
Subject: [PATCH 133/171] dockchanel-hid: Send interface firmware lazily
Wait until the HID device is opened to send the multitouch firmware.
This avoids having to put it in the initramfs to get basic keyboard
functionality there.
Signed-off-by: Hector Martin <marcan@marcan.st>
---
drivers/hid/dockchannel-hid/dockchannel-hid.c | 246 +++++++++++-------
1 file changed, 157 insertions(+), 89 deletions(-)
diff --git a/drivers/hid/dockchannel-hid/dockchannel-hid.c b/drivers/hid/dockchannel-hid/dockchannel-hid.c
index 07ac3a77c390..aeaeb60edf39 100644
--- a/drivers/hid/dockchannel-hid/dockchannel-hid.c
+++ b/drivers/hid/dockchannel-hid/dockchannel-hid.c
@@ -17,7 +17,8 @@
#include <linux/of.h>
#include "../hid-ids.h"
-#define COMMAND_TIMEOUT 1000
+#define COMMAND_TIMEOUT_MS 1000
+#define START_TIMEOUT_MS 2000
#define MAX_INTERFACES 16
@@ -146,6 +147,10 @@ struct dchid_work {
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;
@@ -153,7 +158,9 @@ struct dchid_iface {
uint8_t tx_seq;
bool deferred;
+ bool starting;
bool open;
+ struct completion ready;
void *hid_desc;
size_t hid_desc_len;
@@ -183,7 +190,8 @@ struct dockchannel_hid {
u8 pkt_buf[MAX_PKT_SIZE];
- struct workqueue_struct *wq;
+ /* Workqueue to asynchronously create HID devices */
+ struct workqueue_struct *new_iface_wq;
};
static struct dchid_iface *
@@ -208,9 +216,11 @@ dchid_get_interface(struct dockchannel_hid *dchid, int index, const char *name)
iface->dchid = dchid;
iface->out_report= -1;
init_completion(&iface->out_complete);
+ init_completion(&iface->ready);
mutex_init(&iface->out_mutex);
-
- dev_info(dchid->dev, "Initializing interface %s\n", iface->name);
+ 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")) {
@@ -308,7 +318,7 @@ static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req,
if (ret < 0)
goto done;
- if (!wait_for_completion_timeout(&iface->out_complete, msecs_to_jiffies(1000))) {
+ 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;
@@ -431,6 +441,49 @@ static int dchid_get_firmware(struct dchid_iface *iface, void **firmware, size_t
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)
{
@@ -445,6 +498,18 @@ static void dchid_stop(struct hid_device *hdev)
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;
@@ -506,32 +571,87 @@ static struct hid_ll_driver dchid_ll = {
.raw_request = &dchid_raw_request,
};
-static void dchid_init_interface(struct dchid_iface *iface)
+static void dchid_create_interface_work(struct work_struct *ws)
{
- void *fw;
- size_t size;
-
- iface->deferred = false;
+ struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work);
+ struct dockchannel_hid *dchid = iface->dchid;
+ struct hid_device *hid;
+ int ret;
- /* Enable interface (general) */
- if (dchid_enable_interface(iface) < 0)
+ if (iface->hid) {
+ dev_warn(dchid->dev, "Interface %s already created!\n",
+ iface->name);
return;
+ }
- /* Look to see if we need firmware */
- if (dchid_get_firmware(iface, &fw, &size) < 0)
- return;
+ dev_info(dchid->dev, "New interface %s\n", iface->name);
- /* Only multi-touch has firmware */
- if (!fw || !size)
+ /* 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;
+ }
- /* Send it to the device */
- if (dchid_send_firmware(iface, fw, size) < 0)
+ iface->deferred = false;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
return;
- /* After loading firmware, multi-touch needs a reset */
- dchid_reset_interface(iface, 0);
- dchid_reset_interface(iface, 2);
+ 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 */
+ switch (dchid->device_id.keyboard_type) {
+ 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)
@@ -549,20 +669,19 @@ static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc, s
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_init_interface(iface);
- else
+ 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 hid_device *hid;
struct dchid_iface *iface;
- int ret;
u8 *pkt = data;
u8 index;
- int i;
+ int i, ret;
if (length < 2) {
dev_err(dchid->dev, "Bad length for ready message: %ld\n", length);
@@ -582,11 +701,8 @@ static void dchid_handle_ready(struct dockchannel_hid *dchid, void *data, size_t
return;
}
- if (iface->hid) {
- dev_warn(iface->dchid->dev, "Interface %s already ready!\n",
- iface->name);
- return;
- }
+ dev_info(dchid->dev, "Inteface %s is now ready\n", iface->name);
+ complete_all(&iface->ready);
/* When STM is ready, grab global device info */
if (!strcmp(iface->name, "stm")) {
@@ -606,59 +722,12 @@ static void dchid_handle_ready(struct dockchannel_hid *dchid, void *data, size_t
}
dchid->id_ready = true;
- for (i = 0; i < MAX_INTERFACES; i++)
- if (dchid->ifaces[i] && dchid->ifaces[i]->deferred)
- dchid_init_interface(dchid->ifaces[i]);
-
- }
-
- 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(iface->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 */
- switch (dchid->device_id.keyboard_type) {
- 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;
+ for (i = 0; i < MAX_INTERFACES; i++) {
+ if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred)
+ continue;
+ dchid_create_interface(dchid->ifaces[i]);
}
}
-
- hid->dev.parent = iface->dchid->dev;
- hid->driver_data = iface;
-
- ret = hid_add_device(hid);
- if (ret < 0) {
- hid_destroy_device(hid);
- dev_warn(iface->dchid->dev, "Failed to register hid device %s", iface->name);
- return;
- }
-
- iface->hid = hid;
}
static void dchid_request_gpio(struct dchid_iface *iface, int id, const char *name)
@@ -951,7 +1020,7 @@ static void dchid_handle_packet(void *cookie, size_t avail)
memcpy(work->data, dchid->pkt_buf, hdr.length);
INIT_WORK(&work->work, dchid_packet_work);
- queue_work(dchid->wq, &work->work);
+ queue_work(iface->wq, &work->work);
done:
dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr));
@@ -1004,6 +1073,9 @@ static int dockchannel_hid_probe(struct platform_device *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) {
@@ -1011,10 +1083,6 @@ static int dockchannel_hid_probe(struct platform_device *pdev)
return -EIO;
}
- dchid->wq = alloc_ordered_workqueue("dockchannel-hid-report", WQ_MEM_RECLAIM);
- if (!dchid->wq)
- return -ENOMEM;
-
dev_info(dchid->dev, "initialized, awaiting packets\n");
dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr));
--
2.34.1