From: "Ertuğrul Topçu" <ertugtopcu0@gmail.com>
To: hansg@kernel.org, ilpo.jarvinen@linux.intel.com
Cc: platform-driver-x86@vger.kernel.org,
linux-kernel@vger.kernel.org,
"Ertuğrul Topçu" <ertugtopcu0@gmail.com>
Subject: [PATCH v2] platform/x86: Add Goodix fingerprint EC mailbox transport
Date: Mon, 3 Aug 2026 10:44:54 +0300 [thread overview]
Message-ID: <20260803074454.49474-1-ertugtopcu0@gmail.com> (raw)
In-Reply-To: <20260727205837.91413-1-ertugtopcu0@gmail.com>
Some Goodix fingerprint sensors are connected through an ACPI-described
embedded-controller shared-memory mailbox rather than directly through USB
or SPI.
Add a transport driver for the GXFP5130 platform interface. The driver
owns the MMIO mailbox, GPIO handshakes and IRQ. It exposes opaque mailbox
records through /dev/gxfp. Keep sensor initialization, TLS, capture and
image processing in userspace.
Restrict the raw transport to CAP_SYS_RAWIO and document the UAPI. Handle
device removal and system suspend without invalidating open file
descriptors.
Signed-off-by: Ertuğrul Topçu <ertugtopcu0@gmail.com>
---
Notes:
Changes in v2:
- Treat IRQ 0 as valid and always register a successfully resolved IRQ.
- Replace mailbox handshake busy waits with fsleep().
- Remove unverified Nuvoton-specific wording, stale comments and unused model data.
.../ABI/testing/dev-goodix-ec-mailbox | 22 +
MAINTAINERS | 8 +
drivers/platform/x86/Kconfig | 2 +
drivers/platform/x86/Makefile | 3 +
.../platform/x86/goodix-ec-mailbox/Kconfig | 15 +
.../platform/x86/goodix-ec-mailbox/Makefile | 2 +
.../x86/goodix-ec-mailbox/goodix_ec_mailbox.h | 113 +++
.../x86/goodix-ec-mailbox/goodix_ec_main.c | 714 ++++++++++++++++++
.../x86/goodix-ec-mailbox/goodix_ec_uapi.c | 360 +++++++++
include/uapi/linux/goodix_ec.h | 37 +
10 files changed, 1276 insertions(+)
create mode 100644 Documentation/ABI/testing/dev-goodix-ec-mailbox
create mode 100644 drivers/platform/x86/goodix-ec-mailbox/Kconfig
create mode 100644 drivers/platform/x86/goodix-ec-mailbox/Makefile
create mode 100644 drivers/platform/x86/goodix-ec-mailbox/goodix_ec_mailbox.h
create mode 100644 drivers/platform/x86/goodix-ec-mailbox/goodix_ec_main.c
create mode 100644 drivers/platform/x86/goodix-ec-mailbox/goodix_ec_uapi.c
create mode 100644 include/uapi/linux/goodix_ec.h
diff --git a/Documentation/ABI/testing/dev-goodix-ec-mailbox b/Documentation/ABI/testing/dev-goodix-ec-mailbox
new file mode 100644
index 000000000000..11ca80a41ca7
--- /dev/null
+++ b/Documentation/ABI/testing/dev-goodix-ec-mailbox
@@ -0,0 +1,22 @@
+What: /dev/gxfp
+Date: July 2026
+KernelVersion: TBD
+Contact: Ertugrul Topcu <ertugtopcu0@gmail.com>
+Description:
+ Binary userspace interface for the ACPI Goodix EC mailbox
+ fingerprint transport. Only callers with CAP_SYS_RAWIO may open
+ the device. At most one reader may be open at a time.
+
+ A write consists of struct goodix_ec_tx_header followed by exactly
+ payload_len opaque MP payload bytes. reserved and flags must be zero.
+
+ A read returns one struct goodix_ec_record_header followed by exactly
+ len bytes. mp_type is the normalized MP type and timestamp_ns is a
+ CLOCK_MONOTONIC timestamp captured when the record entered the RX
+ queue. Records are never split across reads.
+
+ poll(2) reports POLLIN while a complete record is queued. Device
+ removal reports POLLERR | POLLHUP. System suspend reports POLLERR.
+
+ GOODIX_EC_IOCTL_FLUSH_RX discards all queued receive records.
+Users: libfprint Goodix GXFP5130 userspace driver
diff --git a/MAINTAINERS b/MAINTAINERS
index 92a2167f1eb8..0c62071bbc1b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11109,6 +11109,14 @@ M: Maud Spierings <maudspierings@gocontroll.com>
S: Maintained
F: Documentation/devicetree/bindings/connector/gocontroll,moduline-module-slot.yaml
+GOODIX EC MAILBOX FINGERPRINT TRANSPORT DRIVER
+M: Ertugrul Topcu <ertugtopcu0@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/dev-goodix-ec-mailbox
+F: drivers/platform/x86/goodix-ec-mailbox/
+F: include/uapi/linux/goodix_ec.h
+
GOODIX TOUCHSCREEN
M: Hans de Goede <hansg@kernel.org>
L: linux-input@vger.kernel.org
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 957034f39e4e..57d1311f8670 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -440,6 +440,8 @@ config FUJITSU_TABLET
If you have a Fujitsu convertible or slate, say Y or M here.
+source "drivers/platform/x86/goodix-ec-mailbox/Kconfig"
+
config GPD_POCKET_FAN
tristate "GPD Pocket Fan Controller support"
depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 872ac3842391..650ab67f7902 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -54,6 +54,9 @@ obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o
obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o
obj-$(CONFIG_FUJITSU_TABLET) += fujitsu-tablet.o
+# Goodix
+obj-$(CONFIG_GOODIX_EC_MAILBOX) += goodix-ec-mailbox/
+
# GPD
obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o
diff --git a/drivers/platform/x86/goodix-ec-mailbox/Kconfig b/drivers/platform/x86/goodix-ec-mailbox/Kconfig
new file mode 100644
index 000000000000..6f43b1323fa4
--- /dev/null
+++ b/drivers/platform/x86/goodix-ec-mailbox/Kconfig
@@ -0,0 +1,15 @@
+config GOODIX_EC_MAILBOX
+ tristate "Goodix fingerprint EC mailbox transport"
+ depends on X86
+ depends on ACPI
+ depends on GPIOLIB
+ help
+ Enable transport support for Goodix fingerprint sensors connected
+ through an ACPI-described embedded-controller shared-memory mailbox.
+
+ The driver exposes the opaque mailbox transport through /dev/gxfp.
+ Sensor configuration, TLS, capture and image processing remain in
+ userspace.
+
+ To compile this driver as a module, choose M here. The module will be
+ called goodix_ec_mailbox.
diff --git a/drivers/platform/x86/goodix-ec-mailbox/Makefile b/drivers/platform/x86/goodix-ec-mailbox/Makefile
new file mode 100644
index 000000000000..52e5516eefb0
--- /dev/null
+++ b/drivers/platform/x86/goodix-ec-mailbox/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_GOODIX_EC_MAILBOX) += goodix_ec_mailbox.o
+goodix_ec_mailbox-y := goodix_ec_main.o goodix_ec_uapi.o
diff --git a/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_mailbox.h b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_mailbox.h
new file mode 100644
index 000000000000..6f412c46159b
--- /dev/null
+++ b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_mailbox.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _GOODIX_EC_H_
+#define _GOODIX_EC_H_
+
+#include <linux/gpio/consumer.h>
+#include <linux/ioport.h>
+#include <linux/kfifo.h>
+#include <linux/kref.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+
+#define GOODIX_EC_DRIVER_NAME "goodix-ec-mailbox"
+
+/* Shared-memory mailbox layout. */
+#define GOODIX_EC_MMIO_SIZE 0x1000
+#define GOODIX_EC_TX_OFFSET 0x000
+#define GOODIX_EC_TX_SIZE 0x0200
+#define GOODIX_EC_RX_OFFSET 0x0200
+#define GOODIX_EC_RX_SIZE 0x0e00
+#define GOODIX_EC_PACKET_ALIGNMENT 8
+
+/* EC mailbox framing. */
+#define GOODIX_EC_PACKET_TYPE 0xf0
+#define GOODIX_EC_SEQUENCE_SEED 0x8881
+
+/* MP framing. The payload is opaque to the kernel. */
+#define GOODIX_MP_HEADER_SIZE 4
+
+/* Mailbox handshake timing. */
+#define GOODIX_WRITE_DONE_PRE_US 50
+#define GOODIX_WRITE_DONE_HIGH_US 4000
+#define GOODIX_WRITE_DONE_POST_US 200
+#define GOODIX_READ_DONE_HIGH_US 50
+#define GOODIX_READ_DONE_POST_US 200
+#define GOODIX_SYNC_RX_DELAY_US 1000
+
+struct device;
+
+struct goodix_ec_header {
+ u8 type;
+ __le16 payload_len;
+ u8 checksum;
+ __le16 sequence;
+ u8 reserved[2];
+} __packed;
+
+struct goodix_mp_header {
+ u8 type;
+ __le16 payload_len;
+ u8 checksum;
+} __packed;
+
+struct goodix_device;
+
+struct goodix_model_data {
+ const char *name;
+};
+
+struct goodix_device {
+ struct device *dev;
+ struct kref refcount;
+ bool disconnected;
+ bool suspended;
+ bool irq_enabled;
+
+ void __iomem *mailbox;
+ resource_size_t mailbox_phys;
+ resource_size_t mailbox_size;
+
+ struct gpio_desc *write_done_gpio;
+ struct gpio_desc *read_done_gpio;
+ struct gpio_desc *irq_gpio;
+ int irq;
+
+ u8 *tx_buf;
+ u8 *rx_buf;
+
+ u8 *rx_reassembly;
+ size_t rx_reassembly_len;
+ size_t rx_reassembly_received;
+ u8 rx_reassembly_mp_type;
+ bool rx_reassembly_active;
+
+ u16 tx_sequence;
+ /* Serializes mailbox TX with threaded-IRQ RX access. */
+ struct mutex transfer_lock;
+
+ struct miscdevice miscdev;
+ struct kfifo rx_fifo;
+ /* Protects the RX FIFO and reader ownership state. */
+ spinlock_t rx_fifo_lock;
+ /* Serializes record-oriented read operations. */
+ struct mutex rx_read_lock;
+ wait_queue_head_t rx_wait;
+ bool rx_fifo_ready;
+ bool rx_reader_open;
+ bool misc_registered;
+
+ const struct goodix_model_data *model;
+};
+
+int goodix_ec_sync_send(struct goodix_device *gdev,
+ const u8 *tx, size_t tx_len);
+bool goodix_ec_device_get(struct goodix_device *gdev);
+void goodix_ec_device_put(struct goodix_device *gdev);
+int goodix_ec_uapi_register(struct goodix_device *gdev);
+void goodix_ec_uapi_unregister(struct goodix_device *gdev);
+
+#endif /* _GOODIX_EC_H_ */
diff --git a/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_main.c b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_main.c
new file mode 100644
index 000000000000..92f1c6f36b19
--- /dev/null
+++ b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_main.c
@@ -0,0 +1,714 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Goodix fingerprint sensors behind an EC shared-memory mailbox.
+ *
+ * The tested GXFP5130 platform exposes the fingerprint transport through
+ * ACPI-described MMIO and GPIO handshakes. The host exchanges opaque MP
+ * payloads with the device through this mailbox.
+ *
+ * Sensor protocol policy deliberately stays in userspace. This driver does
+ * not know about Goodix commands, checksums, TLS, configuration, capture, or
+ * sensor power states.
+ */
+
+#include <linux/acpi.h>
+#include <linux/align.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/gpio/consumer.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/capability.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timekeeping.h>
+#include <linux/uaccess.h>
+#include <linux/unaligned.h>
+
+#include "goodix_ec_mailbox.h"
+#include <linux/goodix_ec.h>
+
+#define GXFP5130_ACPI_HID "GXFP5130"
+
+static void goodix_ec_device_release(struct kref *refcount)
+{
+ struct goodix_device *gdev =
+ container_of(refcount, struct goodix_device, refcount);
+
+ kfree(gdev);
+}
+
+bool goodix_ec_device_get(struct goodix_device *gdev)
+{
+ return kref_get_unless_zero(&gdev->refcount);
+}
+
+void goodix_ec_device_put(struct goodix_device *gdev)
+{
+ kref_put(&gdev->refcount, goodix_ec_device_release);
+}
+
+static void goodix_ec_device_put_action(void *data)
+{
+ goodix_ec_device_put(data);
+}
+
+/* EC mailbox transport. */
+
+struct goodix_ec_acpi_gpio_state {
+ unsigned int gpio_count;
+ int irq_index;
+ int done_index[2];
+ u8 done_polarity[2];
+ unsigned int done_count;
+};
+
+static int goodix_ec_acpi_gpio_resource(struct acpi_resource *resource,
+ void *context)
+{
+ struct goodix_ec_acpi_gpio_state *state = context;
+ struct acpi_resource_gpio *gpio;
+ unsigned int index;
+
+ if (resource->type != ACPI_RESOURCE_TYPE_GPIO)
+ return 0;
+
+ gpio = &resource->data.gpio;
+ index = state->gpio_count++;
+ if (!gpio->pin_table_length)
+ return 0;
+
+ if (gpio->connection_type == ACPI_RESOURCE_GPIO_TYPE_INT) {
+ if (state->irq_index < 0)
+ state->irq_index = index;
+ } else if (gpio->connection_type == ACPI_RESOURCE_GPIO_TYPE_IO &&
+ state->done_count < ARRAY_SIZE(state->done_index)) {
+ state->done_index[state->done_count] = index;
+ state->done_polarity[state->done_count] = gpio->polarity;
+ state->done_count++;
+ }
+
+ return 0;
+}
+
+static int goodix_ec_add_gpio_mappings(struct device *dev)
+{
+ struct goodix_ec_acpi_gpio_state state = {
+ .irq_index = -1,
+ .done_index = { -1, -1 },
+ };
+ struct acpi_gpio_mapping *mappings;
+ struct acpi_gpio_params *params;
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ LIST_HEAD(resources);
+ bool active_low;
+ int ret;
+
+ if (!adev)
+ return -ENODEV;
+
+ ret = acpi_dev_get_resources(adev, &resources,
+ goodix_ec_acpi_gpio_resource, &state);
+ acpi_dev_free_resource_list(&resources);
+ if (ret <= 0)
+ return ret < 0 ? ret : -ENOENT;
+ if (state.irq_index < 0 || state.done_count != 2)
+ return dev_err_probe(dev, -ENOENT,
+ "ACPI _CRS does not describe one IRQ and two done GPIOs\n");
+ if (state.done_polarity[0] != state.done_polarity[1])
+ return dev_err_probe(dev, -EINVAL,
+ "ACPI done GPIO polarities differ\n");
+
+ active_low = state.done_polarity[0] != 0;
+ params = devm_kcalloc(dev, 3, sizeof(*params), GFP_KERNEL);
+ mappings = devm_kcalloc(dev, 4, sizeof(*mappings), GFP_KERNEL);
+ if (!params || !mappings)
+ return -ENOMEM;
+
+ params[0].crs_entry_index = state.irq_index;
+ params[0].line_index = 0;
+ params[0].active_low = false;
+ mappings[0].name = "irq-gpios";
+ mappings[0].data = ¶ms[0];
+ mappings[0].size = 1;
+
+ params[1].crs_entry_index = state.done_index[0];
+ params[1].line_index = 0;
+ params[1].active_low = active_low;
+ mappings[1].name = "write-done-gpios";
+ mappings[1].data = ¶ms[1];
+ mappings[1].size = 1;
+
+ params[2].crs_entry_index = state.done_index[1];
+ params[2].line_index = 0;
+ params[2].active_low = active_low;
+ mappings[2].name = "read-done-gpios";
+ mappings[2].data = ¶ms[2];
+ mappings[2].size = 1;
+
+ ret = devm_acpi_dev_add_driver_gpios(dev, mappings);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to install ACPI GPIO mappings\n");
+
+ dev_dbg(dev,
+ "ACPI GPIO mappings: irq=%d write-done=%d read-done=%d active-low=%u\n",
+ state.irq_index, state.done_index[0], state.done_index[1],
+ active_low);
+ return 0;
+}
+
+static int goodix_ec_get_gpio(struct goodix_device *gdev,
+ struct gpio_desc **gpio,
+ const char *name,
+ enum gpiod_flags flags)
+{
+ *gpio = devm_gpiod_get(gdev->dev, name, flags);
+ if (IS_ERR(*gpio))
+ return dev_err_probe(gdev->dev, PTR_ERR(*gpio),
+ "failed to acquire %s GPIO\n", name);
+
+ if (!*gpio)
+ return dev_err_probe(gdev->dev, -ENODEV,
+ "%s GPIO missing\n", name);
+
+ return 0;
+}
+
+static void goodix_ec_mmio_write_tx(struct goodix_device *gdev, size_t len)
+{
+ size_t offset;
+
+ for (offset = 0; offset < len; offset += sizeof(u64)) {
+ u64 value = get_unaligned_le64(gdev->tx_buf + offset);
+
+ writeq(value, gdev->mailbox + GOODIX_EC_TX_OFFSET + offset);
+ }
+
+ /* Publish every qword before asserting the write-done doorbell. */
+ wmb();
+}
+
+static void goodix_ec_mmio_read_rx(struct goodix_device *gdev)
+{
+ size_t offset;
+
+ for (offset = 0; offset < GOODIX_EC_RX_SIZE; offset += sizeof(u64)) {
+ u64 value = readq(gdev->mailbox + GOODIX_EC_RX_OFFSET + offset);
+
+ put_unaligned_le64(value, gdev->rx_buf + offset);
+ }
+
+ /* Complete the mailbox snapshot before parsing its headers. */
+ rmb();
+}
+
+static int goodix_ec_pulse_write_done(struct goodix_device *gdev)
+{
+ if (!gdev->write_done_gpio)
+ return -ENODEV;
+
+ gpiod_set_value_cansleep(gdev->write_done_gpio, 0);
+ fsleep(GOODIX_WRITE_DONE_PRE_US);
+
+ gpiod_set_value_cansleep(gdev->write_done_gpio, 1);
+ fsleep(GOODIX_WRITE_DONE_HIGH_US);
+
+ gpiod_set_value_cansleep(gdev->write_done_gpio, 0);
+ fsleep(GOODIX_WRITE_DONE_POST_US);
+
+ return 0;
+}
+
+static int goodix_ec_pulse_read_done(struct goodix_device *gdev)
+{
+ if (!gdev->read_done_gpio)
+ return -ENODEV;
+
+ /* RX must be copied before acknowledging that the host consumed it. */
+ gpiod_set_value_cansleep(gdev->read_done_gpio, 1);
+ fsleep(GOODIX_READ_DONE_HIGH_US);
+
+ gpiod_set_value_cansleep(gdev->read_done_gpio, 0);
+ fsleep(GOODIX_READ_DONE_POST_US);
+
+ return 0;
+}
+
+static int goodix_ec_build_packet(struct goodix_device *gdev,
+ const u8 *payload, size_t payload_len,
+ size_t *packet_len)
+{
+ struct goodix_ec_header *header;
+ size_t total_len;
+
+ if (!gdev || !payload || !payload_len || !packet_len)
+ return -EINVAL;
+
+ if (payload_len > U16_MAX)
+ return -EOVERFLOW;
+
+ total_len = ALIGN(sizeof(*header) + payload_len,
+ GOODIX_EC_PACKET_ALIGNMENT);
+ if (total_len > GOODIX_EC_TX_SIZE)
+ return -EMSGSIZE;
+
+ memset(gdev->tx_buf, 0, total_len);
+
+ header = (struct goodix_ec_header *)gdev->tx_buf;
+ header->type = GOODIX_EC_PACKET_TYPE;
+ header->payload_len = cpu_to_le16(payload_len);
+ header->checksum = header->type + (payload_len & 0xff) +
+ ((payload_len >> 8) & 0xff);
+ header->sequence = cpu_to_le16(++gdev->tx_sequence);
+
+ memcpy(gdev->tx_buf + sizeof(*header), payload, payload_len);
+ *packet_len = total_len;
+
+ return 0;
+}
+
+int goodix_ec_sync_send(struct goodix_device *gdev,
+ const u8 *tx, size_t tx_len)
+{
+ size_t packet_len;
+ int ret;
+
+ ret = goodix_ec_build_packet(gdev, tx, tx_len, &packet_len);
+ if (ret)
+ return ret;
+
+ goodix_ec_mmio_write_tx(gdev, packet_len);
+
+ ret = goodix_ec_pulse_write_done(gdev);
+ if (ret)
+ return ret;
+
+ fsleep(GOODIX_SYNC_RX_DELAY_US);
+ return 0;
+}
+
+/* MP packet transport. */
+static void goodix_ec_rx_push(struct goodix_device *gdev, u8 mp_type,
+ const u8 *payload, size_t payload_len)
+{
+ struct goodix_ec_record_header header;
+ unsigned long flags;
+ size_t required;
+
+ if (!gdev || !payload || !payload_len ||
+ payload_len > GOODIX_EC_UAPI_RX_MAX)
+ return;
+
+ memset(&header, 0, sizeof(header));
+ header.len = payload_len;
+ header.mp_type = mp_type >> 4;
+ header.timestamp_ns = ktime_get_ns();
+ required = sizeof(header) + payload_len;
+
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+ if (!gdev->rx_fifo_ready || kfifo_avail(&gdev->rx_fifo) < required) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ dev_warn_ratelimited(gdev->dev,
+ "RX queue full; dropping %zu-byte packet\n",
+ payload_len);
+ return;
+ }
+
+ kfifo_in(&gdev->rx_fifo, &header, sizeof(header));
+ kfifo_in(&gdev->rx_fifo, payload, payload_len);
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+
+ wake_up_interruptible(&gdev->rx_wait);
+}
+
+static irqreturn_t goodix_ec_irq_thread(int irq, void *data)
+{
+ struct goodix_device *gdev = data;
+ size_t chunk_len;
+ size_t remaining;
+ u16 declared_len;
+ u8 mp_type;
+ int ret = 0;
+ int ack_ret;
+
+ mutex_lock(&gdev->transfer_lock);
+
+ goodix_ec_mmio_read_rx(gdev);
+
+ /*
+ * Continuation IRQs contain raw payload bytes without another
+ * MP header.
+ */
+ if (gdev->rx_reassembly_active) {
+ remaining = gdev->rx_reassembly_len -
+ gdev->rx_reassembly_received;
+
+ chunk_len = min_t(size_t,
+ remaining,
+ GOODIX_EC_RX_SIZE);
+
+ memcpy(gdev->rx_reassembly +
+ gdev->rx_reassembly_received,
+ gdev->rx_buf,
+ chunk_len);
+
+ gdev->rx_reassembly_received += chunk_len;
+
+ ack_ret = goodix_ec_pulse_read_done(gdev);
+ if (ack_ret)
+ ret = ack_ret;
+
+ if (!ret &&
+ gdev->rx_reassembly_received ==
+ gdev->rx_reassembly_len) {
+ dev_dbg(gdev->dev,
+ "RX reassembly complete: MP=0x%02x payload=%zu bytes\n",
+ gdev->rx_reassembly_mp_type,
+ gdev->rx_reassembly_len);
+
+ goodix_ec_rx_push(gdev,
+ gdev->rx_reassembly_mp_type,
+ gdev->rx_reassembly,
+ gdev->rx_reassembly_len);
+
+ kfree(gdev->rx_reassembly);
+ gdev->rx_reassembly = NULL;
+ gdev->rx_reassembly_len = 0;
+ gdev->rx_reassembly_received = 0;
+ gdev->rx_reassembly_mp_type = 0;
+ gdev->rx_reassembly_active = false;
+ }
+
+ mutex_unlock(&gdev->transfer_lock);
+
+ if (ret)
+ dev_warn_ratelimited(gdev->dev,
+ "IRQ %d continuation ACK failed: %d\n",
+ irq, ret);
+
+ return IRQ_HANDLED;
+ }
+
+ mp_type = gdev->rx_buf[0];
+
+ if ((mp_type >> 4) != 0x0a &&
+ (mp_type >> 4) != 0x0b &&
+ (mp_type >> 4) != 0x0c) {
+ ret = -EBADMSG;
+ goto acknowledge;
+ }
+
+ if (gdev->rx_buf[3] !=
+ gdev->rx_buf[0] +
+ gdev->rx_buf[1] +
+ gdev->rx_buf[2]) {
+ ret = -EBADMSG;
+ goto acknowledge;
+ }
+
+ declared_len = get_unaligned_le16(gdev->rx_buf + 1);
+
+ /* The complete payload fits in one mailbox transaction. */
+ if (declared_len <=
+ GOODIX_EC_RX_SIZE -
+ sizeof(struct goodix_mp_header)) {
+ goodix_ec_rx_push(gdev,
+ mp_type,
+ gdev->rx_buf +
+ sizeof(struct goodix_mp_header),
+ declared_len);
+
+ dev_dbg(gdev->dev,
+ "IRQ %d RX queued: MP=0x%02x payload=%u bytes\n",
+ irq,
+ mp_type >> 4,
+ declared_len);
+
+ goto acknowledge;
+ }
+
+ /* Reassemble payloads delivered across multiple IRQs. */
+ gdev->rx_reassembly = kmalloc(declared_len, GFP_KERNEL);
+ if (!gdev->rx_reassembly) {
+ ret = -ENOMEM;
+ goto acknowledge;
+ }
+
+ chunk_len = GOODIX_EC_RX_SIZE -
+ sizeof(struct goodix_mp_header);
+
+ memcpy(gdev->rx_reassembly,
+ gdev->rx_buf +
+ sizeof(struct goodix_mp_header),
+ chunk_len);
+
+ gdev->rx_reassembly_len = declared_len;
+ gdev->rx_reassembly_received = chunk_len;
+ gdev->rx_reassembly_mp_type = mp_type;
+ gdev->rx_reassembly_active = true;
+
+ dev_dbg(gdev->dev,
+ "RX reassembly started: MP=0x%02x total=%u first=%zu remaining=%zu\n",
+ mp_type >> 4,
+ declared_len,
+ chunk_len,
+ (size_t)declared_len - chunk_len);
+
+acknowledge:
+ ack_ret = goodix_ec_pulse_read_done(gdev);
+ if (!ret && ack_ret)
+ ret = ack_ret;
+
+ mutex_unlock(&gdev->transfer_lock);
+
+ if (ret)
+ dev_warn_ratelimited(gdev->dev,
+ "IRQ %d RX handling failed: %d\n",
+ irq, ret);
+
+ return IRQ_HANDLED;
+}
+
+static const struct goodix_model_data gxfp5130_model = {
+ .name = "GXFP5130",
+};
+
+/* ACPI platform driver and model selection. */
+
+static int goodix_ec_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct acpi_device_id *match;
+ struct goodix_device *gdev;
+ struct resource *resource;
+ resource_size_t mailbox_size;
+ unsigned long irq_flags;
+ unsigned int irq_type;
+ int ret;
+
+ match = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!match || !match->driver_data)
+ return -ENODEV;
+
+ gdev = kzalloc_obj(*gdev);
+ if (!gdev)
+ return -ENOMEM;
+ kref_init(&gdev->refcount);
+ ret = devm_add_action_or_reset(dev, goodix_ec_device_put_action, gdev);
+ if (ret)
+ return ret;
+
+ gdev->dev = dev;
+ gdev->model = (const struct goodix_model_data *)match->driver_data;
+ gdev->tx_sequence = GOODIX_EC_SEQUENCE_SEED;
+ mutex_init(&gdev->transfer_lock);
+ platform_set_drvdata(pdev, gdev);
+
+ gdev->mailbox = devm_platform_get_and_ioremap_resource(pdev, 0,
+ &resource);
+ if (IS_ERR(gdev->mailbox))
+ return dev_err_probe(dev, PTR_ERR(gdev->mailbox),
+ "failed to map EC mailbox MMIO resource\n");
+
+ mailbox_size = resource_size(resource);
+ if (mailbox_size < GOODIX_EC_MMIO_SIZE)
+ return dev_err_probe(dev, -EINVAL,
+ "EC mailbox resource too small: %#llx\n",
+ (unsigned long long)mailbox_size);
+
+ gdev->mailbox_phys = resource->start;
+ gdev->mailbox_size = mailbox_size;
+
+ gdev->tx_buf = devm_kzalloc(dev, GOODIX_EC_TX_SIZE, GFP_KERNEL);
+ if (!gdev->tx_buf)
+ return -ENOMEM;
+
+ gdev->rx_buf = devm_kzalloc(dev, GOODIX_EC_RX_SIZE, GFP_KERNEL);
+ if (!gdev->rx_buf)
+ return -ENOMEM;
+
+ ret = goodix_ec_add_gpio_mappings(dev);
+ if (ret)
+ return ret;
+
+ ret = goodix_ec_get_gpio(gdev, &gdev->write_done_gpio,
+ "write-done", GPIOD_OUT_LOW);
+ if (ret)
+ return ret;
+
+ ret = goodix_ec_get_gpio(gdev, &gdev->read_done_gpio,
+ "read-done", GPIOD_OUT_LOW);
+ if (ret)
+ return ret;
+
+ ret = goodix_ec_get_gpio(gdev, &gdev->irq_gpio,
+ "irq", GPIOD_IN);
+ if (ret)
+ return ret;
+
+ gdev->irq = gpiod_to_irq(gdev->irq_gpio);
+ if (gdev->irq < 0)
+ return dev_err_probe(dev, gdev->irq,
+ "failed to map IRQ GPIO to an IRQ\n");
+
+ irq_type = irq_get_trigger_type(gdev->irq);
+ if (irq_type == IRQ_TYPE_NONE) {
+ ret = irq_set_irq_type(gdev->irq, IRQ_TYPE_LEVEL_HIGH);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to set level-high IRQ trigger\n");
+ irq_type = IRQ_TYPE_LEVEL_HIGH;
+ }
+
+ dev_info(dev, "bound %s: mailbox=%pa size=%#llx irq=%d\n",
+ gdev->model->name, &gdev->mailbox_phys,
+ (unsigned long long)gdev->mailbox_size, gdev->irq);
+
+ irq_flags = IRQF_ONESHOT | IRQF_NO_AUTOEN;
+ ret = devm_request_threaded_irq(dev,
+ gdev->irq,
+ NULL,
+ goodix_ec_irq_thread,
+ irq_flags,
+ dev_name(dev),
+ gdev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to request IRQ %d\n",
+ gdev->irq);
+
+ dev_dbg(dev, "IRQ %d registered: flags=%#lx trigger=%#x\n",
+ gdev->irq, irq_flags, irq_type);
+
+ ret = goodix_ec_uapi_register(gdev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register userspace interface\n");
+
+ enable_irq(gdev->irq);
+ gdev->irq_enabled = true;
+ dev_info(dev, "IRQ %d armed\n", gdev->irq);
+
+ dev_info(dev, "EC mailbox transport ready\n");
+ return 0;
+}
+
+static void goodix_ec_remove(struct platform_device *pdev)
+{
+ struct goodix_device *gdev = platform_get_drvdata(pdev);
+
+ if (!gdev)
+ return;
+
+ WRITE_ONCE(gdev->disconnected, true);
+ if (gdev->irq_enabled) {
+ disable_irq(gdev->irq);
+ synchronize_irq(gdev->irq);
+ gdev->irq_enabled = false;
+ }
+
+ goodix_ec_uapi_unregister(gdev);
+ mutex_lock(&gdev->transfer_lock);
+ kfree(gdev->rx_reassembly);
+ gdev->rx_reassembly = NULL;
+ gdev->rx_reassembly_len = 0;
+ gdev->rx_reassembly_received = 0;
+ gdev->rx_reassembly_active = false;
+
+ if (gdev->write_done_gpio)
+ gpiod_set_value_cansleep(gdev->write_done_gpio, 0);
+ if (gdev->read_done_gpio)
+ gpiod_set_value_cansleep(gdev->read_done_gpio, 0);
+ mutex_unlock(&gdev->transfer_lock);
+
+ dev_info(gdev->dev, "%s detached\n", gdev->model->name);
+}
+
+static int goodix_ec_suspend(struct device *dev)
+{
+ struct goodix_device *gdev = dev_get_drvdata(dev);
+ unsigned long flags;
+
+ if (!gdev || READ_ONCE(gdev->disconnected))
+ return 0;
+
+ WRITE_ONCE(gdev->suspended, true);
+ wake_up_interruptible_all(&gdev->rx_wait);
+ if (gdev->irq_enabled) {
+ disable_irq(gdev->irq);
+ synchronize_irq(gdev->irq);
+ gdev->irq_enabled = false;
+ }
+
+ mutex_lock(&gdev->transfer_lock);
+ gpiod_set_value_cansleep(gdev->write_done_gpio, 0);
+ gpiod_set_value_cansleep(gdev->read_done_gpio, 0);
+ kfree(gdev->rx_reassembly);
+ gdev->rx_reassembly = NULL;
+ gdev->rx_reassembly_len = 0;
+ gdev->rx_reassembly_received = 0;
+ gdev->rx_reassembly_active = false;
+ mutex_unlock(&gdev->transfer_lock);
+
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+ if (gdev->rx_fifo_ready)
+ kfifo_reset(&gdev->rx_fifo);
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ return 0;
+}
+
+static int goodix_ec_resume(struct device *dev)
+{
+ struct goodix_device *gdev = dev_get_drvdata(dev);
+
+ if (!gdev || READ_ONCE(gdev->disconnected))
+ return 0;
+
+ gpiod_set_value_cansleep(gdev->write_done_gpio, 0);
+ gpiod_set_value_cansleep(gdev->read_done_gpio, 0);
+ if (!gdev->irq_enabled) {
+ enable_irq(gdev->irq);
+ gdev->irq_enabled = true;
+ }
+ /* Permit new writes only after the receive path is armed. */
+ WRITE_ONCE(gdev->suspended, false);
+ wake_up_interruptible_all(&gdev->rx_wait);
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(goodix_ec_pm_ops, goodix_ec_suspend,
+ goodix_ec_resume);
+
+static const struct acpi_device_id goodix_ec_acpi_match[] = {
+ {
+ .id = GXFP5130_ACPI_HID,
+ .driver_data = (kernel_ulong_t)&gxfp5130_model,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, goodix_ec_acpi_match);
+
+static struct platform_driver goodix_ec_driver = {
+ .probe = goodix_ec_probe,
+ .remove = goodix_ec_remove,
+ .driver = {
+ .name = GOODIX_EC_DRIVER_NAME,
+ .acpi_match_table = ACPI_PTR(goodix_ec_acpi_match),
+ .pm = pm_sleep_ptr(&goodix_ec_pm_ops),
+ },
+};
+module_platform_driver(goodix_ec_driver);
+
+MODULE_AUTHOR("Ertugrul Topcu <ertugtopcu0@gmail.com>");
+MODULE_DESCRIPTION("Goodix fingerprint sensors over an EC mailbox");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_uapi.c b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_uapi.c
new file mode 100644
index 000000000000..d2f075acb481
--- /dev/null
+++ b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_uapi.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/capability.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/unaligned.h>
+
+#include "goodix_ec_mailbox.h"
+#include <linux/goodix_ec.h>
+
+#define GOODIX_EC_RX_FIFO_BYTES (256u * 1024u)
+
+static bool goodix_ec_rx_ready(struct goodix_device *gdev)
+{
+ unsigned long flags;
+ bool ready;
+
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+ ready = gdev->rx_fifo_ready && !kfifo_is_empty(&gdev->rx_fifo);
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+
+ return ready;
+}
+
+static int goodix_ec_uapi_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *misc = file->private_data;
+ struct goodix_device *gdev =
+ container_of(misc, struct goodix_device, miscdev);
+ unsigned long flags;
+
+ if (!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+ if (!goodix_ec_device_get(gdev))
+ return -ENODEV;
+ if (READ_ONCE(gdev->disconnected)) {
+ goodix_ec_device_put(gdev);
+ return -ENODEV;
+ }
+
+ if (file->f_mode & FMODE_READ) {
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+ if (!gdev->rx_fifo_ready) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ goodix_ec_device_put(gdev);
+ return -ENODEV;
+ }
+
+ if (gdev->rx_reader_open) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ goodix_ec_device_put(gdev);
+ return -EBUSY;
+ }
+
+ gdev->rx_reader_open = true;
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ }
+
+ file->private_data = gdev;
+ return 0;
+}
+
+static int goodix_ec_uapi_release(struct inode *inode, struct file *file)
+{
+ struct goodix_device *gdev = file->private_data;
+ unsigned long flags;
+
+ if (gdev && (file->f_mode & FMODE_READ)) {
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+ gdev->rx_reader_open = false;
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ }
+ if (gdev)
+ goodix_ec_device_put(gdev);
+
+ return 0;
+}
+
+static ssize_t goodix_ec_uapi_write(struct file *file,
+ const char __user *user_buffer,
+ size_t count, loff_t *position)
+{
+ struct goodix_device *gdev = file->private_data;
+ struct goodix_ec_tx_header header;
+ u8 *mp_packet;
+ size_t required;
+ int ret;
+
+ if (!gdev || !user_buffer || READ_ONCE(gdev->disconnected))
+ return -ENODEV;
+
+ if (count < sizeof(header))
+ return -EINVAL;
+
+ if (copy_from_user(&header, user_buffer, sizeof(header)))
+ return -EFAULT;
+
+ if (header.payload_len > GOODIX_EC_UAPI_TX_MAX)
+ return -EMSGSIZE;
+ if (header.reserved || header.flags)
+ return -EINVAL;
+
+ required = sizeof(header) + header.payload_len;
+ if (count != required)
+ return -EINVAL;
+
+ mp_packet = kmalloc(GOODIX_MP_HEADER_SIZE +
+ header.payload_len, GFP_KERNEL);
+ if (!mp_packet)
+ return -ENOMEM;
+
+ mp_packet[0] = header.mp_flags;
+ put_unaligned_le16(header.payload_len, mp_packet + 1);
+ mp_packet[3] = mp_packet[0] + mp_packet[1] + mp_packet[2];
+
+ if (header.payload_len &&
+ copy_from_user(mp_packet + sizeof(struct goodix_mp_header),
+ user_buffer + sizeof(header),
+ header.payload_len)) {
+ kfree(mp_packet);
+ return -EFAULT;
+ }
+
+ mutex_lock(&gdev->transfer_lock);
+ if (READ_ONCE(gdev->disconnected))
+ ret = -ENODEV;
+ else if (READ_ONCE(gdev->suspended))
+ ret = -EHOSTDOWN;
+ else
+ ret = goodix_ec_sync_send(gdev, mp_packet,
+ sizeof(struct goodix_mp_header) +
+ header.payload_len);
+ mutex_unlock(&gdev->transfer_lock);
+
+ kfree(mp_packet);
+
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t goodix_ec_uapi_read(struct file *file, char __user *user_buffer,
+ size_t count, loff_t *position)
+{
+ struct goodix_device *gdev = file->private_data;
+ struct goodix_ec_record_header header;
+ unsigned long flags;
+ u8 *record;
+ size_t required;
+ int ret;
+
+ if (!gdev || !user_buffer || READ_ONCE(gdev->disconnected))
+ return -ENODEV;
+
+ ret = mutex_lock_interruptible(&gdev->rx_read_lock);
+ if (ret)
+ return ret;
+
+ for (;;) {
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+
+ if (!gdev->rx_fifo_ready) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+ if (READ_ONCE(gdev->suspended)) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ ret = -EHOSTDOWN;
+ goto out_unlock;
+ }
+
+ if (kfifo_len(&gdev->rx_fifo) >= sizeof(header))
+ break;
+
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ goto out_unlock;
+ }
+
+ ret = wait_event_interruptible(gdev->rx_wait,
+ goodix_ec_rx_ready(gdev) ||
+ !READ_ONCE(gdev->rx_fifo_ready) ||
+ READ_ONCE(gdev->suspended));
+ if (ret)
+ goto out_unlock;
+ }
+
+ if (kfifo_out_peek(&gdev->rx_fifo, &header,
+ sizeof(header)) != sizeof(header)) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ ret = -EIO;
+ goto out_unlock;
+ }
+
+ required = sizeof(header) + header.len;
+ if (kfifo_len(&gdev->rx_fifo) < required) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ ret = -EIO;
+ goto out_unlock;
+ }
+
+ if (count < required) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ ret = -EMSGSIZE;
+ goto out_unlock;
+ }
+
+ record = kmalloc(required, GFP_ATOMIC);
+ if (!record) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+
+ if (kfifo_out(&gdev->rx_fifo, record, required) != required) {
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ kfree(record);
+ ret = -EIO;
+ goto out_unlock;
+ }
+
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+
+ if (copy_to_user(user_buffer, record, required)) {
+ kfree(record);
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ kfree(record);
+ ret = required;
+
+out_unlock:
+ mutex_unlock(&gdev->rx_read_lock);
+ return ret;
+}
+
+static __poll_t goodix_ec_uapi_poll(struct file *file, poll_table *wait)
+{
+ struct goodix_device *gdev = file->private_data;
+ unsigned long flags;
+ __poll_t mask = 0;
+
+ if (!gdev || READ_ONCE(gdev->disconnected))
+ return EPOLLERR;
+
+ poll_wait(file, &gdev->rx_wait, wait);
+
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+ if (!gdev->rx_fifo_ready || READ_ONCE(gdev->disconnected))
+ mask = EPOLLERR | EPOLLHUP;
+ else if (READ_ONCE(gdev->suspended))
+ mask = EPOLLERR;
+ else if (!kfifo_is_empty(&gdev->rx_fifo))
+ mask = EPOLLIN | EPOLLRDNORM;
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+
+ return mask;
+}
+
+static long goodix_ec_uapi_ioctl(struct file *file,
+ unsigned int command,
+ unsigned long argument)
+{
+ struct goodix_device *gdev = file->private_data;
+ unsigned long flags;
+
+ if (!gdev || READ_ONCE(gdev->disconnected))
+ return -ENODEV;
+
+ switch (command) {
+ case GOODIX_EC_IOCTL_FLUSH_RX:
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+ if (gdev->rx_fifo_ready)
+ kfifo_reset(&gdev->rx_fifo);
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations goodix_ec_uapi_fops = {
+ .owner = THIS_MODULE,
+ .open = goodix_ec_uapi_open,
+ .release = goodix_ec_uapi_release,
+ .read = goodix_ec_uapi_read,
+ .write = goodix_ec_uapi_write,
+ .poll = goodix_ec_uapi_poll,
+ .unlocked_ioctl = goodix_ec_uapi_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .llseek = noop_llseek,
+};
+
+int goodix_ec_uapi_register(struct goodix_device *gdev)
+{
+ int ret;
+
+ spin_lock_init(&gdev->rx_fifo_lock);
+ mutex_init(&gdev->rx_read_lock);
+ init_waitqueue_head(&gdev->rx_wait);
+
+ ret = kfifo_alloc(&gdev->rx_fifo, GOODIX_EC_RX_FIFO_BYTES, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ gdev->rx_fifo_ready = true;
+ gdev->rx_reader_open = false;
+
+ gdev->miscdev.minor = MISC_DYNAMIC_MINOR;
+ gdev->miscdev.name = "gxfp";
+ gdev->miscdev.fops = &goodix_ec_uapi_fops;
+ gdev->miscdev.parent = gdev->dev;
+
+ ret = misc_register(&gdev->miscdev);
+ if (ret) {
+ gdev->rx_fifo_ready = false;
+ kfifo_free(&gdev->rx_fifo);
+ return ret;
+ }
+
+ gdev->misc_registered = true;
+ dev_info(gdev->dev, "userspace interface registered: /dev/gxfp\n");
+ return 0;
+}
+
+void goodix_ec_uapi_unregister(struct goodix_device *gdev)
+{
+ unsigned long flags;
+
+ if (!gdev)
+ return;
+
+ if (gdev->misc_registered) {
+ misc_deregister(&gdev->miscdev);
+ gdev->misc_registered = false;
+ }
+
+ if (!gdev->rx_fifo_ready)
+ return;
+
+ spin_lock_irqsave(&gdev->rx_fifo_lock, flags);
+ gdev->rx_fifo_ready = false;
+ gdev->rx_reader_open = false;
+ kfifo_reset(&gdev->rx_fifo);
+ spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags);
+
+ wake_up_interruptible_all(&gdev->rx_wait);
+ kfifo_free(&gdev->rx_fifo);
+}
diff --git a/include/uapi/linux/goodix_ec.h b/include/uapi/linux/goodix_ec.h
new file mode 100644
index 000000000000..ff8d0d505ae7
--- /dev/null
+++ b/include/uapi/linux/goodix_ec.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_GOODIX_EC_H_
+#define _UAPI_GOODIX_EC_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define GOODIX_EC_UAPI_MAGIC 'G'
+#define GOODIX_EC_UAPI_TX_MAX 500u
+#define GOODIX_EC_UAPI_RX_MAX (128u * 1024u)
+
+/*
+ * read(2) returns one record:
+ * struct goodix_ec_record_header
+ * followed by len bytes of MP payload (normally one Goodix frame).
+ */
+struct goodix_ec_record_header {
+ __u32 len;
+ __u32 mp_type;
+ __u64 timestamp_ns;
+};
+
+/*
+ * write(2) accepts:
+ * struct goodix_ec_tx_header
+ * followed by payload_len bytes used as the MP payload.
+ */
+struct goodix_ec_tx_header {
+ __u8 mp_flags;
+ __u8 reserved;
+ __u16 payload_len;
+ __u32 flags;
+};
+
+#define GOODIX_EC_IOCTL_FLUSH_RX _IO(GOODIX_EC_UAPI_MAGIC, 0x11)
+
+#endif /* _UAPI_GOODIX_EC_H_ */
--
2.55.0
next prev parent reply other threads:[~2026-08-03 7:45 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-27 20:58 [PATCH] platform/x86: Add Goodix fingerprint EC mailbox transport Ertuğrul Topçu
2026-08-03 7:44 ` Ertuğrul Topçu [this message]
2026-08-16 1:26 ` kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260803074454.49474-1-ertugtopcu0@gmail.com \
--to=ertugtopcu0@gmail.com \
--cc=hansg@kernel.org \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=linux-kernel@vger.kernel.org \
--cc=platform-driver-x86@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.