From: "Metehan Günen" <metehangnen@gmail.com>
To: linux-kernel@vger.kernel.org
Cc: gregkh@linuxfoundation.org, linux-acpi@vger.kernel.org,
linux-gpio@vger.kernel.org,
"Metehan Günen" <metehangnen@gmail.com>
Subject: [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver
Date: Sat, 18 Jul 2026 11:14:29 +0300 [thread overview]
Message-ID: <20260718081431.25456-3-metehangnen@gmail.com> (raw)
In-Reply-To: <20260718081431.25456-1-metehangnen@gmail.com>
The GXFP5130 is a press-type fingerprint sensor by Goodix Technology.
It is found in Huawei MateBook laptops (D16 2024, X Pro 2024, 14 2024)
and is enumerated through ACPI with HID GXFP5130.
Hardware interface: The sensor does not sit on SPI or I2C. The ACPI
firmware maps a memory window (eSPI flash-channel mailbox) and three
GPIOs: write-done (host pulses after writing a command), read-done
(host pulses after reading each response chunk), and irq (sensor asserts
to signal a response is ready). All frames use a 4-byte eSPI wrapper
header followed by a Goodix MP protocol frame carrying the command body.
Driver structure:
transport/ eSPI framing layer (TX, synchronous RX, IRQ-driven RX)
hw/ ACPI resource parsing, MMIO helpers, GPIO helpers, delay
proto/ MP and Goodix command protocol encode/decode
cmd/ individual command helpers (version, MCU state, reset ...)
driver/ platform probe/remove, IRQ thread, UAPI misc device,
debugfs trace ring buffer
include/ private driver headers (gxfp_priv.h, gxfp_constants.h)
Userspace interface: A misc character device /dev/gxfp is created. Only
one reader at a time is allowed (CAP_SYS_ADMIN required). write(2) sends
command frames; read(2)/poll(2) return response records prefixed with
gxfp_tap_hdr. GXFP_IOCTL_FLUSH_RXQ discards the kernel-side queue.
The libfprint library communicates with the sensor through this device.
Startup: On probe the driver executes a firmware-version handshake (up
to 3 retries) and falls back to a session-recovery sequence if the
sensor does not acknowledge the first attempt. The IRQ line is kept
masked until the handshake succeeds.
Signed-off-by: Metehan Günen <metehangnen@gmail.com>
---
drivers/misc/Kconfig | 2 +
drivers/misc/Makefile | 2 +
drivers/misc/gxfp5130/Kconfig | 18 ++
drivers/misc/gxfp5130/Makefile | 28 ++
drivers/misc/gxfp5130/cmd/gxfp_cmd.c | 177 ++++++++++++
drivers/misc/gxfp5130/cmd/gxfp_cmd.h | 54 ++++
.../misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c | 28 ++
.../misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h | 12 +
.../misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c | 89 +++++++
.../misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h | 30 +++
drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c | 185 +++++++++++++
drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h | 27 ++
drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c | 100 +++++++
drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h | 22 ++
drivers/misc/gxfp5130/driver/gxfp_irq.c | 57 ++++
drivers/misc/gxfp5130/driver/gxfp_irq.h | 9 +
drivers/misc/gxfp5130/driver/gxfp_platform.c | 251 ++++++++++++++++++
drivers/misc/gxfp5130/driver/gxfp_platform.h | 12 +
drivers/misc/gxfp5130/driver/gxfp_trace.c | 179 +++++++++++++
drivers/misc/gxfp5130/driver/gxfp_trace.h | 15 ++
drivers/misc/gxfp5130/driver/gxfp_uapi.h | 25 ++
drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c | 136 ++++++++++
drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c | 245 +++++++++++++++++
drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c | 123 +++++++++
drivers/misc/gxfp5130/gxfp_main.c | 127 +++++++++
drivers/misc/gxfp5130/hw/gxfp_acpi.c | 130 +++++++++
drivers/misc/gxfp5130/hw/gxfp_acpi.h | 15 ++
drivers/misc/gxfp5130/hw/gxfp_delay.c | 15 ++
drivers/misc/gxfp5130/hw/gxfp_delay.h | 7 +
drivers/misc/gxfp5130/hw/gxfp_gpio.c | 64 +++++
drivers/misc/gxfp5130/hw/gxfp_gpio.h | 17 ++
drivers/misc/gxfp5130/hw/gxfp_mmio.c | 45 ++++
drivers/misc/gxfp5130/hw/gxfp_mmio.h | 10 +
.../misc/gxfp5130/include/gxfp_constants.h | 38 +++
drivers/misc/gxfp5130/include/gxfp_priv.h | 55 ++++
.../misc/gxfp5130/proto/gxfp_goodix_proto.c | 84 ++++++
.../misc/gxfp5130/proto/gxfp_goodix_proto.h | 31 +++
drivers/misc/gxfp5130/proto/gxfp_mp_proto.c | 96 +++++++
drivers/misc/gxfp5130/proto/gxfp_mp_proto.h | 27 ++
.../gxfp5130/transport/gxfp_espi_common.c | 80 ++++++
.../gxfp5130/transport/gxfp_espi_common.h | 27 ++
.../gxfp5130/transport/gxfp_espi_rx_irq.c | 120 +++++++++
.../gxfp5130/transport/gxfp_espi_rx_irq.h | 15 ++
.../gxfp5130/transport/gxfp_espi_rx_sync.c | 96 +++++++
.../gxfp5130/transport/gxfp_espi_rx_sync.h | 14 +
.../misc/gxfp5130/transport/gxfp_espi_tx.c | 88 ++++++
.../misc/gxfp5130/transport/gxfp_espi_tx.h | 20 ++
47 files changed, 3047 insertions(+)
create mode 100644 drivers/misc/gxfp5130/Kconfig
create mode 100644 drivers/misc/gxfp5130/Makefile
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd.c
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd.h
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c
create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_irq.c
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_irq.h
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_platform.c
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_platform.h
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_trace.c
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_trace.h
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_uapi.h
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c
create mode 100644 drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c
create mode 100644 drivers/misc/gxfp5130/gxfp_main.c
create mode 100644 drivers/misc/gxfp5130/hw/gxfp_acpi.c
create mode 100644 drivers/misc/gxfp5130/hw/gxfp_acpi.h
create mode 100644 drivers/misc/gxfp5130/hw/gxfp_delay.c
create mode 100644 drivers/misc/gxfp5130/hw/gxfp_delay.h
create mode 100644 drivers/misc/gxfp5130/hw/gxfp_gpio.c
create mode 100644 drivers/misc/gxfp5130/hw/gxfp_gpio.h
create mode 100644 drivers/misc/gxfp5130/hw/gxfp_mmio.c
create mode 100644 drivers/misc/gxfp5130/hw/gxfp_mmio.h
create mode 100644 drivers/misc/gxfp5130/include/gxfp_constants.h
create mode 100644 drivers/misc/gxfp5130/include/gxfp_priv.h
create mode 100644 drivers/misc/gxfp5130/proto/gxfp_goodix_proto.c
create mode 100644 drivers/misc/gxfp5130/proto/gxfp_goodix_proto.h
create mode 100644 drivers/misc/gxfp5130/proto/gxfp_mp_proto.c
create mode 100644 drivers/misc/gxfp5130/proto/gxfp_mp_proto.h
create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_common.c
create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_common.h
create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.c
create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.h
create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.c
create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.h
create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_tx.c
create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_tx.h
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 390256ed9..5b20aa292 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -651,4 +651,6 @@ source "drivers/misc/mchp_pci1xxxx/Kconfig"
source "drivers/misc/keba/Kconfig"
source "drivers/misc/amd-sbi/Kconfig"
source "drivers/misc/rp1/Kconfig"
+source "drivers/misc/gxfp5130/Kconfig"
+
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index fed47c767..e85c68aa1 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -74,3 +74,5 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o
obj-y += keba/
obj-y += amd-sbi/
obj-$(CONFIG_MISC_RP1) += rp1/
+
+obj-$(CONFIG_GXFP5130) += gxfp5130/
diff --git a/drivers/misc/gxfp5130/Kconfig b/drivers/misc/gxfp5130/Kconfig
new file mode 100644
index 000000000..0af2a0dec
--- /dev/null
+++ b/drivers/misc/gxfp5130/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+config GXFP5130
+ tristate "Goodix GXFP5130 eSPI fingerprint sensor"
+ depends on ACPI && HAS_IOMEM && GPIOLIB
+ help
+ Driver for the Goodix GXFP5130 fingerprint sensor found in
+ Huawei MateBook laptops (MateBook D16 2024, MateBook X Pro 2024,
+ MateBook 14 2024).
+
+ The sensor connects to the host via an eSPI-based MMIO mailbox
+ and is enumerated through ACPI (ACPI HID: GXFP5130).
+
+ A character device (/dev/gxfp) is created for userspace access.
+ The libfprint library uses this interface for biometric enrollment
+ and verification via fprintd.
+
+ If unsure, say N. To compile as a module, say M; the module
+ will be called gxfp.
diff --git a/drivers/misc/gxfp5130/Makefile b/drivers/misc/gxfp5130/Makefile
new file mode 100644
index 000000000..0bb206d23
--- /dev/null
+++ b/drivers/misc/gxfp5130/Makefile
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_GXFP5130) += gxfp.o
+
+ccflags-y += -I$(src)/include
+
+gxfp-y := \
+ gxfp_main.o \
+ transport/gxfp_espi_common.o \
+ transport/gxfp_espi_tx.o \
+ transport/gxfp_espi_rx_sync.o \
+ transport/gxfp_espi_rx_irq.o \
+ hw/gxfp_acpi.o \
+ hw/gxfp_delay.o \
+ hw/gxfp_mmio.o \
+ hw/gxfp_gpio.o \
+ proto/gxfp_mp_proto.o \
+ proto/gxfp_goodix_proto.o \
+ cmd/gxfp_cmd.o \
+ cmd/gxfp_cmd_version.o \
+ cmd/gxfp_cmd_mcu_state.o \
+ cmd/gxfp_cmd_mcu_config.o \
+ cmd/gxfp_cmd_reset.o \
+ driver/gxfp_platform.o \
+ driver/gxfp_irq.o \
+ driver/gxfp_trace.o \
+ driver/gxfp_uapi_fifo.o \
+ driver/gxfp_uapi_ctrl.o \
+ driver/gxfp_uapi_misc.o
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd.c b/drivers/misc/gxfp5130/cmd/gxfp_cmd.c
new file mode 100644
index 000000000..90a50710f
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+
+#include "gxfp_cmd.h"
+#include "../proto/gxfp_goodix_proto.h"
+#include "../proto/gxfp_mp_proto.h"
+#include "../transport/gxfp_espi_tx.h"
+#include "../hw/gxfp_delay.h"
+#include "gxfp_priv.h"
+#include "gxfp_constants.h"
+#include "../driver/gxfp_trace.h"
+
+int gxfp_xfer(struct gxfp_dev *gdev, const struct gxfp_xfer_req *req)
+{
+ __u8 *tx;
+ __u8 *body;
+ __u8 *rx;
+ size_t rx_len = 0;
+ unsigned int t;
+ int body_len;
+ int frame_len;
+ int ret;
+
+ if (!gdev || !req)
+ return -EINVAL;
+ if (req->req_payload_len && !req->req_payload)
+ return -EINVAL;
+ if (req->resp_payload && req->resp_payload_cap == 0)
+ return -EINVAL;
+ if (!req->resp_payload && req->resp_payload_cap != 0)
+ return -EINVAL;
+
+ tx = kmalloc(GXFP_TX_BUFFER_SIZE, GFP_KERNEL);
+ if (!tx)
+ return -ENOMEM;
+ body = kmalloc(GXFP_TX_BUFFER_SIZE, GFP_KERNEL);
+ if (!body) {
+ ret = -ENOMEM;
+ goto out_free_tx;
+ }
+
+ rx = kmalloc(GXFP_MAILBOX_MIN_SIZE, GFP_KERNEL);
+ if (!rx) {
+ ret = -ENOMEM;
+ goto out_free_body;
+ }
+
+ body_len = gxfp_goodix_build_frame(req->req_cmd,
+ req->req_payload, req->req_payload_len,
+ body, GXFP_TX_BUFFER_SIZE);
+ if (body_len < 0) {
+ ret = body_len;
+ goto out_free_rx;
+ }
+
+ frame_len = gxfp_mp_build_frame(GXFP_MP_TYPE_A,
+ body, (size_t)body_len,
+ tx, GXFP_TX_BUFFER_SIZE);
+ if (frame_len < 0) {
+ ret = frame_len;
+ goto out_free_rx;
+ }
+ gxfp_trace_logf("cmd_req req=0x%02x expect=0x%02x tries=%u tx_mp_len=%d",
+ req->req_cmd, req->expect_cmd, req->tries ? req->tries : 1, frame_len);
+
+ for (t = 0; t < (req->tries ? req->tries : 1); t++) {
+ struct gxfp_mp_frame_parsed mp;
+ struct gxfp_frame_parsed p;
+
+ ret = gxfp_espi_xfer(gdev, tx, (size_t)frame_len,
+ rx, GXFP_MAILBOX_MIN_SIZE, &rx_len);
+ if (ret) {
+ gxfp_trace_logf("cmd_retry req=0x%02x expect=0x%02x try=%u rc=%d",
+ req->req_cmd, req->expect_cmd, t + 1, ret);
+ gxfp_busy_wait_us(req->retry_delay_us);
+ continue;
+ }
+
+ if (!gxfp_parse_mp_frame(rx,
+ min_t(size_t, rx_len, (size_t)GXFP_MAILBOX_MIN_SIZE),
+ &mp)) {
+ gxfp_trace_logf("cmd_bad_mp req=0x%02x expect=0x%02x try=%u rx_len=%zu",
+ req->req_cmd, req->expect_cmd, t + 1, rx_len);
+ gxfp_busy_wait_us(req->retry_delay_us);
+ continue;
+ }
+ if (!gxfp_parse_goodix_body(mp.payload, mp.payload_len, &p) || !p.valid) {
+ gxfp_busy_wait_us(req->retry_delay_us);
+ continue;
+ }
+ if (req->require_proto_ck_ok && !p.proto_checksum_ok) {
+ gxfp_busy_wait_us(req->retry_delay_us);
+ continue;
+ }
+ if (p.cmd != req->expect_cmd) {
+ gxfp_trace_logf("cmd_unexpected req=0x%02x expect=0x%02x got=0x%02x try=%u",
+ req->req_cmd, req->expect_cmd, p.cmd, t + 1);
+ gxfp_busy_wait_us(req->retry_delay_us);
+ continue;
+ }
+
+ if (req->resp_payload) {
+ if (p.payload_len > req->resp_payload_cap) {
+ ret = -EMSGSIZE;
+ goto out_free_rx;
+ }
+ memcpy(req->resp_payload, p.payload, p.payload_len);
+ }
+
+ if (req->out_resp_payload_len)
+ *req->out_resp_payload_len = p.payload_len;
+ if (req->out_frame) {
+ *req->out_frame = p;
+ req->out_frame->payload = req->resp_payload;
+ }
+ ret = 0;
+ gxfp_trace_logf("cmd_ok req=0x%02x expect=0x%02x try=%u payload=%u",
+ req->req_cmd, req->expect_cmd, t + 1, p.payload_len);
+ goto out_free_rx;
+ }
+
+ ret = -ETIMEDOUT;
+ gxfp_trace_logf("cmd_timeout req=0x%02x expect=0x%02x tries=%u",
+ req->req_cmd, req->expect_cmd, req->tries ? req->tries : 1);
+
+out_free_rx:
+ kfree(rx);
+out_free_body:
+ kfree(body);
+out_free_tx:
+ kfree(tx);
+ return ret;
+}
+
+int gxfp_cmd_xfer(struct gxfp_dev *gdev,
+ __u8 req_cmd,
+ const __u8 *req_payload,
+ size_t req_payload_len,
+ __u8 expect_cmd,
+ __u8 *resp_payload,
+ size_t resp_payload_cap,
+ size_t *out_resp_payload_len,
+ unsigned int tries,
+ unsigned int retry_delay_us)
+{
+ struct gxfp_frame_parsed p;
+ int ret;
+
+ if (!resp_payload || resp_payload_cap == 0)
+ return -EINVAL;
+
+ memset(&p, 0, sizeof(p));
+ ret = gxfp_xfer(gdev, &(const struct gxfp_xfer_req){
+ .req_cmd = req_cmd,
+ .req_payload = req_payload,
+ .req_payload_len = req_payload_len,
+ .expect_cmd = expect_cmd,
+ .require_proto_ck_ok = true,
+ .out_frame = &p,
+ .resp_payload = resp_payload,
+ .resp_payload_cap = resp_payload_cap,
+ .out_resp_payload_len = out_resp_payload_len,
+ .tries = tries,
+ .retry_delay_us = retry_delay_us,
+ });
+ if (ret)
+ return ret;
+
+ dev_dbg(gdev->dev,
+ "CMD_XFER: req=0x%02x expect=0x%02x payload_len=%u\n",
+ req_cmd, expect_cmd, p.payload_len);
+ return 0;
+}
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd.h b/drivers/misc/gxfp5130/cmd/gxfp_cmd.h
new file mode 100644
index 000000000..7d7b5bba3
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_CMD_H
+#define __GXFP_CMD_H
+
+#include <linux/types.h>
+
+struct gxfp_dev;
+struct gxfp_frame_parsed;
+
+/**
+ * struct gxfp_xfer_req - Parameters for a single Goodix command exchange.
+ * @req_cmd: Command byte to send.
+ * @expect_cmd: Expected command byte in the response frame.
+ * @require_proto_ck_ok: If true, require protocol checksum to pass.
+ * @tries: Number of send/receive attempts (0 treated as 1).
+ * @retry_delay_us: Microseconds to wait between retries.
+ * @req_payload: Pointer to request payload (may be NULL).
+ * @req_payload_len: Length of request payload in bytes.
+ * @out_frame: If non-NULL, receives parsed frame metadata on success.
+ * @resp_payload: If non-NULL, response payload is copied here.
+ * @resp_payload_cap: Capacity of @resp_payload buffer.
+ * @out_resp_payload_len: If non-NULL, receives actual response payload length.
+ */
+struct gxfp_xfer_req {
+ __u8 req_cmd;
+ __u8 expect_cmd;
+ bool require_proto_ck_ok;
+ unsigned int tries;
+ unsigned int retry_delay_us;
+
+ const __u8 *req_payload;
+ size_t req_payload_len;
+
+ struct gxfp_frame_parsed *out_frame;
+
+ __u8 *resp_payload;
+ size_t resp_payload_cap;
+ size_t *out_resp_payload_len;
+};
+
+int gxfp_xfer(struct gxfp_dev *gdev, const struct gxfp_xfer_req *req);
+
+int gxfp_cmd_xfer(struct gxfp_dev *gdev,
+ __u8 req_cmd,
+ const __u8 *req_payload,
+ size_t req_payload_len,
+ __u8 expect_cmd,
+ __u8 *resp_payload,
+ size_t resp_payload_cap,
+ size_t *out_resp_payload_len,
+ unsigned int tries,
+ unsigned int retry_delay_us);
+
+#endif /* __GXFP_CMD_H */
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c
new file mode 100644
index 000000000..cc38d7d96
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/errno.h>
+
+#include "gxfp_priv.h"
+#include "gxfp_cmd.h"
+#include "gxfp_constants.h"
+#include "gxfp_cmd_mcu_config.h"
+
+int gxfp_cmd_upload_config_mcu(struct gxfp_dev *gdev, const __u8 *cfg, size_t cfg_len)
+{
+ __u8 ack_payload[32];
+ size_t ack_len = 0;
+
+ if (!gdev)
+ return -EINVAL;
+ if (!cfg || cfg_len == 0)
+ return -EINVAL;
+
+ return gxfp_cmd_xfer(gdev,
+ GXFP_CMD_UPLOAD_CONFIG_MCU,
+ cfg, cfg_len,
+ GXFP_CMD_ACK,
+ ack_payload, sizeof(ack_payload),
+ &ack_len,
+ GXFP_DEFAULT_RETRY_COUNT,
+ GXFP_DEFAULT_TIMEOUT_US);
+}
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h
new file mode 100644
index 000000000..9a32aa802
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_CMD_MCU_CONFIG_H
+#define __GXFP_CMD_MCU_CONFIG_H
+
+#include <linux/types.h>
+
+struct gxfp_dev;
+
+/* Not used or tested */
+int gxfp_cmd_upload_config_mcu(struct gxfp_dev *gdev, const __u8 *cfg, size_t cfg_len);
+
+#endif /* __GXFP_CMD_MCU_CONFIG_H */
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c
new file mode 100644
index 000000000..d6a238474
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/timekeeping.h>
+
+#include "gxfp_priv.h"
+#include "gxfp_cmd.h"
+#include "gxfp_constants.h"
+#include "gxfp_cmd_mcu_state.h"
+
+int gxfp_cmd_parse_mcu_state_payload(const __u8 *buf, size_t len,
+ struct gxfp_mcu_state *out)
+{
+ if (!buf || !out)
+ return -EINVAL;
+
+ memset(out, 0, sizeof(*out));
+ if (len < GXFP_MCU_STATE_LEN)
+ return -EBADMSG;
+
+ out->version = buf[0];
+
+ out->b1_raw = buf[1];
+ out->is_pov_image_valid = (buf[1] >> 0) & 0x1;
+ out->is_tls_connected = (buf[1] >> 1) & 0x1;
+ out->is_tls_used = (buf[1] >> 2) & 0x1;
+ out->is_locked = (buf[1] >> 3) & 0x1;
+
+ memcpy(out->reserved, buf + 2, sizeof(out->reserved));
+
+ return 0;
+}
+
+static int gxfp_do_query_mcu_state(struct gxfp_dev *gdev,
+ struct gxfp_mcu_state *out)
+{
+ __u8 payload[GXFP_MAX_MCU_STATE_PAYLOAD];
+ __u8 trigger_payload[5];
+ size_t payload_len = 0;
+ int ret;
+ struct timespec64 ts;
+ __u16 ts_u16;
+
+ if (!out)
+ return -EINVAL;
+
+ memset(out, 0, sizeof(*out));
+
+ if (!gdev->hw.mailbox_mmio || gdev->hw.mailbox_size < GXFP_MAILBOX_MIN_SIZE)
+ return -ENODEV;
+
+ ktime_get_real_ts64(&ts);
+ ts_u16 = (__u16)(((__u64)(ts.tv_sec % 60) * 1000ull) + ((__u64)ts.tv_nsec / 1000000ull));
+ trigger_payload[0] = 0x55;
+ trigger_payload[1] = (__u8)(ts_u16 & 0xff);
+ trigger_payload[2] = (__u8)((ts_u16 >> 8) & 0xff);
+ trigger_payload[3] = 0x00;
+ trigger_payload[4] = 0x00;
+ dev_dbg(gdev->dev, "MCU_STATE: trigger ts=0x%04x payload=%*ph\n",
+ ts_u16, (int)sizeof(trigger_payload), trigger_payload);
+
+ ret = gxfp_cmd_xfer(gdev,
+ GXFP_CMD_TRIGGER_MCU_STATE, trigger_payload, sizeof(trigger_payload),
+ GXFP_CMD_QUERY_MCU_STATE,
+ payload, sizeof(payload), &payload_len,
+ GXFP_DEFAULT_RETRY_COUNT, GXFP_DEFAULT_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ ret = gxfp_cmd_parse_mcu_state_payload(payload, payload_len, out);
+ if (ret)
+ return ret;
+
+ dev_dbg(gdev->dev,
+ "MCU_STATE: version=%u pov_valid=%u tls=%u locked=%u\n",
+ out->version,
+ out->is_pov_image_valid,
+ out->is_tls_connected,
+ out->is_locked);
+ return 0;
+}
+
+int gxfp_cmd_query_mcu_state(struct gxfp_dev *gdev, struct gxfp_mcu_state *out)
+{
+ return gxfp_do_query_mcu_state(gdev, out);
+}
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h
new file mode 100644
index 000000000..593b62499
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_CMD_MCU_STATE_H
+#define __GXFP_CMD_MCU_STATE_H
+
+#include <linux/types.h>
+#include "gxfp_constants.h"
+
+struct gxfp_dev;
+
+#define GXFP_MCU_STATE_LEN 20
+
+struct gxfp_mcu_state {
+ /* byte 0 */
+ __u8 version;
+
+ /* byte 1 */
+ __u8 b1_raw;
+ __u8 is_pov_image_valid;
+ __u8 is_tls_connected;
+ __u8 is_tls_used;
+ __u8 is_locked;
+
+ /* byte 2 - 19 */
+ __u8 reserved[18];
+};
+
+int gxfp_cmd_query_mcu_state(struct gxfp_dev *gdev, struct gxfp_mcu_state *out);
+int gxfp_cmd_parse_mcu_state_payload(const __u8 *buf, size_t len, struct gxfp_mcu_state *out);
+
+#endif /* __GXFP_CMD_MCU_STATE_H */
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c b/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c
new file mode 100644
index 000000000..b2edba33a
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include "gxfp_priv.h"
+#include "gxfp_cmd.h"
+#include "../hw/gxfp_delay.h"
+#include "gxfp_constants.h"
+
+#include "gxfp_cmd_reset.h"
+
+int gxfp_cmd_notify_power_state(struct gxfp_dev *gdev,
+ __u8 state)
+{
+ __u8 payload[1];
+ __u8 ack_payload[8];
+ size_t ack_len = 0;
+ int rc;
+
+ if (!gdev)
+ return -EINVAL;
+
+ payload[0] = state;
+ rc = gxfp_cmd_xfer(gdev,
+ GXFP_CMD_NOTIFY_POWER_STATE,
+ payload, sizeof(payload),
+ GXFP_CMD_ACK,
+ ack_payload, sizeof(ack_payload), &ack_len,
+ 1,
+ GXFP_DEFAULT_TIMEOUT_US);
+
+ dev_dbg(gdev->dev,
+ "NOTIFY_POWER_STATE: cmd=0x0E state=%u rc=%d ack_len=%zu\n",
+ state, rc, ack_len);
+ return rc;
+}
+
+int gxfp_cmd_sleep_cleanup(struct gxfp_dev *gdev, unsigned int post_delay_ms)
+{
+ static const __u8 sleep_payload[2] = { 0x00, 0x00 };
+ __u8 ack_payload[8];
+ size_t ack_len = 0;
+ int rc;
+
+ if (!gdev)
+ return -EINVAL;
+
+ rc = gxfp_cmd_xfer(gdev,
+ GXFP_CMD_SLEEP_CLEANUP,
+ sleep_payload, sizeof(sleep_payload),
+ GXFP_CMD_ACK,
+ ack_payload, sizeof(ack_payload), &ack_len,
+ 1,
+ GXFP_DEFAULT_TIMEOUT_US);
+ dev_dbg(gdev->dev, "SLEEP/CLEANUP: cmd=0xD2 rc=%d ack_len=%zu\n", rc, ack_len);
+
+ if (post_delay_ms)
+ msleep(post_delay_ms);
+
+ return rc;
+}
+
+int gxfp_cmd_reset_device(struct gxfp_dev *gdev,
+ __u8 reset_flags)
+{
+ __u8 payload[2];
+ __u8 ack_payload[8];
+ size_t ack_len = 0;
+ int rc;
+
+ if (!gdev)
+ return -EINVAL;
+
+ payload[0] = reset_flags;
+ payload[1] = 0x14;
+
+ rc = gxfp_cmd_xfer(gdev,
+ GXFP_CMD_RESET_DEVICE,
+ payload, sizeof(payload),
+ GXFP_CMD_ACK,
+ ack_payload, sizeof(ack_payload), &ack_len,
+ 1,
+ GXFP_DEFAULT_TIMEOUT_US);
+ dev_dbg(gdev->dev,
+ "RESET_DEVICE: cmd=0xA1 flags=0x%02x rc=%d ack_len=%zu\n",
+ reset_flags, rc, ack_len);
+ return rc;
+}
+
+int gxfp_cmd_recover_session(struct gxfp_dev *gdev, bool unstick_tls)
+{
+ int rc = 0;
+ int r;
+ unsigned int tries;
+
+ if (!gdev)
+ return -EINVAL;
+
+ /* D0Exit notifies MCU power state */
+ for (tries = 0; tries < 3; tries++) {
+ r = gxfp_cmd_notify_power_state(gdev, 1);
+ if (!r)
+ break;
+ if ((tries + 1) < 3)
+ usleep_range(10000, 11000);
+ }
+ if (r && !rc)
+ rc = r;
+ gxfp_busy_wait_us(GXFP_RESET_DELAY_US);
+
+ if (unstick_tls) {
+ r = gxfp_cmd_protocol_init(gdev);
+ if (r && !rc)
+ rc = r;
+ gxfp_busy_wait_us(1000);
+
+ r = gxfp_cmd_tls_unlock(gdev);
+ if (r && !rc)
+ rc = r;
+ gxfp_busy_wait_us(1000);
+ }
+
+ r = gxfp_cmd_sleep_cleanup(gdev, 200);
+ if (r && !rc)
+ rc = r;
+
+ for (tries = 0; tries < 6; tries++) {
+ r = gxfp_cmd_reset_device(gdev, GXFP_RESET_FLAG_MCU_SENSOR);
+ if (!r)
+ break;
+ if ((tries + 1) < 6)
+ gxfp_busy_wait_us(500000);
+ }
+ if (r && !rc)
+ rc = r;
+ gxfp_busy_wait_us(GXFP_RESET_DELAY_US);
+
+ dev_dbg(gdev->dev, "RECOVER_SESSION: unstick_tls=%u rc=%d\n", unstick_tls ? 1 : 0, rc);
+ return rc;
+}
+
+int gxfp_cmd_tls_unlock(struct gxfp_dev *gdev)
+{
+ __u8 payload[2] = { 0x00, 0x00 };
+ __u8 ack_payload[8];
+ size_t ack_len = 0;
+ int rc;
+
+ if (!gdev)
+ return -EINVAL;
+
+ rc = gxfp_cmd_xfer(gdev,
+ GXFP_CMD_TLS_UNLOCK,
+ payload, sizeof(payload),
+ GXFP_CMD_ACK,
+ ack_payload, sizeof(ack_payload), &ack_len,
+ 1,
+ GXFP_DEFAULT_TIMEOUT_US);
+
+ dev_dbg(gdev->dev, "TLS_UNLOCK: cmd=0xD4 rc=%d ack_len=%zu\n", rc, ack_len);
+ return rc;
+}
+
+int gxfp_cmd_protocol_init(struct gxfp_dev *gdev)
+{
+ __u8 payload[4] = { 0x00, 0x00, 0x00, 0x00 };
+ __u8 ack_payload[8];
+ size_t ack_len = 0;
+ int rc;
+
+ if (!gdev)
+ return -EINVAL;
+
+ rc = gxfp_cmd_xfer(gdev,
+ GXFP_CMD_PROTOCOL_INIT,
+ payload, sizeof(payload),
+ GXFP_CMD_ACK,
+ ack_payload, sizeof(ack_payload), &ack_len,
+ 1,
+ GXFP_DEFAULT_TIMEOUT_US);
+
+ dev_dbg(gdev->dev, "PROTO_INIT: cmd=0x01 rc=%d ack_len=%zu\n", rc, ack_len);
+ return rc;
+}
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h b/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h
new file mode 100644
index 000000000..29f7434c1
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_CMD_RESET_H
+#define __GXFP_CMD_RESET_H
+
+#include <linux/types.h>
+
+struct gxfp_dev;
+
+#define GXFP_RESET_FLAG_SENSOR 0x01u
+#define GXFP_RESET_FLAG_MCU 0x02u
+#define GXFP_RESET_FLAG_MCU_SENSOR (GXFP_RESET_FLAG_MCU | GXFP_RESET_FLAG_SENSOR)
+
+int gxfp_cmd_sleep_cleanup(struct gxfp_dev *gdev, unsigned int post_delay_ms);
+
+int gxfp_cmd_notify_power_state(struct gxfp_dev *gdev,
+ __u8 state);
+
+int gxfp_cmd_reset_device(struct gxfp_dev *gdev,
+ __u8 reset_flags);
+
+int gxfp_cmd_tls_unlock(struct gxfp_dev *gdev);
+
+int gxfp_cmd_protocol_init(struct gxfp_dev *gdev);
+
+int gxfp_cmd_recover_session(struct gxfp_dev *gdev, bool unstick_tls);
+
+#endif /* __GXFP_CMD_RESET_H */
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c b/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c
new file mode 100644
index 000000000..7ea28e965
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/unaligned.h>
+#include <linux/string.h>
+
+#include "gxfp_priv.h"
+#include "gxfp_cmd.h"
+#include "../hw/gxfp_delay.h"
+#include "gxfp_constants.h"
+#include "gxfp_cmd_version.h"
+
+static bool gxfp_extract_ascii_version(const __u8 *data, char *out, size_t out_len)
+{
+ size_t j;
+
+ if (!out || out_len == 0)
+ return false;
+ out[0] = '\0';
+
+ if (!data)
+ return false;
+
+ for (j = 0; (j + 1) < out_len; j++) {
+ __u8 ch = data[j];
+
+ if (ch == 0 || ch < 0x20 || ch > 0x7e)
+ break;
+ out[j] = (char)ch;
+ }
+ out[j] = '\0';
+
+ return j != 0;
+}
+
+static int gxfp_do_get_version(struct gxfp_dev *gdev,
+ struct gxfp_get_version_result *out)
+{
+ static const __u8 req_payload[] = { 0x01 };
+ __u8 payload[GXFP_MAX_VERSION_RAW];
+ size_t rx_len = 0;
+ int ret;
+
+ memset(out, 0, sizeof(*out));
+ out->status = (__u32)-EIO;
+ out->gpio_status = (__u32)-EIO;
+ out->tx_status = (__u32)-EIO;
+ out->rx_status = (__u32)-EIO;
+
+ if (!gdev->hw.mailbox_mmio || gdev->hw.mailbox_size < GXFP_MAILBOX_MIN_SIZE) {
+ out->status = (__u32)-ENODEV;
+ out->gpio_status = (__u32)-ENODEV;
+ out->tx_status = (__u32)-ENODEV;
+ out->rx_status = (__u32)-ENODEV;
+ return -ENODEV;
+ }
+
+ ret = gxfp_cmd_xfer(gdev,
+ GXFP_CMD_FIRMWARE_VERSION,
+ req_payload,
+ sizeof(req_payload),
+ GXFP_CMD_FIRMWARE_VERSION,
+ payload,
+ sizeof(payload),
+ &rx_len,
+ GXFP_DEFAULT_RETRY_COUNT,
+ GXFP_DEFAULT_TIMEOUT_US);
+ out->tx_status = 0;
+ out->gpio_status = 0;
+ out->rx_status = (__u32)(ret ? ret : 0);
+ if (ret) {
+ out->status = (__u32)ret;
+ return ret;
+ }
+
+ out->bytes_read = (__u32)min_t(size_t, rx_len, sizeof(out->raw));
+ memset(out->raw, 0, sizeof(out->raw));
+ memcpy(out->raw, payload, out->bytes_read);
+ dev_dbg(gdev->dev,
+ "GET_VERSION: payload_len=%zu head16=%*ph\n",
+ rx_len, 16, out->raw);
+
+ (void)gxfp_extract_ascii_version(payload,
+ out->version_ascii,
+ sizeof(out->version_ascii));
+ if (out->version_ascii[0] == '\0') {
+ out->status = (__u32)-ENODATA;
+ return 0;
+ }
+
+ out->status = 0;
+ return 0;
+}
+
+int gxfp_cmd_get_version(struct gxfp_dev *gdev, struct gxfp_get_version_result *out)
+{
+ return gxfp_do_get_version(gdev, out);
+}
diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h b/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h
new file mode 100644
index 000000000..8f619ab3a
--- /dev/null
+++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_CMD_VERSION_H
+#define __GXFP_CMD_VERSION_H
+
+#include <linux/types.h>
+#include "gxfp_constants.h"
+
+struct gxfp_dev;
+
+struct gxfp_get_version_result {
+ __u32 status;
+ __u32 gpio_status;
+ __u32 tx_status;
+ __u32 rx_status;
+ __u32 bytes_read;
+ char version_ascii[GXFP_MAX_VERSION_ASCII];
+ __u8 raw[GXFP_MAX_VERSION_RAW];
+};
+
+int gxfp_cmd_get_version(struct gxfp_dev *gdev, struct gxfp_get_version_result *out);
+
+#endif /* __GXFP_CMD_VERSION_H */
diff --git a/drivers/misc/gxfp5130/driver/gxfp_irq.c b/drivers/misc/gxfp5130/driver/gxfp_irq.c
new file mode 100644
index 000000000..09ad2aaaa
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_irq.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/minmax.h>
+
+#include "gxfp_priv.h"
+#include "../transport/gxfp_espi_rx_irq.h"
+
+#include "gxfp_irq.h"
+#include "gxfp_uapi.h"
+#include "gxfp_trace.h"
+
+#include "../hw/gxfp_gpio.h"
+
+irqreturn_t gxfp_irq_thread(int irq, void *data)
+{
+ struct gxfp_dev *gdev = data;
+ size_t rx_len = 0;
+ int ret;
+ bool complete = false;
+ bool push = false;
+
+ if (!gdev || !gdev->irq.rx_buf || gdev->irq.rx_cap < 16)
+ return IRQ_NONE;
+
+ mutex_lock(&gdev->lock);
+
+ ret = gxfp_espi_irq_read_step(gdev, gdev->irq.rx_buf, gdev->irq.rx_cap,
+ &rx_len, &complete);
+ if (ret) {
+ gxfp_trace_logf("irq_read_step rc=%d", ret);
+ goto out_clear;
+ }
+
+ if (!complete)
+ goto out_clear;
+
+ if (rx_len == 0)
+ goto out_clear;
+
+ push = true;
+
+out_clear:
+ gxfp_gpio_set_read_done(gdev, 0);
+ mutex_unlock(&gdev->lock);
+
+ if (push)
+ gxfp_trace_logf("irq_rx_complete len=%zu head=%*ph", rx_len,
+ (int)min_t(size_t, 8, rx_len), gdev->irq.rx_buf);
+
+ if (push)
+ gxfp_uapi_rxq_push_packet(gdev, gdev->irq.rx_buf, rx_len);
+
+ return IRQ_HANDLED;
+}
diff --git a/drivers/misc/gxfp5130/driver/gxfp_irq.h b/drivers/misc/gxfp5130/driver/gxfp_irq.h
new file mode 100644
index 000000000..9b7fb41a1
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_irq.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_IRQ_H
+#define __GXFP_IRQ_H
+
+#include <linux/interrupt.h>
+
+irqreturn_t gxfp_irq_thread(int irq, void *data);
+
+#endif /* __GXFP_IRQ_H */
diff --git a/drivers/misc/gxfp5130/driver/gxfp_platform.c b/drivers/misc/gxfp5130/driver/gxfp_platform.c
new file mode 100644
index 000000000..842c9d803
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_platform.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <linux/atomic.h>
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include "gxfp_priv.h"
+#include "../hw/gxfp_acpi.h"
+#include "../hw/gxfp_gpio.h"
+#include "gxfp_constants.h"
+#include "gxfp_uapi.h"
+#include "gxfp_irq.h"
+#include "gxfp_platform.h"
+#include "gxfp_trace.h"
+
+#define GXFP_IRQ_RX_CAP (128u * 1024u)
+
+
+static int gxfp_platform_add_acpi_driver_gpios(struct platform_device *pdev)
+{
+ struct gxfp_dev *gdev = platform_get_drvdata(pdev);
+ struct acpi_gpio_params *params;
+ struct acpi_gpio_mapping *map;
+ int irq_idx;
+ int done_idx[2];
+ bool done_active_low;
+ int rc;
+
+ if (!pdev || !gdev)
+ return -EINVAL;
+
+ rc = gxfp_acpi_get_gpio_crs_indexes(&pdev->dev, &irq_idx, done_idx, &done_active_low);
+ if (rc) {
+ dev_warn(&pdev->dev, "ACPI _CRS: cannot build driver GPIO mapping (rc=%d)\n", rc);
+ return rc;
+ }
+
+ params = devm_kcalloc(&pdev->dev, 3, sizeof(*params), GFP_KERNEL);
+ map = devm_kcalloc(&pdev->dev, 4, sizeof(*map), GFP_KERNEL);
+ if (!params || !map)
+ return -ENOMEM;
+
+ params[0].crs_entry_index = (unsigned int)irq_idx;
+ params[0].line_index = 0;
+ params[0].active_low = false;
+ map[0].name = "irq-gpios";
+ map[0].data = ¶ms[0];
+ map[0].size = 1;
+
+ params[1].crs_entry_index = (unsigned int)done_idx[0];
+ params[1].line_index = 0;
+ params[1].active_low = done_active_low;
+ map[1].name = "write-done-gpios";
+ map[1].data = ¶ms[1];
+ map[1].size = 1;
+ map[1].quirks = 0;
+
+ params[2].crs_entry_index = (unsigned int)done_idx[1];
+ params[2].line_index = 0;
+ params[2].active_low = done_active_low;
+ map[2].name = "read-done-gpios";
+ map[2].data = ¶ms[2];
+ map[2].size = 1;
+ map[2].quirks = 0;
+
+ /* map[3] is terminator */
+
+ rc = devm_acpi_dev_add_driver_gpios(&pdev->dev, map);
+ if (rc) {
+ dev_warn(&pdev->dev, "ACPI: dev_add_driver_gpios failed (rc=%d)\n", rc);
+ return rc;
+ }
+
+ dev_dbg(&pdev->dev,
+ "ACPI: installed driver GPIO mapping (irq_idx=%d done_idx=%d,%d done_active_low=%u)\n",
+ irq_idx, done_idx[0], done_idx[1], done_active_low ? 1 : 0);
+ return 0;
+}
+
+int gxfp_platform_probe(struct platform_device *pdev)
+{
+ struct gxfp_dev *gdev;
+ struct resource *res;
+ unsigned long irq_flags;
+ unsigned int irq_type;
+ int ret;
+
+ gdev = devm_kzalloc(&pdev->dev, sizeof(*gdev), GFP_KERNEL);
+ if (!gdev)
+ return -ENOMEM;
+ gdev->dev = &pdev->dev;
+ mutex_init(&gdev->lock);
+ atomic_set(&gdev->mailbox_seq16, GXFP_ESPI_SEQ16_INIT);
+ gdev->irq.assem_active = false;
+ gdev->irq.assem_total = 0;
+ gdev->irq.assem_copied = 0;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "missing MMIO resource (mailbox window)\n");
+ return -ENODEV;
+ }
+
+ gdev->hw.mailbox_size = resource_size(res);
+ gdev->hw.mailbox_mmio = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(gdev->hw.mailbox_mmio))
+ return PTR_ERR(gdev->hw.mailbox_mmio);
+
+ platform_set_drvdata(pdev, gdev);
+ (void)gxfp_trace_init(&pdev->dev);
+
+ /* GPIOs */
+ {
+ int gpio_rc = gxfp_platform_init_gpios(pdev);
+
+ if (gpio_rc) {
+ if (gpio_rc == -EPROBE_DEFER)
+ dev_dbg(&pdev->dev, "GPIO not ready, deferring probe\n");
+ return gpio_rc;
+ }
+ }
+
+ /* misc device */
+ ret = gxfp_uapi_register(gdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register misc device (/dev/gxfp) rc=%d\n", ret);
+ return ret;
+ }
+
+ /* IRQ */
+ gdev->irq.rx_cap = GXFP_IRQ_RX_CAP;
+ gdev->irq.rx_buf = devm_kmalloc(&pdev->dev, gdev->irq.rx_cap, GFP_KERNEL);
+ if (!gdev->irq.rx_buf) {
+ dev_err(&pdev->dev, "IRQ: kmalloc(%zu) failed\n", gdev->irq.rx_cap);
+ ret = -ENOMEM;
+ goto err_ioctl;
+ }
+
+ gdev->irq.rx_tmp = devm_kmalloc(&pdev->dev, (size_t)GXFP_READ_CHUNK_MAX, GFP_KERNEL);
+ if (!gdev->irq.rx_tmp) {
+ ret = -ENOMEM;
+ goto err_ioctl;
+ }
+
+ gdev->hw.irq = gpiod_to_irq(gdev->hw.gpio_irq);
+ if (gdev->hw.irq < 0) {
+ dev_err(&pdev->dev, "IRQ: gpiod_to_irq failed rc=%d\n", gdev->hw.irq);
+ ret = gdev->hw.irq;
+ goto err_ioctl;
+ }
+
+ irq_type = irq_get_trigger_type(gdev->hw.irq);
+ if (irq_type == IRQ_TYPE_NONE) {
+ /*
+ * ACPI DSDT for SPBA reports:
+ * GpioInt(Level, ActiveHigh, ExclusiveAndWake, ...)
+ */
+ dev_info(&pdev->dev, "IRQ: trigger type unknown; defaulting to LEVEL_HIGH\n");
+ (void)irq_set_irq_type(gdev->hw.irq, IRQ_TYPE_LEVEL_HIGH);
+ irq_type = irq_get_trigger_type(gdev->hw.irq);
+ }
+
+ irq_flags = IRQF_ONESHOT | IRQF_NO_AUTOEN;
+ ret = devm_request_threaded_irq(&pdev->dev,
+ gdev->hw.irq,
+ NULL,
+ gxfp_irq_thread,
+ irq_flags,
+ "gxfp-irq",
+ gdev);
+ if (ret) {
+ dev_err(&pdev->dev, "IRQ: request_threaded_irq failed (irq=%d, rc=%d)\n",
+ gdev->hw.irq, ret);
+ gdev->hw.irq = 0;
+ goto err_ioctl;
+ }
+ dev_dbg(&pdev->dev, "IRQ: registered (irq=%d, flags=0x%lx, type=0x%x)\n",
+ gdev->hw.irq, irq_flags, irq_type);
+
+ dev_dbg(&pdev->dev, "gxfp probed (mailbox=%pa len=%pa)\n",
+ &res->start, &gdev->hw.mailbox_size);
+ return 0;
+
+err_ioctl:
+ gxfp_uapi_unregister(gdev);
+ return ret;
+}
+
+int gxfp_platform_init_gpios(struct platform_device *pdev)
+{
+ struct gxfp_dev *gdev = platform_get_drvdata(pdev);
+ struct gpio_desc *d;
+ int added_map = 0;
+
+ d = devm_gpiod_get(&pdev->dev, "write-done", GPIOD_OUT_LOW);
+ if (IS_ERR(d) && PTR_ERR(d) == -ENOENT) {
+ (void)gxfp_platform_add_acpi_driver_gpios(pdev);
+ added_map = 1;
+ d = devm_gpiod_get(&pdev->dev, "write-done", GPIOD_OUT_LOW);
+ }
+ gdev->hw.gpio_write_done = d;
+ if (IS_ERR(gdev->hw.gpio_write_done))
+ return PTR_ERR(gdev->hw.gpio_write_done);
+
+ d = devm_gpiod_get(&pdev->dev, "read-done", GPIOD_OUT_LOW);
+ if (!added_map && IS_ERR(d) && PTR_ERR(d) == -ENOENT) {
+ (void)gxfp_platform_add_acpi_driver_gpios(pdev);
+ added_map = 1;
+ d = devm_gpiod_get(&pdev->dev, "read-done", GPIOD_OUT_LOW);
+ }
+ gdev->hw.gpio_read_done = d;
+ if (IS_ERR(gdev->hw.gpio_read_done))
+ return PTR_ERR(gdev->hw.gpio_read_done);
+
+ d = devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN);
+ if (!added_map && IS_ERR(d) && PTR_ERR(d) == -ENOENT) {
+ (void)gxfp_platform_add_acpi_driver_gpios(pdev);
+ added_map = 1;
+ d = devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN);
+ }
+ gdev->hw.gpio_irq = d;
+ if (IS_ERR(gdev->hw.gpio_irq))
+ return PTR_ERR(gdev->hw.gpio_irq);
+
+ if (!gdev->hw.gpio_write_done || !gdev->hw.gpio_read_done || !gdev->hw.gpio_irq) {
+ dev_err(&pdev->dev, "missing GPIOs write-done/read-done/irq (write_done=%p, read_done=%p, irq=%p)\n",
+ gdev->hw.gpio_write_done, gdev->hw.gpio_read_done, gdev->hw.gpio_irq);
+ return -EINVAL;
+ }
+
+ gxfp_gpio_force_done_deassert(gdev->dev, gdev);
+
+ return 0;
+}
+
+void gxfp_platform_remove(struct platform_device *pdev)
+{
+ struct gxfp_dev *gdev = platform_get_drvdata(pdev);
+
+ if (gdev && gdev->hw.irq > 0) {
+ disable_irq(gdev->hw.irq);
+ synchronize_irq(gdev->hw.irq);
+ }
+ gxfp_trace_exit();
+ gxfp_uapi_unregister(gdev);
+}
diff --git a/drivers/misc/gxfp5130/driver/gxfp_platform.h b/drivers/misc/gxfp5130/driver/gxfp_platform.h
new file mode 100644
index 000000000..fa69f18e5
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_platform.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_PLATFORM_H
+#define __GXFP_PLATFORM_H
+
+#include <linux/platform_device.h>
+
+int gxfp_platform_probe(struct platform_device *pdev);
+void gxfp_platform_remove(struct platform_device *pdev);
+
+int gxfp_platform_init_gpios(struct platform_device *pdev);
+
+#endif /* __GXFP_PLATFORM_H */
diff --git a/drivers/misc/gxfp5130/driver/gxfp_trace.c b/drivers/misc/gxfp5130/driver/gxfp_trace.c
new file mode 100644
index 000000000..e51dfe422
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_trace.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/stdarg.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include "gxfp_trace.h"
+
+#define GXFP_TRACE_RING_SIZE 1024u
+#define GXFP_TRACE_LINE_MAX 192u
+#define GXFP_TRACE_DEFAULT_ENABLE 0
+
+static struct {
+ spinlock_t lock;
+ bool inited;
+ bool enabled;
+ u32 seq;
+ u32 head;
+ u32 count;
+ char lines[GXFP_TRACE_RING_SIZE][GXFP_TRACE_LINE_MAX];
+ struct dentry *dir;
+} gxfp_trace;
+
+static void gxfp_trace_push_locked(const char *line)
+{
+ u32 idx;
+
+ if (!line)
+ return;
+
+ idx = gxfp_trace.head;
+ strscpy(gxfp_trace.lines[idx], line, GXFP_TRACE_LINE_MAX);
+ gxfp_trace.head = (idx + 1u) % GXFP_TRACE_RING_SIZE;
+ if (gxfp_trace.count < GXFP_TRACE_RING_SIZE)
+ gxfp_trace.count++;
+}
+
+void gxfp_trace_logf(const char *fmt, ...)
+{
+ unsigned long flags;
+ char msg[128];
+ char line[GXFP_TRACE_LINE_MAX];
+ va_list ap;
+ u64 ts;
+ u32 seq;
+ int n;
+
+ if (!fmt || !READ_ONCE(gxfp_trace.inited) || !READ_ONCE(gxfp_trace.enabled))
+ return;
+
+ va_start(ap, fmt);
+ vscnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+
+ ts = ktime_get_ns();
+ spin_lock_irqsave(&gxfp_trace.lock, flags);
+ seq = ++gxfp_trace.seq;
+ n = scnprintf(line, sizeof(line), "%u %llu %s", seq, ts, msg);
+ if (n >= 0)
+ gxfp_trace_push_locked(line);
+ spin_unlock_irqrestore(&gxfp_trace.lock, flags);
+}
+
+void gxfp_trace_clear(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gxfp_trace.lock, flags);
+ gxfp_trace.head = 0;
+ gxfp_trace.count = 0;
+ spin_unlock_irqrestore(&gxfp_trace.lock, flags);
+}
+
+static int gxfp_trace_dump_show(struct seq_file *s, void *unused)
+{
+ char (*snapshot)[GXFP_TRACE_LINE_MAX];
+ unsigned long flags;
+ u32 count;
+ u32 start;
+ u32 i;
+
+ snapshot = kcalloc(GXFP_TRACE_RING_SIZE, sizeof(*snapshot), GFP_KERNEL);
+ if (!snapshot)
+ return -ENOMEM;
+
+ spin_lock_irqsave(&gxfp_trace.lock, flags);
+ count = gxfp_trace.count;
+ start = (gxfp_trace.head + GXFP_TRACE_RING_SIZE - count) % GXFP_TRACE_RING_SIZE;
+ for (i = 0; i < count; i++) {
+ u32 idx = (start + i) % GXFP_TRACE_RING_SIZE;
+
+ strscpy(snapshot[i], gxfp_trace.lines[idx], GXFP_TRACE_LINE_MAX);
+ }
+ spin_unlock_irqrestore(&gxfp_trace.lock, flags);
+
+ seq_printf(s, "enabled=%u ring_size=%u count=%u\n",
+ READ_ONCE(gxfp_trace.enabled) ? 1 : 0,
+ GXFP_TRACE_RING_SIZE,
+ count);
+ seq_puts(s, "# seq ts_ns message\n");
+ for (i = 0; i < count; i++)
+ seq_printf(s, "%s\n", snapshot[i]);
+
+ kfree(snapshot);
+ return 0;
+}
+
+static int gxfp_trace_dump_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gxfp_trace_dump_show, inode->i_private);
+}
+
+static const struct file_operations gxfp_trace_dump_fops = {
+ .owner = THIS_MODULE,
+ .open = gxfp_trace_dump_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static ssize_t gxfp_trace_clear_write(struct file *file,
+ const char __user *ubuf,
+ size_t count,
+ loff_t *ppos)
+{
+ (void)file;
+ (void)ubuf;
+ (void)ppos;
+ gxfp_trace_clear();
+ return count;
+}
+
+static const struct file_operations gxfp_trace_clear_fops = {
+ .owner = THIS_MODULE,
+ .write = gxfp_trace_clear_write,
+ .llseek = noop_llseek,
+};
+
+int gxfp_trace_init(struct device *dev)
+{
+ (void)dev;
+
+ if (READ_ONCE(gxfp_trace.inited))
+ return 0;
+
+ memset(&gxfp_trace, 0, sizeof(gxfp_trace));
+ spin_lock_init(&gxfp_trace.lock);
+ gxfp_trace.enabled = GXFP_TRACE_DEFAULT_ENABLE;
+
+ gxfp_trace.dir = debugfs_create_dir("gxfp", NULL);
+ if (!IS_ERR_OR_NULL(gxfp_trace.dir)) {
+ debugfs_create_file("trace_dump", 0400, gxfp_trace.dir,
+ NULL, &gxfp_trace_dump_fops);
+ debugfs_create_file("trace_clear", 0200, gxfp_trace.dir,
+ NULL, &gxfp_trace_clear_fops);
+ debugfs_create_bool("trace_enable", 0600, gxfp_trace.dir, &gxfp_trace.enabled);
+ }
+
+ gxfp_trace.inited = true;
+ return 0;
+}
+
+void gxfp_trace_exit(void)
+{
+ if (!READ_ONCE(gxfp_trace.inited))
+ return;
+
+ debugfs_remove_recursive(gxfp_trace.dir);
+ gxfp_trace.dir = NULL;
+ gxfp_trace.inited = false;
+}
diff --git a/drivers/misc/gxfp5130/driver/gxfp_trace.h b/drivers/misc/gxfp5130/driver/gxfp_trace.h
new file mode 100644
index 000000000..2041ba0b0
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_TRACE_H
+#define __GXFP_TRACE_H
+
+#include <linux/types.h>
+
+struct device;
+
+int gxfp_trace_init(struct device *dev);
+void gxfp_trace_exit(void);
+void gxfp_trace_clear(void);
+
+void gxfp_trace_logf(const char *fmt, ...) __printf(1, 2);
+
+#endif /* __GXFP_TRACE_H */
diff --git a/drivers/misc/gxfp5130/driver/gxfp_uapi.h b/drivers/misc/gxfp5130/driver/gxfp_uapi.h
new file mode 100644
index 000000000..ecc60ee06
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_uapi.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_UAPI_DRV_H
+#define __GXFP_UAPI_DRV_H
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+
+struct gxfp_dev;
+
+void gxfp_uapi_rxq_push_packet(struct gxfp_dev *gdev, const __u8 *buf, size_t len);
+
+void gxfp_uapi_rxq_flush_locked(struct gxfp_dev *gdev);
+ssize_t gxfp_uapi_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos);
+__poll_t gxfp_uapi_poll(struct file *file, poll_table *wait);
+
+int gxfp_uapi_open(struct inode *inode, struct file *file);
+int gxfp_uapi_release(struct inode *inode, struct file *file);
+ssize_t gxfp_uapi_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos);
+long gxfp_uapi_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+
+int gxfp_uapi_register(struct gxfp_dev *gdev);
+void gxfp_uapi_unregister(struct gxfp_dev *gdev);
+
+#endif /* __GXFP_UAPI_DRV_H */
diff --git a/drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c b/drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c
new file mode 100644
index 000000000..d6b4a888f
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+
+#include "gxfp_priv.h"
+#include "../transport/gxfp_espi_tx.h"
+#include "../include/uapi/linux/gxfp_ioctl.h"
+#include "gxfp_uapi.h"
+#include "gxfp_trace.h"
+
+int gxfp_uapi_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *m = file->private_data;
+ struct gxfp_dev *gdev = container_of(m, struct gxfp_dev, uapi.miscdev);
+ unsigned long flags;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (file->f_mode & FMODE_READ) {
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ if (!gdev->uapi.rxq_inited) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -ENODEV;
+ }
+ if (gdev->uapi.rxq_reader_open) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -EBUSY;
+ }
+ gdev->uapi.rxq_reader_open = true;
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ }
+
+ file->private_data = gdev;
+ gxfp_trace_logf("uapi_open ok mode=0x%x", file->f_mode);
+ return 0;
+}
+
+int gxfp_uapi_release(struct inode *inode, struct file *file)
+{
+ struct gxfp_dev *gdev = file->private_data;
+ unsigned long flags;
+
+ if (!gdev)
+ return 0;
+
+ if (file->f_mode & FMODE_READ) {
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ gdev->uapi.rxq_reader_open = false;
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ }
+
+ return 0;
+}
+
+ssize_t gxfp_uapi_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
+{
+ struct gxfp_dev *gdev = file->private_data;
+ struct gxfp_tx_pkt_hdr txh;
+ __u8 *mp_local = NULL;
+ size_t payload_len;
+ size_t need;
+ int ret;
+
+ if (!gdev || !ubuf)
+ return -ENODEV;
+ if (count < sizeof(txh))
+ return -EINVAL;
+
+ if (copy_from_user(&txh, ubuf, sizeof(txh)))
+ return -EFAULT;
+
+ payload_len = (size_t)txh.payload_len;
+ if (payload_len > GXFP_IOCTL_TX_PAYLOAD_MAX)
+ return -EINVAL;
+
+ need = sizeof(txh) + payload_len;
+ if (count != need)
+ return -EINVAL;
+
+ mp_local = kmalloc(4u + payload_len, GFP_KERNEL);
+ if (!mp_local)
+ return -ENOMEM;
+
+ mp_local[0] = txh.mp_flags;
+ mp_local[1] = (__u8)(payload_len & 0xff);
+ mp_local[2] = (__u8)((payload_len >> 8) & 0xff);
+ mp_local[3] = (__u8)(mp_local[0] + mp_local[1] + mp_local[2]);
+ if (payload_len && copy_from_user(mp_local + 4, ubuf + sizeof(txh), payload_len)) {
+ kfree(mp_local);
+ return -EFAULT;
+ }
+
+ mutex_lock(&gdev->lock);
+ ret = gxfp_espi_write(gdev, mp_local, 4u + payload_len);
+ mutex_unlock(&gdev->lock);
+ gxfp_trace_logf("uapi_write rc=%d mp_len=%zu head=%*ph", ret, 4u + payload_len,
+ (int)min_t(size_t, 8, 4u + payload_len), mp_local);
+
+ kfree(mp_local);
+ if (ret)
+ return ret;
+
+ return (ssize_t)count;
+}
+
+long gxfp_uapi_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct gxfp_dev *gdev = file->private_data;
+ int ret;
+
+ if (!gdev)
+ return -ENODEV;
+
+ switch (cmd) {
+ case GXFP_IOCTL_FLUSH_RXQ: {
+ unsigned long flush_flags;
+
+ ret = mutex_lock_interruptible(&gdev->uapi.rxq_mutex);
+ if (ret)
+ return ret;
+
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flush_flags);
+ gxfp_uapi_rxq_flush_locked(gdev);
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flush_flags);
+ mutex_unlock(&gdev->uapi.rxq_mutex);
+ gxfp_trace_logf("uapi_ioctl flush_rxq ok");
+ return 0;
+ }
+ default:
+ return -ENOTTY;
+ }
+}
diff --git a/drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c b/drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c
new file mode 100644
index 000000000..7be5f1265
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/kfifo.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/ktime.h>
+#include <linux/minmax.h>
+
+#include "gxfp_priv.h"
+#include "gxfp_uapi.h"
+#include "../include/uapi/linux/gxfp_ioctl.h"
+#include "../proto/gxfp_mp_proto.h"
+
+void gxfp_uapi_rxq_flush_locked(struct gxfp_dev *gdev)
+{
+ if (!gdev || !gdev->uapi.rxq_inited)
+ return;
+ kfifo_reset(&gdev->uapi.rxq_fifo);
+}
+
+static void gxfp_uapi_rxq_push(struct gxfp_dev *gdev, const __u8 *buf, size_t len)
+{
+ unsigned long flags;
+ struct gxfp_tap_hdr hdr;
+ struct gxfp_mp_frame_parsed mp;
+ size_t need;
+
+ if (!gdev || !gdev->uapi.rxq_inited || !buf || len == 0)
+ return;
+ if (!gxfp_parse_mp_frame(buf, len, &mp) || !mp.valid)
+ return;
+ if (mp.payload_len > (size_t)U32_MAX)
+ return;
+
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.len = (__u32)mp.payload_len;
+ hdr.type = (__u32)(mp.flags >> 4);
+ hdr.ts_ns = ktime_get_ns();
+ memcpy(hdr.head16, mp.payload, min_t(size_t, mp.payload_len, sizeof(hdr.head16)));
+
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ if (!gdev->uapi.rxq_inited) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return;
+ }
+ need = sizeof(hdr) + mp.payload_len;
+ if (kfifo_avail(&gdev->uapi.rxq_fifo) < need) {
+ unsigned int avail = kfifo_avail(&gdev->uapi.rxq_fifo);
+
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ dev_warn_ratelimited(gdev->dev,
+ "rxq: drop need=%zu avail=%u\n",
+ need, avail);
+ return;
+ }
+ (void)kfifo_in(&gdev->uapi.rxq_fifo, (const void *)&hdr, sizeof(hdr));
+ (void)kfifo_in(&gdev->uapi.rxq_fifo, mp.payload, mp.payload_len);
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+
+ wake_up_interruptible(&gdev->uapi.rxq_wq);
+}
+
+void gxfp_uapi_rxq_push_packet(struct gxfp_dev *gdev, const __u8 *buf, size_t len)
+{
+ gxfp_uapi_rxq_push(gdev, buf, len);
+}
+
+static bool gxfp_uapi_rxq_ready(struct gxfp_dev *gdev)
+{
+ unsigned long flags;
+ bool ready;
+
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ ready = gdev->uapi.rxq_inited && !kfifo_is_empty(&gdev->uapi.rxq_fifo);
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+
+ return ready;
+}
+
+static int gxfp_uapi_wait_nonempty(struct gxfp_dev *gdev, struct file *file)
+{
+ unsigned long flags;
+ int ret;
+
+ for (;;) {
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ if (!gdev->uapi.rxq_inited) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -ENODEV;
+ }
+ if (!kfifo_is_empty(&gdev->uapi.rxq_fifo)) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return 0;
+ }
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ ret = wait_event_interruptible(gdev->uapi.rxq_wq,
+ gxfp_uapi_rxq_ready(gdev) || !READ_ONCE(gdev->uapi.rxq_inited));
+ if (ret)
+ return ret;
+ }
+}
+
+static int gxfp_uapi_peek_need(struct gxfp_dev *gdev, size_t count, size_t *out_need)
+{
+ struct gxfp_tap_hdr hdr;
+ unsigned long flags;
+ size_t need;
+
+ if (!out_need)
+ return -EINVAL;
+
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ if (!gdev->uapi.rxq_inited) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -ENODEV;
+ }
+ if (kfifo_len(&gdev->uapi.rxq_fifo) < sizeof(hdr)) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -EIO;
+ }
+ if (kfifo_out_peek(&gdev->uapi.rxq_fifo, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -EIO;
+ }
+ need = sizeof(hdr) + (size_t)hdr.len;
+ if ((size_t)kfifo_len(&gdev->uapi.rxq_fifo) < need) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -EIO;
+ }
+ if (count < need) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -EMSGSIZE;
+ }
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+
+ *out_need = need;
+ return 0;
+}
+
+static int gxfp_uapi_pop_record(struct gxfp_dev *gdev, __u8 *tmp,
+ size_t need, unsigned int *out_popped)
+{
+ unsigned long flags;
+ unsigned int popped;
+
+ if (!out_popped)
+ return -EINVAL;
+
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ if (!gdev->uapi.rxq_inited) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return -ENODEV;
+ }
+ popped = kfifo_out(&gdev->uapi.rxq_fifo, tmp, need);
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+
+ *out_popped = popped;
+ return 0;
+}
+
+ssize_t gxfp_uapi_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
+{
+ struct gxfp_dev *gdev = file->private_data;
+ __u8 *tmp;
+ size_t need;
+ int ret;
+ unsigned int popped = 0;
+
+ if (!gdev || !gdev->uapi.rxq_inited)
+ return -ENODEV;
+ if (count == 0)
+ return 0;
+
+ ret = mutex_lock_interruptible(&gdev->uapi.rxq_mutex);
+ if (ret)
+ return ret;
+
+ ret = gxfp_uapi_wait_nonempty(gdev, file);
+ if (ret)
+ goto out_unlock;
+
+ ret = gxfp_uapi_peek_need(gdev, count, &need);
+ if (ret)
+ goto out_unlock;
+
+ tmp = kmalloc(need, GFP_KERNEL);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+
+ ret = gxfp_uapi_pop_record(gdev, tmp, need, &popped);
+ if (ret) {
+ kfree(tmp);
+ goto out_unlock;
+ }
+
+ if (popped != need) {
+ kfree(tmp);
+ ret = -EIO;
+ goto out_unlock;
+ }
+
+ if (copy_to_user(ubuf, tmp, popped)) {
+ kfree(tmp);
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ kfree(tmp);
+ ret = (int)popped;
+
+out_unlock:
+ mutex_unlock(&gdev->uapi.rxq_mutex);
+ return ret;
+}
+
+__poll_t gxfp_uapi_poll(struct file *file, poll_table *wait)
+{
+ struct gxfp_dev *gdev = file->private_data;
+ __poll_t mask = 0;
+ unsigned long flags;
+
+ if (!gdev || !gdev->uapi.rxq_inited)
+ return EPOLLERR;
+
+ poll_wait(file, &gdev->uapi.rxq_wq, wait);
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ if (!gdev->uapi.rxq_inited) {
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return EPOLLERR;
+ }
+ if (!kfifo_is_empty(&gdev->uapi.rxq_fifo))
+ mask |= EPOLLIN | EPOLLRDNORM;
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ return mask;
+}
diff --git a/drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c b/drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c
new file mode 100644
index 000000000..c797ed432
--- /dev/null
+++ b/drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/kfifo.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/sizes.h>
+
+#include "gxfp_priv.h"
+#include "../hw/gxfp_acpi.h"
+#include "gxfp_uapi.h"
+
+#define GXFP_RXQ_FIFO_BYTES SZ_1M
+
+static ssize_t acpi_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct miscdevice *m = dev_get_drvdata(dev);
+ struct gxfp_dev *gdev;
+ char hid[16];
+ int rc;
+
+ if (!m)
+ return -ENODEV;
+ gdev = container_of(m, struct gxfp_dev, uapi.miscdev);
+ if (!gdev || !gdev->dev)
+ return -ENODEV;
+ rc = gxfp_acpi_get_hid(gdev->dev, hid, sizeof(hid));
+ if (rc)
+ return sysfs_emit(buf, "\n");
+ return sysfs_emit(buf, "%s\n", hid);
+}
+static DEVICE_ATTR_RO(acpi_id);
+
+static struct attribute *gxfp_misc_attrs[] = {
+ &dev_attr_acpi_id.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(gxfp_misc);
+
+static const struct file_operations gxfp_uapi_fops = {
+ .owner = THIS_MODULE,
+ .open = gxfp_uapi_open,
+ .release = gxfp_uapi_release,
+ .read = gxfp_uapi_read,
+ .write = gxfp_uapi_write,
+ .poll = gxfp_uapi_poll,
+ .unlocked_ioctl = gxfp_uapi_unlocked_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .llseek = noop_llseek,
+};
+
+int gxfp_uapi_register(struct gxfp_dev *gdev)
+{
+ int ret;
+ int rc_fifo;
+ unsigned int fifo_bytes;
+
+ if (!gdev)
+ return -EINVAL;
+ if (gdev->uapi.misc_registered)
+ return 0;
+
+ gdev->uapi.miscdev.minor = MISC_DYNAMIC_MINOR;
+ gdev->uapi.miscdev.name = "gxfp";
+ gdev->uapi.miscdev.fops = &gxfp_uapi_fops;
+ gdev->uapi.miscdev.parent = gdev->dev;
+ gdev->uapi.miscdev.groups = gxfp_misc_groups;
+
+ spin_lock_init(&gdev->uapi.rxq_lock);
+ mutex_init(&gdev->uapi.rxq_mutex);
+ init_waitqueue_head(&gdev->uapi.rxq_wq);
+ gdev->uapi.rxq_inited = false;
+ gdev->uapi.rxq_reader_open = false;
+ fifo_bytes = GXFP_RXQ_FIFO_BYTES;
+ rc_fifo = kfifo_alloc(&gdev->uapi.rxq_fifo, fifo_bytes, GFP_KERNEL);
+ if (rc_fifo)
+ return rc_fifo;
+ gdev->uapi.rxq_inited = true;
+
+ ret = misc_register(&gdev->uapi.miscdev);
+ if (ret)
+ goto err_fifo;
+
+ gdev->uapi.misc_registered = true;
+
+ dev_dbg(gdev->dev, "misc device registered: /dev/%s\n", gdev->uapi.miscdev.name);
+ return 0;
+
+err_fifo:
+ if (gdev->uapi.rxq_inited) {
+ kfifo_free(&gdev->uapi.rxq_fifo);
+ gdev->uapi.rxq_inited = false;
+ }
+ return ret;
+}
+
+void gxfp_uapi_unregister(struct gxfp_dev *gdev)
+{
+ unsigned long flags;
+
+ if (!gdev)
+ return;
+ if (!gdev->uapi.misc_registered && !gdev->uapi.rxq_inited)
+ return;
+
+ if (gdev->uapi.misc_registered) {
+ misc_deregister(&gdev->uapi.miscdev);
+ gdev->uapi.misc_registered = false;
+ }
+
+ if (gdev->uapi.rxq_inited) {
+ mutex_lock(&gdev->uapi.rxq_mutex);
+ spin_lock_irqsave(&gdev->uapi.rxq_lock, flags);
+ gdev->uapi.rxq_inited = false;
+ gdev->uapi.rxq_reader_open = false;
+ kfifo_reset(&gdev->uapi.rxq_fifo);
+ spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags);
+ wake_up_interruptible_all(&gdev->uapi.rxq_wq);
+
+ kfifo_free(&gdev->uapi.rxq_fifo);
+ mutex_unlock(&gdev->uapi.rxq_mutex);
+ }
+}
diff --git a/drivers/misc/gxfp5130/gxfp_main.c b/drivers/misc/gxfp5130/gxfp_main.c
new file mode 100644
index 000000000..863e36a88
--- /dev/null
+++ b/drivers/misc/gxfp5130/gxfp_main.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/string.h>
+
+#include "include/gxfp_priv.h"
+#include "cmd/gxfp_cmd.h"
+#include "cmd/gxfp_cmd_reset.h"
+#include "cmd/gxfp_cmd_version.h"
+#include "cmd/gxfp_cmd_mcu_state.h"
+#include "driver/gxfp_platform.h"
+
+#define GXFP_DRV_NAME "gxfp"
+
+static int gxfp_startup_sequence(struct platform_device *pdev)
+{
+ struct gxfp_dev *gdev = platform_get_drvdata(pdev);
+ struct gxfp_get_version_result ver;
+ struct gxfp_mcu_state mcu;
+ int rc;
+ int mcu_rc;
+ unsigned int try;
+
+ if (!gdev)
+ return -ENODEV;
+
+ msleep(150);
+ memset(&ver, 0, sizeof(ver));
+ memset(&mcu, 0, sizeof(mcu));
+
+ mutex_lock(&gdev->lock);
+ rc = 0;
+ for (try = 0; try < 3; try++) {
+ rc = gxfp_cmd_get_version(gdev, &ver);
+ if (!rc)
+ break;
+ if ((try + 1) < 3)
+ msleep(20);
+ }
+ mcu_rc = gxfp_cmd_query_mcu_state(gdev, &mcu);
+ if (ver.status != 0) {
+ dev_warn(&pdev->dev,
+ "INIT: get_version failed (%d); attempting recover_session then retry\n",
+ (int)ver.status);
+ (void)gxfp_cmd_recover_session(gdev, 1);
+ (void)gxfp_cmd_get_version(gdev, &ver);
+ }
+ mutex_unlock(&gdev->lock);
+
+ if (ver.status == 0 && ver.version_ascii[0] != '\0')
+ dev_info(&pdev->dev, "INIT: FW='%s'\n", ver.version_ascii);
+ if (ver.status != 0)
+ return (int)ver.status;
+
+ return 0;
+}
+
+static void gxfp_shutdown_sequence(struct platform_device *pdev)
+{
+ struct gxfp_dev *gdev;
+ int rc;
+
+ if (!pdev)
+ return;
+ gdev = platform_get_drvdata(pdev);
+ if (!gdev)
+ return;
+
+ mutex_lock(&gdev->lock);
+ (void)gxfp_cmd_notify_power_state(gdev, 1);
+ rc = gxfp_cmd_sleep_cleanup(gdev, 0);
+ mutex_unlock(&gdev->lock);
+ dev_dbg(&pdev->dev, "REMOVE: sent SLEEP/CLEANUP(0xD2) rc=%d\n", rc);
+}
+
+static int gxfp_probe(struct platform_device *pdev)
+{
+ struct gxfp_dev *gdev;
+ int ret;
+
+ ret = gxfp_platform_probe(pdev);
+ if (ret)
+ return ret;
+
+ ret = gxfp_startup_sequence(pdev);
+ if (ret) {
+ gxfp_platform_remove(pdev);
+ return ret;
+ }
+
+ gdev = platform_get_drvdata(pdev);
+ if (gdev && gdev->hw.irq > 0)
+ enable_irq(gdev->hw.irq);
+
+ return 0;
+}
+
+static void gxfp_remove(struct platform_device *pdev)
+{
+ gxfp_shutdown_sequence(pdev);
+ gxfp_platform_remove(pdev);
+}
+
+static const struct acpi_device_id gxfp_acpi_match[] = {
+ { "GXFP5130", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, gxfp_acpi_match);
+
+static struct platform_driver gxfp_driver = {
+ .driver = {
+ .name = GXFP_DRV_NAME,
+ .acpi_match_table = gxfp_acpi_match,
+ },
+ .probe = gxfp_probe,
+ .remove = gxfp_remove,
+};
+
+module_platform_driver(gxfp_driver);
+
+MODULE_DESCRIPTION("eSPI-Based GXFP5130 Fingerprint Sensor Driver");
+MODULE_AUTHOR("vindeu");
+MODULE_AUTHOR("Metehan Günen <metehangnen@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/gxfp5130/hw/gxfp_acpi.c b/drivers/misc/gxfp5130/hw/gxfp_acpi.c
new file mode 100644
index 000000000..17e42fcd3
--- /dev/null
+++ b/drivers/misc/gxfp5130/hw/gxfp_acpi.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+#include "gxfp_acpi.h"
+
+struct gxfp_acpi_res_state {
+ struct device *dev;
+ int gpio_seen;
+ int gpio_io_seen;
+ u16 gpio_io_pin[2];
+ u8 gpio_io_pol[2];
+ int gpio_io_crs_index[2];
+ int gpio_int_seen;
+ int gpio_int_crs_index;
+};
+
+static int gxfp_acpi_res_cb(struct acpi_resource *ares, void *context)
+{
+ struct gxfp_acpi_res_state *st = context;
+
+ if (!ares || !st || !st->dev)
+ return 0;
+
+ switch (ares->type) {
+ case ACPI_RESOURCE_TYPE_GPIO: {
+ struct acpi_resource_gpio *g = &ares->data.gpio;
+ u16 pin0 = 0xffff;
+ const char *ctype = "gpio?";
+
+ st->gpio_seen++;
+
+ if (g->pin_table_length < 1)
+ break;
+ pin0 = g->pin_table[0];
+
+ if (g->connection_type == ACPI_RESOURCE_GPIO_TYPE_INT) {
+ ctype = "GpioInt";
+ st->gpio_int_seen++;
+ if (st->gpio_int_seen == 1)
+ st->gpio_int_crs_index = st->gpio_seen - 1;
+ } else if (g->connection_type == ACPI_RESOURCE_GPIO_TYPE_IO) {
+ ctype = "GpioIo";
+ if (st->gpio_io_seen < 2) {
+ st->gpio_io_pin[st->gpio_io_seen] = pin0;
+ st->gpio_io_pol[st->gpio_io_seen] = g->polarity;
+ st->gpio_io_crs_index[st->gpio_io_seen] = st->gpio_seen - 1;
+ st->gpio_io_seen++;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int gxfp_acpi_get_gpio_crs_indexes(struct device *dev,
+ int *irq_gpio_crs_index,
+ int done_gpio_crs_index[2],
+ bool *done_active_low)
+{
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ struct gxfp_acpi_res_state st = { .dev = dev };
+ LIST_HEAD(res_list);
+ int rc;
+ bool p0, p1;
+
+ if (!dev || !irq_gpio_crs_index || !done_gpio_crs_index || !done_active_low)
+ return -EINVAL;
+ if (!adev)
+ return -ENODEV;
+
+ st.gpio_int_crs_index = -1;
+ st.gpio_io_crs_index[0] = -1;
+ st.gpio_io_crs_index[1] = -1;
+
+ rc = acpi_dev_get_resources(adev, &res_list, gxfp_acpi_res_cb, &st);
+ acpi_dev_free_resource_list(&res_list);
+ if (rc <= 0)
+ return -ENOENT;
+ if (st.gpio_int_seen < 1 || st.gpio_int_crs_index < 0)
+ return -ENOENT;
+ if (st.gpio_io_seen < 2 || st.gpio_io_crs_index[0] < 0 || st.gpio_io_crs_index[1] < 0)
+ return -ENOENT;
+
+ p0 = st.gpio_io_pol[0] ? true : false;
+ p1 = st.gpio_io_pol[1] ? true : false;
+ if (p0 != p1) {
+ dev_warn(dev, "ACPI _CRS: done GPIO polarities differ (done[0]=%u done[1]=%u)\n",
+ st.gpio_io_pol[0], st.gpio_io_pol[1]);
+ return -EINVAL;
+ }
+
+ *irq_gpio_crs_index = st.gpio_int_crs_index;
+ done_gpio_crs_index[0] = st.gpio_io_crs_index[0];
+ done_gpio_crs_index[1] = st.gpio_io_crs_index[1];
+ *done_active_low = p0;
+ return 0;
+}
+
+int gxfp_acpi_get_hid(struct device *dev, char *buf, size_t bufsz)
+{
+ struct acpi_device *adev;
+ const char *hid;
+ size_t len;
+
+ if (!dev || !buf || bufsz < 2)
+ return -EINVAL;
+
+ adev = ACPI_COMPANION(dev);
+ if (!adev)
+ return -ENODEV;
+
+ hid = acpi_device_hid(adev);
+ if (!hid || hid[0] == '\0')
+ return -ENOENT;
+
+ len = strlen(hid);
+ if (len >= bufsz)
+ return -ENOSPC;
+
+ memcpy(buf, hid, len + 1);
+ return 0;
+}
diff --git a/drivers/misc/gxfp5130/hw/gxfp_acpi.h b/drivers/misc/gxfp5130/hw/gxfp_acpi.h
new file mode 100644
index 000000000..5975a9fa0
--- /dev/null
+++ b/drivers/misc/gxfp5130/hw/gxfp_acpi.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_ACPI_H
+#define __GXFP_ACPI_H
+
+#include <linux/device.h>
+#include <linux/acpi.h>
+
+int gxfp_acpi_get_gpio_crs_indexes(struct device *dev,
+ int *irq_gpio_crs_index,
+ int done_gpio_crs_index[2],
+ bool *done_active_low);
+
+int gxfp_acpi_get_hid(struct device *dev, char *buf, size_t bufsz);
+
+#endif /* __GXFP_ACPI_H */
diff --git a/drivers/misc/gxfp5130/hw/gxfp_delay.c b/drivers/misc/gxfp5130/hw/gxfp_delay.c
new file mode 100644
index 000000000..58c0e7396
--- /dev/null
+++ b/drivers/misc/gxfp5130/hw/gxfp_delay.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/delay.h>
+
+#include "gxfp_delay.h"
+
+void gxfp_busy_wait_us(unsigned int us)
+{
+ if (!us)
+ return;
+ if (us <= 2000) {
+ udelay(us);
+ return;
+ }
+ usleep_range(us, us + 200);
+}
diff --git a/drivers/misc/gxfp5130/hw/gxfp_delay.h b/drivers/misc/gxfp5130/hw/gxfp_delay.h
new file mode 100644
index 000000000..7c7f7f46a
--- /dev/null
+++ b/drivers/misc/gxfp5130/hw/gxfp_delay.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_DELAY_H
+#define __GXFP_DELAY_H
+
+void gxfp_busy_wait_us(unsigned int us);
+
+#endif /* __GXFP_DELAY_H */
diff --git a/drivers/misc/gxfp5130/hw/gxfp_gpio.c b/drivers/misc/gxfp5130/hw/gxfp_gpio.c
new file mode 100644
index 000000000..934e8397a
--- /dev/null
+++ b/drivers/misc/gxfp5130/hw/gxfp_gpio.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/moduleparam.h>
+#include <linux/module.h>
+
+#include "gxfp_gpio.h"
+#include "gxfp_delay.h"
+#include "gxfp_priv.h"
+
+#define GXFP_GPIO_PULSE_HIGH_US 4000
+#define GXFP_GPIO_READ_DONE_CLEAR_DELAY_US 200
+#define GXFP_GPIO_READ_DONE_HIGH_US 50
+#define GXFP_GPIO_WRITE_DONE_FORCE_DEASSERT true
+#define GXFP_GPIO_WRITE_DONE_PRE_US 50
+
+static inline void gxfp_gpio_pulse_delay_us(unsigned int us)
+{
+ if (us)
+ gxfp_busy_wait_us(us);
+}
+
+
+void gxfp_gpio_pulse_write_done(struct gxfp_dev *gdev)
+{
+ if (GXFP_GPIO_WRITE_DONE_FORCE_DEASSERT && gdev && gdev->hw.gpio_write_done) {
+ gpiod_set_value_cansleep(gdev->hw.gpio_write_done, 0);
+ gxfp_gpio_pulse_delay_us(GXFP_GPIO_WRITE_DONE_PRE_US);
+ }
+ gpiod_set_value_cansleep(gdev->hw.gpio_write_done, 1);
+ gxfp_gpio_pulse_delay_us(GXFP_GPIO_PULSE_HIGH_US);
+ gpiod_set_value_cansleep(gdev->hw.gpio_write_done, 0);
+
+ gxfp_gpio_pulse_delay_us(GXFP_GPIO_READ_DONE_CLEAR_DELAY_US);
+}
+
+void gxfp_gpio_pulse_read_done(struct gxfp_dev *gdev)
+{
+ gxfp_gpio_set_read_done(gdev, 1);
+ gxfp_gpio_pulse_delay_us(GXFP_GPIO_READ_DONE_HIGH_US);
+ gxfp_gpio_set_read_done(gdev, 0);
+}
+
+void gxfp_gpio_set_read_done(struct gxfp_dev *gdev, int value)
+{
+ if (!gdev || !gdev->hw.gpio_read_done)
+ return;
+
+ gpiod_set_value_cansleep(gdev->hw.gpio_read_done, value ? 1 : 0);
+
+ if (!value)
+ gxfp_gpio_pulse_delay_us(GXFP_GPIO_READ_DONE_CLEAR_DELAY_US);
+}
+
+void gxfp_gpio_force_done_deassert(struct device *dev, struct gxfp_dev *gdev)
+{
+ if (!dev || !gdev || !gdev->hw.gpio_write_done || !gdev->hw.gpio_read_done)
+ return;
+
+ gpiod_set_value_cansleep(gdev->hw.gpio_write_done, 0);
+ gpiod_set_value_cansleep(gdev->hw.gpio_read_done, 0);
+}
diff --git a/drivers/misc/gxfp5130/hw/gxfp_gpio.h b/drivers/misc/gxfp5130/hw/gxfp_gpio.h
new file mode 100644
index 000000000..a36550ccf
--- /dev/null
+++ b/drivers/misc/gxfp5130/hw/gxfp_gpio.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_GPIO_H
+#define __GXFP_GPIO_H
+
+#include <linux/device.h>
+
+struct gxfp_dev;
+
+void gxfp_gpio_pulse_write_done(struct gxfp_dev *gdev);
+
+void gxfp_gpio_set_read_done(struct gxfp_dev *gdev, int value);
+
+void gxfp_gpio_pulse_read_done(struct gxfp_dev *gdev);
+
+void gxfp_gpio_force_done_deassert(struct device *dev, struct gxfp_dev *gdev);
+
+#endif /* __GXFP_GPIO_H */
diff --git a/drivers/misc/gxfp5130/hw/gxfp_mmio.c b/drivers/misc/gxfp5130/hw/gxfp_mmio.c
new file mode 100644
index 000000000..22187cd5a
--- /dev/null
+++ b/drivers/misc/gxfp5130/hw/gxfp_mmio.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+
+#include "gxfp_mmio.h"
+
+int gxfp_mmio_write_qword_aligned(void __iomem *mmio, const __u8 *buf, size_t len)
+{
+ size_t i, qwords;
+
+ if (!mmio || !buf)
+ return -EINVAL;
+ if (len % 8)
+ return -EINVAL;
+
+ qwords = len / 8;
+ for (i = 0; i < qwords; i++) {
+ __u64 v;
+
+ memcpy(&v, buf + i * 8, 8);
+ writeq(v, mmio + i * 8);
+ }
+ wmb(); /* ensure all writes are visible to the device before returning */
+ return 0;
+}
+
+int gxfp_mmio_read_qword_aligned(__u8 *buf, void __iomem *mmio, size_t len)
+{
+ size_t i, qwords;
+
+ if (!mmio || !buf)
+ return -EINVAL;
+ if (len % 8)
+ return -EINVAL;
+
+ qwords = len / 8;
+ for (i = 0; i < qwords; i++) {
+ __u64 v = readq(mmio + i * 8);
+
+ memcpy(buf + i * 8, &v, 8);
+ }
+ rmb(); /* ensure all reads are complete before caller processes data */
+ return 0;
+}
diff --git a/drivers/misc/gxfp5130/hw/gxfp_mmio.h b/drivers/misc/gxfp5130/hw/gxfp_mmio.h
new file mode 100644
index 000000000..b701682a9
--- /dev/null
+++ b/drivers/misc/gxfp5130/hw/gxfp_mmio.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_MMIO_H
+#define __GXFP_MMIO_H
+
+#include <linux/types.h>
+
+int gxfp_mmio_write_qword_aligned(void __iomem *mmio, const __u8 *buf, size_t len);
+int gxfp_mmio_read_qword_aligned(__u8 *buf, void __iomem *mmio, size_t len);
+
+#endif /* __GXFP_MMIO_H */
diff --git a/drivers/misc/gxfp5130/include/gxfp_constants.h b/drivers/misc/gxfp5130/include/gxfp_constants.h
new file mode 100644
index 000000000..ff4025cd4
--- /dev/null
+++ b/drivers/misc/gxfp5130/include/gxfp_constants.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_CONSTANTS_H
+#define __GXFP_CONSTANTS_H
+
+#define GXFP_MAILBOX_MIN_SIZE 0x1000
+
+#define GXFP_READ_CHUNK_MAX 0xe00
+#define GXFP_TX_BUFFER_SIZE 0x200
+
+#define GXFP_CMD_FIRMWARE_VERSION 0xA8
+#define GXFP_CMD_TRIGGER_MCU_STATE 0xAF
+#define GXFP_CMD_QUERY_MCU_STATE 0xAE
+#define GXFP_CMD_ACK 0xB0
+#define GXFP_CMD_UPLOAD_CONFIG_MCU 0x90
+#define GXFP_CMD_NOTIFY_POWER_STATE 0x0E
+#define GXFP_CMD_PROTOCOL_INIT 0x01
+#define GXFP_CMD_SLEEP_CLEANUP 0xD2
+#define GXFP_CMD_RESET_DEVICE 0xA1
+#define GXFP_CMD_TLS_UNLOCK 0xD4
+
+#define GXFP_MAX_VERSION_RAW 512
+#define GXFP_MAX_VERSION_ASCII 32
+#define GXFP_MAX_MCU_STATE_PAYLOAD 256
+
+#define GXFP_DEFAULT_RETRY_COUNT 8
+#define GXFP_DEFAULT_TIMEOUT_US 15000
+#define GXFP_RESET_DELAY_US 200000
+
+#define GXFP_FRAME_ALIGNMENT 8
+
+#define GXFP_MP_TYPE_A 0xAu
+#define GXFP_MP_TYPE_B 0xBu
+#define GXFP_MP_TYPE_C 0xCu
+
+#define GXFP_ESPI_WRAPPER_MAGIC 0xF0
+#define GXFP_ESPI_SEQ16_INIT 0x8881
+
+#endif /* __GXFP_CONSTANTS_H */
diff --git a/drivers/misc/gxfp5130/include/gxfp_priv.h b/drivers/misc/gxfp5130/include/gxfp_priv.h
new file mode 100644
index 000000000..71c6d8332
--- /dev/null
+++ b/drivers/misc/gxfp5130/include/gxfp_priv.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_PRIV_H
+#define __GXFP_PRIV_H
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/atomic.h>
+#include <linux/mutex.h>
+#include <linux/miscdevice.h>
+#include <linux/kfifo.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+
+struct gxfp_dev {
+ struct device *dev;
+
+ struct mutex lock;
+ atomic_t mailbox_seq16;
+
+ struct {
+ void __iomem *mailbox_mmio;
+ resource_size_t mailbox_size;
+
+ struct gpio_desc *gpio_write_done;
+ struct gpio_desc *gpio_read_done;
+ struct gpio_desc *gpio_irq;
+ int irq;
+ } hw;
+
+ struct {
+ __u8 *rx_buf;
+ size_t rx_cap;
+ __u8 *rx_tmp;
+
+ bool assem_active;
+ size_t assem_total;
+ size_t assem_copied;
+ } irq;
+
+ struct {
+ struct miscdevice miscdev;
+ bool misc_registered;
+
+ struct mutex rxq_mutex;
+ spinlock_t rxq_lock;
+ wait_queue_head_t rxq_wq;
+ struct kfifo rxq_fifo; /* bytes: [tap_hdr][payload][tap_hdr][payload]... */
+ bool rxq_inited;
+ bool rxq_reader_open;
+ } uapi;
+
+};
+
+#endif /* __GXFP_PRIV_H */
diff --git a/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.c b/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.c
new file mode 100644
index 000000000..03a30cd6d
--- /dev/null
+++ b/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include "gxfp_goodix_proto.h"
+
+bool gxfp_parse_goodix_body(const __u8 *buf, size_t buf_len,
+ struct gxfp_frame_parsed *out)
+{
+ size_t need;
+ __u16 decl_len, payload_len;
+ __u8 cmd;
+ __u8 proto_ck;
+
+ if (!out || !buf || buf_len < 4)
+ return false;
+ memset(out, 0, sizeof(*out));
+
+ cmd = buf[0];
+ decl_len = (__u16)buf[1] | ((__u16)buf[2] << 8);
+ if (decl_len < 1)
+ return false;
+ payload_len = (__u16)(decl_len - 1);
+
+ need = 3u + (size_t)payload_len + 1u;
+ if (need > buf_len)
+ return false;
+
+ out->cmd = cmd;
+ out->decl_len = decl_len;
+ out->payload = &buf[3];
+ out->payload_len = payload_len;
+ proto_ck = buf[3 + payload_len];
+ out->proto_checksum = proto_ck;
+ out->proto_checksum_ok =
+ (gxfp_goodix_proto_checksum(cmd, decl_len, out->payload, payload_len) == proto_ck);
+ out->valid = true;
+
+ return true;
+}
+
+__u8 gxfp_goodix_proto_checksum(__u8 cmd, __u16 decl_len,
+ const __u8 *payload, size_t payload_len)
+{
+ __u32 sum = (__u32)cmd + (__u32)(decl_len & 0xff) + (__u32)((decl_len >> 8) & 0xff);
+ size_t i;
+
+ for (i = 0; i < payload_len; i++)
+ sum += (__u32)payload[i];
+
+ return (__u8)(0xaa - (__u8)sum);
+}
+
+int gxfp_goodix_build_frame(__u8 cmd,
+ const __u8 *payload,
+ size_t payload_len,
+ __u8 *out,
+ size_t out_cap)
+{
+ __u16 decl_len;
+ size_t total;
+
+ if (!out || out_cap < 4)
+ return -EINVAL;
+ if (payload_len && !payload)
+ return -EINVAL;
+ if (payload_len > GXFP_GOODIX_PAYLOAD_U16_MAX)
+ return -EOVERFLOW;
+
+ decl_len = (__u16)(1u + payload_len);
+ total = 1u + 2u + payload_len + 1u;
+ if (total > out_cap)
+ return -EMSGSIZE;
+
+ out[0] = cmd;
+ out[1] = (__u8)(decl_len & 0xff);
+ out[2] = (__u8)((decl_len >> 8) & 0xff);
+ if (payload_len)
+ memcpy(&out[3], payload, payload_len);
+ out[3 + payload_len] = gxfp_goodix_proto_checksum(cmd, decl_len, payload, payload_len);
+
+ return (int)total;
+}
diff --git a/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.h b/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.h
new file mode 100644
index 000000000..4bbee6f79
--- /dev/null
+++ b/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_GOODIX_PROTO_H
+#define __GXFP_GOODIX_PROTO_H
+
+#include <linux/types.h>
+
+#define GXFP_GOODIX_PAYLOAD_U16_MAX 0xFF00u
+
+struct gxfp_frame_parsed {
+ bool valid;
+ bool proto_checksum_ok;
+ __u8 cmd;
+ __u16 decl_len;
+ const __u8 *payload;
+ __u16 payload_len;
+ __u8 proto_checksum;
+};
+
+bool gxfp_parse_goodix_body(const __u8 *buf, size_t buf_len,
+ struct gxfp_frame_parsed *out);
+
+__u8 gxfp_goodix_proto_checksum(__u8 cmd, __u16 decl_len,
+ const __u8 *payload, size_t payload_len);
+
+int gxfp_goodix_build_frame(__u8 cmd,
+ const __u8 *payload,
+ size_t payload_len,
+ __u8 *out,
+ size_t out_cap);
+
+#endif /* __GXFP_GOODIX_PROTO_H */
diff --git a/drivers/misc/gxfp5130/proto/gxfp_mp_proto.c b/drivers/misc/gxfp5130/proto/gxfp_mp_proto.c
new file mode 100644
index 000000000..19fa44e36
--- /dev/null
+++ b/drivers/misc/gxfp5130/proto/gxfp_mp_proto.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include "gxfp_mp_proto.h"
+#include "gxfp_constants.h"
+
+bool gxfp_parse_mp_frame(const __u8 *buf, size_t buf_len,
+ struct gxfp_mp_frame_parsed *out)
+{
+ __u16 mp_len = 0;
+ size_t total;
+
+ if (!out || !buf || buf_len < 4)
+ return false;
+ memset(out, 0, sizeof(*out));
+
+ if (!gxfp_check_mp_head(buf, 4, &mp_len))
+ return false;
+
+ total = 4u + (size_t)mp_len;
+ if (total > buf_len)
+ return false;
+
+ out->flags = buf[0];
+ out->payload_len = mp_len;
+ out->payload = &buf[4];
+ out->valid = true;
+ return true;
+}
+
+bool gxfp_check_mp_head(const __u8 *buf, size_t buf_len, __u16 *out_mp_len)
+{
+ __u8 flags, type, ck_expect;
+ __u16 mp_len;
+
+ if (out_mp_len)
+ *out_mp_len = 0;
+ if (!buf || buf_len < 4)
+ return false;
+
+ flags = buf[0];
+ type = flags >> 4;
+ if (type != GXFP_MP_TYPE_A &&
+ type != GXFP_MP_TYPE_B &&
+ type != GXFP_MP_TYPE_C)
+ return false;
+
+ mp_len = (__u16)buf[1] | ((__u16)buf[2] << 8);
+ if (mp_len >= (__u16)GXFP_GOODIX_MP_LEN_MAX)
+ return false;
+
+ ck_expect = (__u8)(flags + buf[1] + buf[2]);
+ if (buf[3] != ck_expect)
+ return false;
+
+ if (out_mp_len)
+ *out_mp_len = mp_len;
+ return true;
+}
+
+int gxfp_mp_build_frame(__u8 mp_type,
+ const __u8 *payload,
+ size_t payload_len,
+ __u8 *out,
+ size_t out_cap)
+{
+ __u8 flags;
+ __u16 mp_len;
+ size_t total;
+
+ if (!out || out_cap < 4)
+ return -EINVAL;
+ if (payload_len && !payload)
+ return -EINVAL;
+ if (payload_len > GXFP_GOODIX_MP_LEN_MAX)
+ return -EOVERFLOW;
+ if (mp_type != GXFP_MP_TYPE_A && mp_type != GXFP_MP_TYPE_B && mp_type != GXFP_MP_TYPE_C)
+ return -EINVAL;
+
+ mp_len = (__u16)payload_len;
+ total = 4u + payload_len;
+ if (total > out_cap)
+ return -EMSGSIZE;
+
+ flags = (__u8)(mp_type << 4);
+ out[0] = flags;
+ out[1] = (__u8)(mp_len & 0xff);
+ out[2] = (__u8)((mp_len >> 8) & 0xff);
+ out[3] = (__u8)(out[0] + out[1] + out[2]);
+ if (payload_len)
+ memcpy(&out[4], payload, payload_len);
+
+ return (int)total;
+}
diff --git a/drivers/misc/gxfp5130/proto/gxfp_mp_proto.h b/drivers/misc/gxfp5130/proto/gxfp_mp_proto.h
new file mode 100644
index 000000000..9773c09be
--- /dev/null
+++ b/drivers/misc/gxfp5130/proto/gxfp_mp_proto.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_MP_PROTO_H
+#define __GXFP_MP_PROTO_H
+
+#include <linux/types.h>
+
+#define GXFP_GOODIX_MP_LEN_MAX 0x6c00u
+
+struct gxfp_mp_frame_parsed {
+ bool valid;
+ __u8 flags;
+ __u16 payload_len;
+ const __u8 *payload;
+};
+
+bool gxfp_parse_mp_frame(const __u8 *buf, size_t buf_len,
+ struct gxfp_mp_frame_parsed *out);
+
+bool gxfp_check_mp_head(const __u8 *buf, size_t buf_len, __u16 *out_mp_len);
+
+int gxfp_mp_build_frame(__u8 mp_type,
+ const __u8 *payload,
+ size_t payload_len,
+ __u8 *out,
+ size_t out_cap);
+
+#endif /* __GXFP_MP_PROTO_H */
diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_common.c b/drivers/misc/gxfp5130/transport/gxfp_espi_common.c
new file mode 100644
index 000000000..fd93a2d86
--- /dev/null
+++ b/drivers/misc/gxfp5130/transport/gxfp_espi_common.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/minmax.h>
+#include <linux/unaligned.h>
+
+#include "gxfp_espi_common.h"
+#include "gxfp_priv.h"
+#include "../hw/gxfp_mmio.h"
+#include "../proto/gxfp_mp_proto.h"
+
+void gxfp_espi_irq_assem_reset(struct gxfp_dev *gdev)
+{
+ gdev->irq.assem_active = false;
+ gdev->irq.assem_total = 0;
+ gdev->irq.assem_copied = 0;
+}
+
+int gxfp_espi_rx_read_window(struct gxfp_dev *gdev, __u8 *dst)
+{
+ int ret;
+
+ ret = gxfp_mmio_read_qword_aligned(dst,
+ gdev->hw.mailbox_mmio + GXFP_ESPI_RX_OFF,
+ (size_t)GXFP_ESPI_CHUNK_MAX);
+ if (ret)
+ return -EIO;
+
+ return 0;
+}
+
+void gxfp_espi_rx_chunk_calc(size_t remain,
+ size_t *chunk,
+ size_t *pad,
+ size_t *do_read)
+{
+ *chunk = min_t(size_t, remain, (size_t)GXFP_ESPI_CHUNK_MAX);
+ *pad = (GXFP_ESPI_ALIGN - (*chunk & (GXFP_ESPI_ALIGN - 1))) &
+ (GXFP_ESPI_ALIGN - 1);
+ if (do_read)
+ *do_read = *chunk + *pad;
+}
+
+int gxfp_espi_rx_start_packet(struct gxfp_dev *gdev,
+ __u8 *rx_buf,
+ size_t rx_cap,
+ bool validate_head,
+ size_t *out_total,
+ size_t *out_first_chunk)
+{
+ size_t total;
+ __u16 mp_len = 0;
+ int ret;
+
+ ret = gxfp_espi_rx_read_window(gdev, rx_buf);
+ if (ret)
+ return ret;
+
+ if (validate_head) {
+ if (!gxfp_check_mp_head(rx_buf, 4, &mp_len)) {
+ dev_warn_ratelimited(gdev->dev,
+ "ESPI RX: bad package head (gxfp_check_mp_head failed)\n");
+ return -EBADMSG;
+ }
+ } else {
+ mp_len = get_unaligned_le16(rx_buf + 1);
+ }
+
+ total = 4u + (size_t)mp_len;
+ if (total == 0 || total > rx_cap)
+ return -EMSGSIZE;
+
+ if (out_total)
+ *out_total = total;
+ if (out_first_chunk)
+ *out_first_chunk = min_t(size_t, total, (size_t)GXFP_ESPI_CHUNK_MAX);
+
+ return 0;
+}
diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_common.h b/drivers/misc/gxfp5130/transport/gxfp_espi_common.h
new file mode 100644
index 000000000..75607c478
--- /dev/null
+++ b/drivers/misc/gxfp5130/transport/gxfp_espi_common.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_ESPI_COMMON_H
+#define __GXFP_ESPI_COMMON_H
+
+#include <linux/types.h>
+#include "../include/gxfp_constants.h"
+
+struct gxfp_dev;
+
+#define GXFP_ESPI_RX_OFF 0x200
+#define GXFP_ESPI_CHUNK_MAX GXFP_READ_CHUNK_MAX
+#define GXFP_ESPI_ALIGN GXFP_FRAME_ALIGNMENT
+
+void gxfp_espi_irq_assem_reset(struct gxfp_dev *gdev);
+int gxfp_espi_rx_read_window(struct gxfp_dev *gdev, __u8 *dst);
+void gxfp_espi_rx_chunk_calc(size_t remain,
+ size_t *chunk,
+ size_t *pad,
+ size_t *do_read);
+int gxfp_espi_rx_start_packet(struct gxfp_dev *gdev,
+ __u8 *rx_buf,
+ size_t rx_cap,
+ bool validate_head,
+ size_t *out_total,
+ size_t *out_first_chunk);
+
+#endif /* __GXFP_ESPI_COMMON_H */
diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.c b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.c
new file mode 100644
index 000000000..43c424366
--- /dev/null
+++ b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/errno.h>
+#include <linux/string.h>
+
+#include "gxfp_espi_common.h"
+#include "gxfp_espi_rx_irq.h"
+#include "gxfp_priv.h"
+#include "../hw/gxfp_gpio.h"
+
+int gxfp_espi_irq_read_step(struct gxfp_dev *gdev,
+ __u8 *rx_buf,
+ size_t rx_cap,
+ size_t *out_rx_len,
+ bool *complete)
+{
+ int ret;
+ size_t total;
+ size_t first_chunk;
+ size_t copied;
+
+ if (complete)
+ *complete = false;
+ if (out_rx_len)
+ *out_rx_len = 0;
+
+ if (!gdev || !rx_buf || rx_cap < GXFP_ESPI_CHUNK_MAX)
+ return -EINVAL;
+ if (!gdev->hw.mailbox_mmio)
+ return -ENODEV;
+ if (gdev->hw.mailbox_size < GXFP_ESPI_RX_OFF + GXFP_ESPI_CHUNK_MAX)
+ return -ENODEV;
+
+ if (!gdev->irq.assem_active) {
+ ret = gxfp_espi_rx_start_packet(gdev, rx_buf, rx_cap, true,
+ &total, &first_chunk);
+ if (ret) {
+ gxfp_espi_irq_assem_reset(gdev);
+ return ret;
+ }
+
+ gdev->irq.assem_total = total;
+ gdev->irq.assem_copied = first_chunk;
+ gdev->irq.assem_active = true;
+
+ copied = gdev->irq.assem_copied;
+ if (copied >= total) {
+ gxfp_gpio_set_read_done(gdev, 1);
+ gxfp_espi_irq_assem_reset(gdev);
+ if (complete)
+ *complete = true;
+ if (out_rx_len)
+ *out_rx_len = total;
+ return 0;
+ }
+
+ gxfp_gpio_pulse_read_done(gdev);
+ if (out_rx_len)
+ *out_rx_len = copied;
+ return 0;
+ }
+
+ total = gdev->irq.assem_total;
+ copied = gdev->irq.assem_copied;
+ if (total == 0 || copied == 0 || copied > rx_cap) {
+ gxfp_espi_irq_assem_reset(gdev);
+ return -EINVAL;
+ }
+
+ {
+ size_t remain = (copied < total) ? (total - copied) : 0;
+ size_t chunk;
+ size_t pad;
+ __u8 *tmp = gdev->irq.rx_tmp;
+
+ gxfp_espi_rx_chunk_calc(remain, &chunk, &pad, NULL);
+ if (chunk == 0) {
+ gxfp_espi_irq_assem_reset(gdev);
+ if (complete)
+ *complete = true;
+ if (out_rx_len)
+ *out_rx_len = total;
+ return 0;
+ }
+
+ if (copied + chunk > rx_cap) {
+ gxfp_espi_irq_assem_reset(gdev);
+ return -EMSGSIZE;
+ }
+
+ if (!tmp) {
+ gxfp_espi_irq_assem_reset(gdev);
+ return -ENODEV;
+ }
+
+ ret = gxfp_espi_rx_read_window(gdev, tmp);
+ if (ret) {
+ gxfp_espi_irq_assem_reset(gdev);
+ return ret;
+ }
+
+ memcpy(rx_buf + copied, tmp, chunk);
+ copied += chunk;
+ gdev->irq.assem_copied = copied;
+ }
+
+ if (copied < total) {
+ gxfp_gpio_pulse_read_done(gdev);
+ if (out_rx_len)
+ *out_rx_len = copied;
+ return 0;
+ }
+
+ gxfp_gpio_set_read_done(gdev, 1);
+ gxfp_espi_irq_assem_reset(gdev);
+ if (complete)
+ *complete = true;
+ if (out_rx_len)
+ *out_rx_len = total;
+ return 0;
+}
diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.h b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.h
new file mode 100644
index 000000000..98ef1fc18
--- /dev/null
+++ b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_ESPI_RX_IRQ_H
+#define __GXFP_ESPI_RX_IRQ_H
+
+#include <linux/types.h>
+
+struct gxfp_dev;
+
+int gxfp_espi_irq_read_step(struct gxfp_dev *gdev,
+ __u8 *rx_buf,
+ size_t rx_cap,
+ size_t *out_rx_len,
+ bool *complete);
+
+#endif /* __GXFP_ESPI_RX_IRQ_H */
diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.c b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.c
new file mode 100644
index 000000000..5afb6e358
--- /dev/null
+++ b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/minmax.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#include "gxfp_espi_common.h"
+#include "gxfp_espi_rx_sync.h"
+#include "gxfp_priv.h"
+#include "../hw/gxfp_delay.h"
+#include "../hw/gxfp_gpio.h"
+#include "gxfp_constants.h"
+
+static int gxfp_espi_read_assemble(struct gxfp_dev *gdev,
+ __u8 *dst,
+ size_t dst_cap,
+ size_t *out_len)
+{
+ size_t total;
+ size_t first_chunk;
+ size_t off;
+ int ret;
+
+ if (!gdev || !dst || dst_cap < GXFP_ESPI_CHUNK_MAX)
+ return -EINVAL;
+ if (!gdev->hw.mailbox_mmio)
+ return -ENODEV;
+ if (gdev->hw.mailbox_size < GXFP_ESPI_RX_OFF + GXFP_ESPI_CHUNK_MAX)
+ return -ENODEV;
+
+ memset(dst, 0, dst_cap);
+
+ ret = gxfp_espi_rx_start_packet(gdev, dst, dst_cap, false,
+ &total, &first_chunk);
+ if (ret)
+ return ret;
+
+ dev_dbg(gdev->dev, "ESPI RX: first=%u total=%zu (mp_len=%u) head16=%*ph\n",
+ GXFP_ESPI_CHUNK_MAX, total,
+ (unsigned int)get_unaligned_le16(dst + 1), 16, dst);
+ off = first_chunk;
+
+ while (off < total) {
+ size_t remain = total - off;
+ size_t chunk;
+ size_t pad;
+ __u8 *tmp = gdev->irq.rx_tmp;
+
+ gxfp_espi_rx_chunk_calc(remain, &chunk, &pad, NULL);
+
+ dev_dbg(gdev->dev,
+ "ESPI RX: off=%zu remain=%zu chunk=%zu pad=%zu\n",
+ off, remain, chunk, pad);
+
+ if (!tmp)
+ return -ENODEV;
+
+ if (off + chunk > dst_cap)
+ return -EMSGSIZE;
+
+ ret = gxfp_espi_rx_read_window(gdev, tmp);
+ if (ret)
+ return ret;
+
+ memcpy(dst + off, tmp, chunk);
+
+ off += chunk;
+
+ if (off < total) {
+ gxfp_gpio_pulse_read_done(gdev);
+ gxfp_busy_wait_us(GXFP_DEFAULT_TIMEOUT_US);
+ }
+ }
+
+ gxfp_gpio_pulse_read_done(gdev);
+ dev_dbg(gdev->dev, "ESPI RX: done total=%zu\n", total);
+
+ if (out_len)
+ *out_len = total;
+
+ return 0;
+}
+
+int gxfp_espi_read(struct gxfp_dev *gdev,
+ __u8 *rx_buf,
+ size_t rx_cap,
+ size_t *out_rx_len)
+{
+ if (!gdev || !rx_buf || rx_cap == 0)
+ return -EINVAL;
+ if (!gdev->hw.mailbox_mmio)
+ return -ENODEV;
+
+ return gxfp_espi_read_assemble(gdev, rx_buf, rx_cap, out_rx_len);
+}
diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.h b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.h
new file mode 100644
index 000000000..665f82348
--- /dev/null
+++ b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_ESPI_RX_SYNC_H
+#define __GXFP_ESPI_RX_SYNC_H
+
+#include <linux/types.h>
+
+struct gxfp_dev;
+
+int gxfp_espi_read(struct gxfp_dev *gdev,
+ __u8 *rx_buf,
+ size_t rx_cap,
+ size_t *out_rx_len);
+
+#endif /* __GXFP_ESPI_RX_SYNC_H */
diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_tx.c b/drivers/misc/gxfp5130/transport/gxfp_espi_tx.c
new file mode 100644
index 000000000..05d67ed04
--- /dev/null
+++ b/drivers/misc/gxfp5130/transport/gxfp_espi_tx.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/atomic.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "gxfp_espi_tx.h"
+#include "gxfp_espi_rx_sync.h"
+#include "gxfp_espi_common.h"
+#include "gxfp_priv.h"
+#include "../hw/gxfp_delay.h"
+#include "../hw/gxfp_gpio.h"
+#include "../hw/gxfp_mmio.h"
+#include "gxfp_constants.h"
+#include "../driver/gxfp_trace.h"
+
+int gxfp_espi_xfer(struct gxfp_dev *gdev,
+ const __u8 *mp_frame,
+ size_t mp_frame_len,
+ __u8 *rx_buf,
+ size_t rx_cap,
+ size_t *out_rx_len)
+{
+ int ret;
+
+ ret = gxfp_espi_write(gdev, mp_frame, mp_frame_len);
+ if (ret)
+ return ret;
+
+ gxfp_busy_wait_us(1000);
+
+ return gxfp_espi_read(gdev, rx_buf, rx_cap, out_rx_len);
+}
+
+int gxfp_espi_write(struct gxfp_dev *gdev,
+ const __u8 *mp_frame,
+ size_t mp_frame_len)
+{
+ __u8 *tx;
+ __u16 seq16;
+ size_t pad;
+ size_t tx_len;
+ int ret;
+
+ if (!gdev || !mp_frame || mp_frame_len == 0)
+ return -EINVAL;
+ if (!gdev->hw.mailbox_mmio)
+ return -ENODEV;
+
+ pad = (GXFP_ESPI_ALIGN - (mp_frame_len & (GXFP_ESPI_ALIGN - 1))) &
+ (GXFP_ESPI_ALIGN - 1);
+ tx_len = 8 + mp_frame_len + pad;
+ if (tx_len > GXFP_TX_BUFFER_SIZE)
+ return -EOVERFLOW;
+
+ tx = kmalloc(tx_len, GFP_KERNEL);
+ if (!tx)
+ return -ENOMEM;
+
+ memset(tx, 0, tx_len);
+ tx[0] = GXFP_ESPI_WRAPPER_MAGIC;
+ tx[1] = (__u8)(mp_frame_len & 0xff);
+ tx[2] = (__u8)((mp_frame_len >> 8) & 0xff);
+ tx[3] = (__u8)((tx[0] + tx[1] + tx[2]) & 0xff);
+
+ seq16 = (__u16)(atomic_inc_return(&gdev->mailbox_seq16) & 0xFFFF);
+ tx[4] = (__u8)(seq16 & 0xff);
+ tx[5] = (__u8)((seq16 >> 8) & 0xff);
+
+ memcpy(&tx[8], mp_frame, mp_frame_len);
+
+ ret = gxfp_mmio_write_qword_aligned(gdev->hw.mailbox_mmio, tx,
+ ALIGN(tx_len, GXFP_ESPI_ALIGN));
+ gxfp_trace_logf("espi_tx rc=%d seq=0x%04x mp_len=%zu mp_head=%*ph", ret,
+ seq16, mp_frame_len, (int)min_t(size_t, 8, mp_frame_len), mp_frame);
+ if (ret) {
+ dev_err_ratelimited(gdev->dev,
+ "ESPI TX: mmio_write failed rc=%d seq16=0x%04x payload_len=%zu\n",
+ ret, seq16, mp_frame_len);
+ kfree(tx);
+ return -EIO;
+ }
+
+ kfree(tx);
+ gxfp_gpio_pulse_write_done(gdev);
+ return 0;
+}
diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_tx.h b/drivers/misc/gxfp5130/transport/gxfp_espi_tx.h
new file mode 100644
index 000000000..e1a788ab5
--- /dev/null
+++ b/drivers/misc/gxfp5130/transport/gxfp_espi_tx.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __GXFP_ESPI_TX_H
+#define __GXFP_ESPI_TX_H
+
+#include <linux/types.h>
+
+struct gxfp_dev;
+
+int gxfp_espi_xfer(struct gxfp_dev *gdev,
+ const __u8 *mp_frame,
+ size_t mp_frame_len,
+ __u8 *rx_buf,
+ size_t rx_cap,
+ size_t *out_rx_len);
+
+int gxfp_espi_write(struct gxfp_dev *gdev,
+ const __u8 *mp_frame,
+ size_t mp_frame_len);
+
+#endif /* __GXFP_ESPI_TX_H */
--
2.55.0
next prev parent reply other threads:[~2026-07-18 8:14 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-18 8:14 [PATCH 0/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Metehan Günen
2026-07-18 8:14 ` [PATCH 1/4] include/uapi/linux: add gxfp_ioctl.h for GXFP5130 fingerprint sensor Metehan Günen
2026-07-18 9:07 ` Greg KH
2026-07-18 9:08 ` Greg KH
2026-07-18 8:14 ` Metehan Günen [this message]
2026-07-18 9:09 ` [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Greg KH
2026-07-18 9:12 ` Greg KH
2026-07-18 8:14 ` [PATCH 3/4] Documentation/misc-devices: add gxfp5130.rst Metehan Günen
2026-07-18 9:06 ` Greg KH
2026-07-18 8:14 ` [PATCH 4/4] MAINTAINERS: add entry for GXFP5130 fingerprint sensor driver Metehan Günen
2026-07-18 9:06 ` Greg KH
2026-07-18 9:05 ` [PATCH 0/4] drivers/misc: add Goodix GXFP5130 eSPI " Greg KH
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=20260718081431.25456-3-metehangnen@gmail.com \
--to=metehangnen@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-kernel@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox