* [PATCH 0/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver
@ 2026-07-18 8:14 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
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Metehan Günen @ 2026-07-18 8:14 UTC (permalink / raw)
To: linux-kernel; +Cc: gregkh, linux-acpi, linux-gpio, Metehan Günen
This series adds a kernel driver for the Goodix GXFP5130 fingerprint
sensor found in Huawei MateBook laptops (D16 2024, X Pro 2024, 14 2024).
The sensor is enumerated through ACPI (HID: GXFP5130) and communicates
with the host via an eSPI-based memory-mapped mailbox window plus three
GPIOs. It does not use SPI or I2C bus drivers.
A misc character device /dev/gxfp is exposed to userspace. The libfprint
library (with a corresponding GXFP5130 plugin) uses this device to perform
biometric enrollment and verification via fprintd.
The four patches are:
[1/4] UAPI header (include/uapi/linux/gxfp_ioctl.h)
[2/4] Driver source tree (drivers/misc/gxfp5130/)
[3/4] Documentation (Documentation/misc-devices/gxfp5130.rst)
[4/4] MAINTAINERS entry
Metehan Günen (4):
include/uapi/linux: add gxfp_ioctl.h for GXFP5130 fingerprint sensor
drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver
Documentation/misc-devices: add gxfp5130.rst
MAINTAINERS: add entry for GXFP5130 fingerprint sensor driver
Documentation/misc-devices/gxfp5130.rst | 90 +++++++
Documentation/misc-devices/index.rst | 1 +
MAINTAINERS | 7 +
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 ++
include/uapi/linux/gxfp_ioctl.h | 43 +++
51 files changed, 3188 insertions(+)
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/4] include/uapi/linux: add gxfp_ioctl.h for GXFP5130 fingerprint sensor
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 ` Metehan Günen
2026-07-18 9:07 ` Greg KH
2026-07-18 9:08 ` Greg KH
2026-07-18 8:14 ` [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Metehan Günen
` (3 subsequent siblings)
4 siblings, 2 replies; 12+ messages in thread
From: Metehan Günen @ 2026-07-18 8:14 UTC (permalink / raw)
To: linux-kernel; +Cc: gregkh, linux-acpi, linux-gpio, Metehan Günen
Define the userspace ABI for the Goodix GXFP5130 eSPI fingerprint sensor
driver:
- struct gxfp_tap_hdr: header prepended to every read(2) record, carries
payload length, MP protocol type and a ktime_get_ns() timestamp.
- struct gxfp_tx_pkt_hdr: header that userspace prepends to write(2)
payloads so the driver can wrap them in the eSPI frame format.
- GXFP_IOCTL_FLUSH_RXQ: flush the kernel-side RX record queue.
Signed-off-by: Metehan Günen <metehangnen@gmail.com>
---
include/uapi/linux/gxfp_ioctl.h | 43 +++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 include/uapi/linux/gxfp_ioctl.h
diff --git a/include/uapi/linux/gxfp_ioctl.h b/include/uapi/linux/gxfp_ioctl.h
new file mode 100644
index 000000000..c9c6c2d48
--- /dev/null
+++ b/include/uapi/linux/gxfp_ioctl.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __UAPI_GXFP_IOCTL_H
+#define __UAPI_GXFP_IOCTL_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define GXFP_IOCTL_MAGIC 'G'
+
+/*
+ * 4-byte MP header + payload must fit GXFP_TX_BUFFER_SIZE(512).
+ */
+#define GXFP_IOCTL_TX_PAYLOAD_MAX 508u
+
+/* Maximum protocol payload bytes exported via read(2) records. */
+#define GXFP_IOCTL_TAP_PAYLOAD_MAX (128u * 1024u)
+
+/*
+ * Stream tap (read/poll) record header.
+ * Followed by `len` bytes of protocol payload.
+ */
+struct gxfp_tap_hdr {
+ __u32 len; /* protocol payload bytes */
+ __u32 type; /* MP type */
+ __u32 _rsvd0;
+ __u64 ts_ns; /* ktime_get_ns() */
+ __u8 head16[16]; /* first 16 bytes of payload */
+};
+
+/*
+ * write(2) packet header.
+ * Userspace writes: [gxfp_tx_pkt_hdr][payload bytes]
+ */
+struct gxfp_tx_pkt_hdr {
+ __u8 mp_flags;
+ __u8 _pad0;
+ __u16 payload_len;
+ __u32 flags;
+};
+
+#define GXFP_IOCTL_FLUSH_RXQ _IO(GXFP_IOCTL_MAGIC, 0x11)
+
+#endif /* __UAPI_GXFP_IOCTL_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver
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 8:14 ` Metehan Günen
2026-07-18 9:09 ` 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
` (2 subsequent siblings)
4 siblings, 2 replies; 12+ messages in thread
From: Metehan Günen @ 2026-07-18 8:14 UTC (permalink / raw)
To: linux-kernel; +Cc: gregkh, linux-acpi, linux-gpio, Metehan Günen
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
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 3/4] Documentation/misc-devices: add gxfp5130.rst
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 8:14 ` [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Metehan Günen
@ 2026-07-18 8:14 ` 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:05 ` [PATCH 0/4] drivers/misc: add Goodix GXFP5130 eSPI " Greg KH
4 siblings, 1 reply; 12+ messages in thread
From: Metehan Günen @ 2026-07-18 8:14 UTC (permalink / raw)
To: linux-kernel; +Cc: gregkh, linux-acpi, linux-gpio, Metehan Günen
Document the Goodix GXFP5130 eSPI fingerprint sensor driver:
hardware interface (MMIO mailbox + 3 GPIOs), userspace ABI
(open/write/read/poll/ioctl), sysfs attribute, debugfs trace interface,
and the table of supported laptop models.
Signed-off-by: Metehan Günen <metehangnen@gmail.com>
---
Documentation/misc-devices/gxfp5130.rst | 90 +++++++++++++++++++++++++
Documentation/misc-devices/index.rst | 1 +
2 files changed, 91 insertions(+)
create mode 100644 Documentation/misc-devices/gxfp5130.rst
diff --git a/Documentation/misc-devices/gxfp5130.rst b/Documentation/misc-devices/gxfp5130.rst
new file mode 100644
index 000000000..73a468a8b
--- /dev/null
+++ b/Documentation/misc-devices/gxfp5130.rst
@@ -0,0 +1,90 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=====================================
+Goodix GXFP5130 Fingerprint Sensor
+=====================================
+
+Overview
+--------
+
+The GXFP5130 is a press-type fingerprint sensor by Goodix Technology used in
+several Huawei MateBook laptops. It connects to the host via an eSPI-based
+MMIO mailbox interface and is enumerated through ACPI (HID: ``GXFP5130``).
+
+This driver exposes a character device (``/dev/gxfp``) that userspace
+libraries such as libfprint use to perform fingerprint enrollment and
+verification through fprintd.
+
+Hardware interface
+------------------
+
+The sensor communicates through a memory-mapped mailbox window provided by
+the ACPI ``_CRS`` resource. Signalling uses three GPIOs:
+
+- **write-done**: host pulses high after writing a command frame
+- **read-done**: host pulses high after reading each chunk of a response
+- **irq**: sensor asserts to indicate a response is available
+
+All frames use a 4-byte eSPI wrapper header followed by a Goodix MP protocol
+frame, which in turn wraps a Goodix command/response body.
+
+UAPI
+----
+
+The character device supports the following operations:
+
+``open(2)``
+ Requires ``CAP_SYS_ADMIN``. Only one reader is allowed at a time.
+
+``write(2)``
+ Write a ``struct gxfp_tx_pkt_hdr`` followed by the payload bytes.
+ The driver wraps this in an eSPI frame and sends it to the sensor.
+
+``read(2)``
+ Returns one response record at a time. Each record begins with a
+ ``struct gxfp_tap_hdr`` header followed by the response payload.
+ Blocks until a response is available (unless ``O_NONBLOCK``).
+
+``poll(2)``
+ Returns ``EPOLLIN`` when a response record is available.
+
+``ioctl(2)``
+ ``GXFP_IOCTL_FLUSH_RXQ`` — discard all queued response records.
+
+Kernel parameters
+-----------------
+
+None. All configuration is automatic from ACPI.
+
+sysfs attributes
+----------------
+
+``/sys/class/misc/gxfp/acpi_id`` (read-only)
+ ACPI HID of the enumerated device (e.g. ``GXFP5130``).
+
+debugfs
+-------
+
+When ``CONFIG_DEBUG_FS`` is enabled, the driver creates
+``/sys/kernel/debug/gxfp/``:
+
+``trace_dump`` (read-only)
+ Ring buffer of recent driver trace events, one per line.
+ Format: ``<seq> <ts_ns> <message>``
+
+``trace_clear`` (write-only)
+ Writing anything clears the trace ring buffer.
+
+``trace_enable`` (read-write)
+ ``0`` to disable tracing (default), ``1`` to enable.
+
+Supported hardware
+------------------
+
+=============================== ======= ============================
+Device ACPI ID Firmware seen
+=============================== ======= ============================
+Huawei MateBook D16 2024 GXFP5130 GF_GCC_EC_20067
+Huawei MateBook X Pro 2024 GXFP5130 GF_GCC_EC_20068
+Huawei MateBook 14 2024 GXFP5130 (under investigation)
+=============================== ======= ============================
diff --git a/Documentation/misc-devices/index.rst b/Documentation/misc-devices/index.rst
index f911edaec..17f78a355 100644
--- a/Documentation/misc-devices/index.rst
+++ b/Documentation/misc-devices/index.rst
@@ -10,6 +10,7 @@ fit into other categories.
.. toctree::
:caption: Table of contents
:maxdepth: 2
+ gxfp5130
ad525x_dpot
amd-sbi
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 4/4] MAINTAINERS: add entry for GXFP5130 fingerprint sensor driver
2026-07-18 8:14 [PATCH 0/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Metehan Günen
` (2 preceding siblings ...)
2026-07-18 8:14 ` [PATCH 3/4] Documentation/misc-devices: add gxfp5130.rst Metehan Günen
@ 2026-07-18 8:14 ` 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
4 siblings, 1 reply; 12+ messages in thread
From: Metehan Günen @ 2026-07-18 8:14 UTC (permalink / raw)
To: linux-kernel; +Cc: gregkh, linux-acpi, linux-gpio, Metehan Günen
Add maintainer entry for the new Goodix GXFP5130 driver covering
the driver source tree, UAPI header, and documentation.
Signed-off-by: Metehan Günen <metehangnen@gmail.com>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index a674e3652..7d18b5410 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11048,6 +11048,13 @@ S: Maintained
F: Documentation/devicetree/bindings/connector/gocontroll,moduline-module-slot.yaml
GOODIX TOUCHSCREEN
+GOODIX GXFP5130 FINGERPRINT SENSOR DRIVER
+M: Metehan Günen <metehangnen@gmail.com>
+L: linux-kernel@vger.kernel.org
+S: Maintained
+F: Documentation/misc-devices/gxfp5130.rst
+F: drivers/misc/gxfp5130/
+F: include/uapi/linux/gxfp_ioctl.h
M: Hans de Goede <hansg@kernel.org>
L: linux-input@vger.kernel.org
S: Maintained
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 0/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver
2026-07-18 8:14 [PATCH 0/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Metehan Günen
` (3 preceding siblings ...)
2026-07-18 8:14 ` [PATCH 4/4] MAINTAINERS: add entry for GXFP5130 fingerprint sensor driver Metehan Günen
@ 2026-07-18 9:05 ` Greg KH
4 siblings, 0 replies; 12+ messages in thread
From: Greg KH @ 2026-07-18 9:05 UTC (permalink / raw)
To: Metehan Günen; +Cc: linux-kernel, linux-acpi, linux-gpio
On Sat, Jul 18, 2026 at 11:14:27AM +0300, Metehan Günen wrote:
> This series adds a kernel driver for the Goodix GXFP5130 fingerprint
> sensor found in Huawei MateBook laptops (D16 2024, X Pro 2024, 14 2024).
>
> The sensor is enumerated through ACPI (HID: GXFP5130) and communicates
> with the host via an eSPI-based memory-mapped mailbox window plus three
> GPIOs. It does not use SPI or I2C bus drivers.
>
> A misc character device /dev/gxfp is exposed to userspace. The libfprint
> library (with a corresponding GXFP5130 plugin) uses this device to perform
> biometric enrollment and verification via fprintd.
Is that the "normal" user/kernel api for these types of sensors? I
thought we had a real api these days, and a vendor one was not generally
a good idea as you are just exposing raw hardware design to userspace.
> The four patches are:
> [1/4] UAPI header (include/uapi/linux/gxfp_ioctl.h)
> [2/4] Driver source tree (drivers/misc/gxfp5130/)
> [3/4] Documentation (Documentation/misc-devices/gxfp5130.rst)
> [4/4] MAINTAINERS entry
>
> Metehan Günen (4):
> include/uapi/linux: add gxfp_ioctl.h for GXFP5130 fingerprint sensor
> drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver
> Documentation/misc-devices: add gxfp5130.rst
> MAINTAINERS: add entry for GXFP5130 fingerprint sensor driver
>
> Documentation/misc-devices/gxfp5130.rst | 90 +++++++
> Documentation/misc-devices/index.rst | 1 +
> MAINTAINERS | 7 +
> 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 ++
> include/uapi/linux/gxfp_ioctl.h | 43 +++
> 51 files changed, 3188 insertions(+)
Why isn't this just one single .c file? 3k for a driver is nothing,
this is a mess of stuff that odds are, can be much much smaller if its
all in one single file, and one .h file for the uapi.
What caused this to be split up so crazily?
And no copyright notices at all? Are you sure about that?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 4/4] MAINTAINERS: add entry for GXFP5130 fingerprint sensor driver
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
0 siblings, 0 replies; 12+ messages in thread
From: Greg KH @ 2026-07-18 9:06 UTC (permalink / raw)
To: Metehan Günen; +Cc: linux-kernel, linux-acpi, linux-gpio
On Sat, Jul 18, 2026 at 11:14:31AM +0300, Metehan Günen wrote:
> Add maintainer entry for the new Goodix GXFP5130 driver covering
> the driver source tree, UAPI header, and documentation.
>
> Signed-off-by: Metehan Günen <metehangnen@gmail.com>
> ---
> MAINTAINERS | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a674e3652..7d18b5410 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11048,6 +11048,13 @@ S: Maintained
> F: Documentation/devicetree/bindings/connector/gocontroll,moduline-module-slot.yaml
>
> GOODIX TOUCHSCREEN
> +GOODIX GXFP5130 FINGERPRINT SENSOR DRIVER
> +M: Metehan Günen <metehangnen@gmail.com>
> +L: linux-kernel@vger.kernel.org
> +S: Maintained
> +F: Documentation/misc-devices/gxfp5130.rst
> +F: drivers/misc/gxfp5130/
> +F: include/uapi/linux/gxfp_ioctl.h
> M: Hans de Goede <hansg@kernel.org>
> L: linux-input@vger.kernel.org
> S: Maintained
I don't think this entry is doing what you think it is doing.
Was it created by a LLM?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/4] Documentation/misc-devices: add gxfp5130.rst
2026-07-18 8:14 ` [PATCH 3/4] Documentation/misc-devices: add gxfp5130.rst Metehan Günen
@ 2026-07-18 9:06 ` Greg KH
0 siblings, 0 replies; 12+ messages in thread
From: Greg KH @ 2026-07-18 9:06 UTC (permalink / raw)
To: Metehan Günen; +Cc: linux-kernel, linux-acpi, linux-gpio
On Sat, Jul 18, 2026 at 11:14:30AM +0300, Metehan Günen wrote:
> Document the Goodix GXFP5130 eSPI fingerprint sensor driver:
> hardware interface (MMIO mailbox + 3 GPIOs), userspace ABI
> (open/write/read/poll/ioctl), sysfs attribute, debugfs trace interface,
> and the table of supported laptop models.
sysfs documentation goes in Documentation/ABI please.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/4] include/uapi/linux: add gxfp_ioctl.h for GXFP5130 fingerprint sensor
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
1 sibling, 0 replies; 12+ messages in thread
From: Greg KH @ 2026-07-18 9:07 UTC (permalink / raw)
To: Metehan Günen; +Cc: linux-kernel, linux-acpi, linux-gpio
On Sat, Jul 18, 2026 at 11:14:28AM +0300, Metehan Günen wrote:
> Define the userspace ABI for the Goodix GXFP5130 eSPI fingerprint sensor
> driver:
>
> - struct gxfp_tap_hdr: header prepended to every read(2) record, carries
> payload length, MP protocol type and a ktime_get_ns() timestamp.
> - struct gxfp_tx_pkt_hdr: header that userspace prepends to write(2)
> payloads so the driver can wrap them in the eSPI frame format.
> - GXFP_IOCTL_FLUSH_RXQ: flush the kernel-side RX record queue.
>
> Signed-off-by: Metehan Günen <metehangnen@gmail.com>
> ---
> include/uapi/linux/gxfp_ioctl.h | 43 +++++++++++++++++++++++++++++++++
> 1 file changed, 43 insertions(+)
> create mode 100644 include/uapi/linux/gxfp_ioctl.h
>
> diff --git a/include/uapi/linux/gxfp_ioctl.h b/include/uapi/linux/gxfp_ioctl.h
> new file mode 100644
> index 000000000..c9c6c2d48
> --- /dev/null
> +++ b/include/uapi/linux/gxfp_ioctl.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __UAPI_GXFP_IOCTL_H
> +#define __UAPI_GXFP_IOCTL_H
> +
> +#include <linux/types.h>
> +#include <linux/ioctl.h>
> +
> +#define GXFP_IOCTL_MAGIC 'G'
> +
> +/*
> + * 4-byte MP header + payload must fit GXFP_TX_BUFFER_SIZE(512).
> + */
> +#define GXFP_IOCTL_TX_PAYLOAD_MAX 508u
> +
> +/* Maximum protocol payload bytes exported via read(2) records. */
> +#define GXFP_IOCTL_TAP_PAYLOAD_MAX (128u * 1024u)
> +
> +/*
> + * Stream tap (read/poll) record header.
> + * Followed by `len` bytes of protocol payload.
> + */
> +struct gxfp_tap_hdr {
> + __u32 len; /* protocol payload bytes */
> + __u32 type; /* MP type */
> + __u32 _rsvd0;
C variables do not start with _, this isn't rust :)
And are you checking this for 0?
> + __u64 ts_ns; /* ktime_get_ns() */
> + __u8 head16[16]; /* first 16 bytes of payload */
> +};
> +
> +/*
> + * write(2) packet header.
> + * Userspace writes: [gxfp_tx_pkt_hdr][payload bytes]
> + */
> +struct gxfp_tx_pkt_hdr {
> + __u8 mp_flags;
> + __u8 _pad0;
Are you checking this for 0?
> + __u16 payload_len;
> + __u32 flags;
No definition of what these flags are?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/4] include/uapi/linux: add gxfp_ioctl.h for GXFP5130 fingerprint sensor
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
1 sibling, 0 replies; 12+ messages in thread
From: Greg KH @ 2026-07-18 9:08 UTC (permalink / raw)
To: Metehan Günen; +Cc: linux-kernel, linux-acpi, linux-gpio
On Sat, Jul 18, 2026 at 11:14:28AM +0300, Metehan Günen wrote:
> Define the userspace ABI for the Goodix GXFP5130 eSPI fingerprint sensor
> driver:
>
> - struct gxfp_tap_hdr: header prepended to every read(2) record, carries
> payload length, MP protocol type and a ktime_get_ns() timestamp.
> - struct gxfp_tx_pkt_hdr: header that userspace prepends to write(2)
> payloads so the driver can wrap them in the eSPI frame format.
> - GXFP_IOCTL_FLUSH_RXQ: flush the kernel-side RX record queue.
>
> Signed-off-by: Metehan Günen <metehangnen@gmail.com>
> ---
> include/uapi/linux/gxfp_ioctl.h | 43 +++++++++++++++++++++++++++++++++
> 1 file changed, 43 insertions(+)
> create mode 100644 include/uapi/linux/gxfp_ioctl.h
>
> diff --git a/include/uapi/linux/gxfp_ioctl.h b/include/uapi/linux/gxfp_ioctl.h
> new file mode 100644
> index 000000000..c9c6c2d48
> --- /dev/null
> +++ b/include/uapi/linux/gxfp_ioctl.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __UAPI_GXFP_IOCTL_H
> +#define __UAPI_GXFP_IOCTL_H
> +
> +#include <linux/types.h>
> +#include <linux/ioctl.h>
> +
> +#define GXFP_IOCTL_MAGIC 'G'
Why do you get to use "G" here?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver
2026-07-18 8:14 ` [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Metehan Günen
@ 2026-07-18 9:09 ` Greg KH
2026-07-18 9:12 ` Greg KH
1 sibling, 0 replies; 12+ messages in thread
From: Greg KH @ 2026-07-18 9:09 UTC (permalink / raw)
To: Metehan Günen; +Cc: linux-kernel, linux-acpi, linux-gpio
On Sat, Jul 18, 2026 at 11:14:29AM +0300, Metehan Günen wrote:
> --- 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"
> +
Why add this extra line?
> 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/
Same here, why the extra line?
> + gxfp_trace_logf("cmd_timeout req=0x%02x expect=0x%02x tries=%u",
> + req->req_cmd, req->expect_cmd, req->tries ? req->tries : 1);
Please do not roll your own tracing functions, use the in-kernel ones
instead.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver
2026-07-18 8:14 ` [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Metehan Günen
2026-07-18 9:09 ` Greg KH
@ 2026-07-18 9:12 ` Greg KH
1 sibling, 0 replies; 12+ messages in thread
From: Greg KH @ 2026-07-18 9:12 UTC (permalink / raw)
To: Metehan Günen; +Cc: linux-kernel, linux-acpi, linux-gpio
On Sat, Jul 18, 2026 at 11:14:29AM +0300, Metehan Günen wrote:
> + gxfp_trace.dir = debugfs_create_dir("gxfp", NULL);
> + if (!IS_ERR_OR_NULL(gxfp_trace.dir)) {
This is not how you use debugfs.
Also, this implements a custom trace function, again, use the built-in
kernel functions for this.
This implies that the driver came from an external company that does not
have much kernel development experience, or a LLM wrote this. As there
is no copyright information on the code, which is it?
> +void gxfp_trace_exit(void)
> +{
> + if (!READ_ONCE(gxfp_trace.inited))
That's an abuse of READ_ONCE() :(
thanks,
greg k-h
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-07-18 9:13 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Metehan Günen
2026-07-18 9:09 ` 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
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.