linux-surface/patches/5.8/0003-ipts.patch
Maximilian Luz 7c0e669f67
Update v5.8 patches
Changes:
 - SAM:
   - Update DTX driver state after resume.
   - Add DTX Documentation, misc. fixes, and cleanup.

 - IPTS:
   This implements a new and refined UAPI interface that should improve
   stability during suspend and move some responsibility into userspace,
   making the driver simpler.

   It also fixes some sleep issues due to improper shutdown of the device.

   Shortlog:
     0a4a44c Add missing include
     31ae03d Improve error handling of ipts_control_* functions
     287dea0 Prevent lockups if stop is called from the receiver thread
     b737a9c On remove, wait until CLEAR_MEM_WINDOW returned.
     c5b66a5 Add GET_DEVICE_READY ioctl
     af0f84a Seperate UAPI initialization and device probing
     4ae7674 Patch the MEI bus to allow sending commands on remove
     27772bc Just a few refactorings...

Links:
- SAM: af4bb01042
- IPTS: 0a4a44c2a9
- kernel: 6e8bb10ad8
2020-10-22 18:27:17 +02:00

1449 lines
38 KiB
Diff

From 1fb79b576315c036f08e778546e71212230d9ddc Mon Sep 17 00:00:00 2001
From: Dorian Stoll <dorian.stoll@tmsp.io>
Date: Fri, 25 Sep 2020 18:06:05 +0200
Subject: [PATCH] mei: Remove client devices before shutting down
Patchset: ipts
---
drivers/misc/mei/init.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c
index bcee77768b91..21ed765003e1 100644
--- a/drivers/misc/mei/init.c
+++ b/drivers/misc/mei/init.c
@@ -302,10 +302,10 @@ void mei_stop(struct mei_device *dev)
{
dev_dbg(dev->dev, "stopping the device.\n");
+ mei_cl_bus_remove_devices(dev);
mutex_lock(&dev->device_lock);
mei_set_devstate(dev, MEI_DEV_POWER_DOWN);
mutex_unlock(&dev->device_lock);
- mei_cl_bus_remove_devices(dev);
mei_cancel_work(dev);
--
2.28.0
From b9b4c4f84f5111e3213225be406e43cc11623dbc Mon Sep 17 00:00:00 2001
From: Dorian Stoll <dorian.stoll@tmsp.io>
Date: Thu, 30 Jul 2020 13:21:53 +0200
Subject: [PATCH] misc: mei: Add missing IPTS device IDs
Patchset: ipts
---
drivers/misc/mei/hw-me-regs.h | 3 +++
drivers/misc/mei/pci-me.c | 3 +++
2 files changed, 6 insertions(+)
diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h
index 7becfc768bbc..0824ef27b08b 100644
--- a/drivers/misc/mei/hw-me-regs.h
+++ b/drivers/misc/mei/hw-me-regs.h
@@ -59,6 +59,7 @@
#define MEI_DEV_ID_SPT 0x9D3A /* Sunrise Point */
#define MEI_DEV_ID_SPT_2 0x9D3B /* Sunrise Point 2 */
+#define MEI_DEV_ID_SPT_3 0x9D3E /* Sunrise Point 3 (iTouch) */
#define MEI_DEV_ID_SPT_H 0xA13A /* Sunrise Point H */
#define MEI_DEV_ID_SPT_H_2 0xA13B /* Sunrise Point H 2 */
@@ -73,6 +74,7 @@
#define MEI_DEV_ID_KBP 0xA2BA /* Kaby Point */
#define MEI_DEV_ID_KBP_2 0xA2BB /* Kaby Point 2 */
+#define MEI_DEV_ID_KBP_3 0xA2BE /* Kaby Point 3 (iTouch) */
#define MEI_DEV_ID_CNP_LP 0x9DE0 /* Cannon Point LP */
#define MEI_DEV_ID_CNP_LP_3 0x9DE4 /* Cannon Point LP 3 (iTouch) */
@@ -90,6 +92,7 @@
#define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */
#define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */
+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */
#define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */
diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c
index 2a3f2fd5df50..319158fd4393 100644
--- a/drivers/misc/mei/pci-me.c
+++ b/drivers/misc/mei/pci-me.c
@@ -68,6 +68,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = {
{MEI_PCI_DEVICE(MEI_DEV_ID_SPT, MEI_ME_PCH8_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_SPT_2, MEI_ME_PCH8_CFG)},
+ {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_3, MEI_ME_PCH8_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H, MEI_ME_PCH8_SPS_4_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H_2, MEI_ME_PCH8_SPS_4_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_LBG, MEI_ME_PCH12_SPS_4_CFG)},
@@ -81,6 +82,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = {
{MEI_PCI_DEVICE(MEI_DEV_ID_KBP, MEI_ME_PCH8_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_KBP_2, MEI_ME_PCH8_CFG)},
+ {MEI_PCI_DEVICE(MEI_DEV_ID_KBP_3, MEI_ME_PCH8_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_CNP_LP, MEI_ME_PCH12_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_CNP_LP_3, MEI_ME_PCH8_CFG)},
@@ -94,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = {
{MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)},
+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)},
{MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)},
--
2.28.0
From e6812c7fe4d645c770c87071d6e30cb6b960c169 Mon Sep 17 00:00:00 2001
From: Dorian Stoll <dorian.stoll@tmsp.io>
Date: Thu, 6 Aug 2020 11:20:41 +0200
Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus
Based on https://github.com/linux-surface/intel-precise-touch/commit/0a4a44c2a9b676bd25d1cd916118dcfe3f447849
Signed-off-by: Dorian Stoll <dorian.stoll@tmsp.io>
Patchset: ipts
---
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/ipts/Kconfig | 17 ++
drivers/misc/ipts/Makefile | 12 ++
drivers/misc/ipts/context.h | 48 +++++
drivers/misc/ipts/control.c | 73 ++++++++
drivers/misc/ipts/control.h | 23 +++
drivers/misc/ipts/mei.c | 128 ++++++++++++++
drivers/misc/ipts/protocol.h | 319 ++++++++++++++++++++++++++++++++++
drivers/misc/ipts/receiver.c | 183 +++++++++++++++++++
drivers/misc/ipts/receiver.h | 17 ++
drivers/misc/ipts/resources.c | 134 ++++++++++++++
drivers/misc/ipts/resources.h | 18 ++
drivers/misc/ipts/uapi.c | 190 ++++++++++++++++++++
drivers/misc/ipts/uapi.h | 47 +++++
15 files changed, 1211 insertions(+)
create mode 100644 drivers/misc/ipts/Kconfig
create mode 100644 drivers/misc/ipts/Makefile
create mode 100644 drivers/misc/ipts/context.h
create mode 100644 drivers/misc/ipts/control.c
create mode 100644 drivers/misc/ipts/control.h
create mode 100644 drivers/misc/ipts/mei.c
create mode 100644 drivers/misc/ipts/protocol.h
create mode 100644 drivers/misc/ipts/receiver.c
create mode 100644 drivers/misc/ipts/receiver.h
create mode 100644 drivers/misc/ipts/resources.c
create mode 100644 drivers/misc/ipts/resources.h
create mode 100644 drivers/misc/ipts/uapi.c
create mode 100644 drivers/misc/ipts/uapi.h
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index e1b1ba5e2b92..be901ffc66fe 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -472,4 +472,5 @@ source "drivers/misc/ocxl/Kconfig"
source "drivers/misc/cardreader/Kconfig"
source "drivers/misc/habanalabs/Kconfig"
source "drivers/misc/uacce/Kconfig"
+source "drivers/misc/ipts/Kconfig"
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c7bd01ac6291..f97938d777e1 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -57,3 +57,4 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_HABANA_AI) += habanalabs/
obj-$(CONFIG_UACCE) += uacce/
obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o
+obj-$(CONFIG_MISC_IPTS) += ipts/
diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig
new file mode 100644
index 000000000000..83e2a930c396
--- /dev/null
+++ b/drivers/misc/ipts/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+config MISC_IPTS
+ tristate "Intel Precise Touch & Stylus"
+ depends on INTEL_MEI
+ help
+ Say Y here if your system has a touchscreen using Intels
+ Precise Touch & Stylus (IPTS) technology.
+
+ If unsure say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ipts.
+
+ Building this driver alone will not give you a working touchscreen.
+ It only exposed a userspace API that can be used by a daemon to
+ receive and process data from the touchscreen hardware.
diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile
new file mode 100644
index 000000000000..8f58b9adbc94
--- /dev/null
+++ b/drivers/misc/ipts/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for the IPTS touchscreen driver
+#
+
+obj-$(CONFIG_MISC_IPTS) += ipts.o
+ipts-objs := control.o
+ipts-objs += mei.o
+ipts-objs += receiver.o
+ipts-objs += resources.o
+ipts-objs += uapi.o
+
diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h
new file mode 100644
index 000000000000..6e8eba3a47e5
--- /dev/null
+++ b/drivers/misc/ipts/context.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#ifndef _IPTS_CONTEXT_H_
+#define _IPTS_CONTEXT_H_
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/mei_cl_bus.h>
+#include <linux/types.h>
+
+#include "protocol.h"
+
+enum ipts_host_status {
+ IPTS_HOST_STATUS_STARTING,
+ IPTS_HOST_STATUS_STARTED,
+ IPTS_HOST_STATUS_STOPPING,
+ IPTS_HOST_STATUS_STOPPED,
+};
+
+struct ipts_buffer_info {
+ u8 *address;
+ dma_addr_t dma_address;
+};
+
+struct ipts_context {
+ struct mei_cl_device *cldev;
+ struct device *dev;
+
+ bool restart;
+ enum ipts_host_status status;
+ struct ipts_get_device_info_rsp device_info;
+
+ struct ipts_buffer_info data[IPTS_BUFFERS];
+ struct ipts_buffer_info doorbell;
+
+ struct ipts_buffer_info feedback[IPTS_BUFFERS];
+ struct ipts_buffer_info workqueue;
+ struct ipts_buffer_info host2me;
+};
+
+#endif /* _IPTS_CONTEXT_H_ */
+
diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c
new file mode 100644
index 000000000000..98787d7ea292
--- /dev/null
+++ b/drivers/misc/ipts/control.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#include <linux/mei_cl_bus.h>
+
+#include "context.h"
+#include "protocol.h"
+#include "resources.h"
+#include "uapi.h"
+
+int ipts_control_send(struct ipts_context *ipts,
+ u32 code, void *payload, size_t size)
+{
+ int ret;
+ struct ipts_command cmd;
+
+ memset(&cmd, 0, sizeof(struct ipts_command));
+ cmd.code = code;
+
+ if (payload && size > 0)
+ memcpy(&cmd.payload, payload, size);
+
+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size);
+ if (ret >= 0 || ret == -EINTR)
+ return 0;
+
+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret);
+ return ret;
+}
+
+int ipts_control_start(struct ipts_context *ipts)
+{
+ if (ipts->status != IPTS_HOST_STATUS_STOPPED)
+ return -EBUSY;
+
+ dev_info(ipts->dev, "Starting IPTS\n");
+ ipts->status = IPTS_HOST_STATUS_STARTING;
+ ipts->restart = false;
+
+ ipts_uapi_link(ipts);
+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0);
+}
+
+int ipts_control_stop(struct ipts_context *ipts)
+{
+ if (ipts->status == IPTS_HOST_STATUS_STOPPING)
+ return -EBUSY;
+
+ if (ipts->status == IPTS_HOST_STATUS_STOPPED)
+ return -EBUSY;
+
+ dev_info(ipts->dev, "Stopping IPTS\n");
+ ipts->status = IPTS_HOST_STATUS_STOPPING;
+
+ ipts_uapi_unlink();
+ ipts_resources_free(ipts);
+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0);
+}
+
+int ipts_control_restart(struct ipts_context *ipts)
+{
+ if (ipts->restart)
+ return -EBUSY;
+
+ ipts->restart = true;
+ return ipts_control_stop(ipts);
+}
+
diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h
new file mode 100644
index 000000000000..2b3172c16063
--- /dev/null
+++ b/drivers/misc/ipts/control.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#ifndef _IPTS_CONTROL_H_
+#define _IPTS_CONTROL_H_
+
+#include <linux/types.h>
+
+#include "context.h"
+
+int ipts_control_send(struct ipts_context *ipts,
+ u32 cmd, void *payload, size_t size);
+int ipts_control_start(struct ipts_context *ipts);
+int ipts_control_restart(struct ipts_context *ipts);
+int ipts_control_stop(struct ipts_context *ipts);
+
+#endif /* _IPTS_CONTROL_H_ */
+
diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c
new file mode 100644
index 000000000000..b74e45c55b62
--- /dev/null
+++ b/drivers/misc/ipts/mei.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/mei_cl_bus.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+
+#include "context.h"
+#include "control.h"
+#include "protocol.h"
+#include "receiver.h"
+#include "uapi.h"
+
+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev)
+{
+ int ret;
+
+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64));
+ if (!ret)
+ return 0;
+
+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32));
+}
+
+static int ipts_mei_probe(struct mei_cl_device *cldev,
+ const struct mei_cl_device_id *id)
+{
+ int ret;
+ struct ipts_context *ipts;
+
+ if (ipts_mei_set_dma_mask(cldev)) {
+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n");
+ return -EFAULT;
+ }
+
+ ret = mei_cldev_enable(cldev);
+ if (ret) {
+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret);
+ return ret;
+ }
+
+ ipts = kzalloc(sizeof(struct ipts_context), GFP_KERNEL);
+ if (!ipts) {
+ mei_cldev_disable(cldev);
+ return -ENOMEM;
+ }
+
+ ipts->cldev = cldev;
+ ipts->dev = &cldev->dev;
+ ipts->status = IPTS_HOST_STATUS_STOPPED;
+
+ mei_cldev_set_drvdata(cldev, ipts);
+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback);
+
+ return ipts_control_start(ipts);
+}
+
+static int ipts_mei_remove(struct mei_cl_device *cldev)
+{
+ int i;
+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev);
+
+ ipts_control_stop(ipts);
+
+ for (i = 0; i < 20; i++) {
+ if (ipts->status == IPTS_HOST_STATUS_STOPPED)
+ break;
+
+ msleep(25);
+ }
+
+ mei_cldev_disable(cldev);
+ kfree(ipts);
+
+ return 0;
+}
+
+static struct mei_cl_device_id ipts_mei_device_id_table[] = {
+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY },
+ { },
+};
+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table);
+
+static struct mei_cl_driver ipts_mei_driver = {
+ .id_table = ipts_mei_device_id_table,
+ .name = "ipts",
+ .probe = ipts_mei_probe,
+ .remove = ipts_mei_remove,
+};
+
+static int __init ipts_mei_init(void)
+{
+ int ret;
+
+ ret = ipts_uapi_init();
+ if (ret)
+ return ret;
+
+ ret = mei_cldev_driver_register(&ipts_mei_driver);
+ if (ret) {
+ ipts_uapi_free();
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit ipts_mei_exit(void)
+{
+ mei_cldev_driver_unregister(&ipts_mei_driver);
+ ipts_uapi_free();
+}
+
+MODULE_DESCRIPTION("IPTS touchscreen driver");
+MODULE_AUTHOR("Dorian Stoll <dorian.stoll@tmsp.io>");
+MODULE_LICENSE("GPL");
+
+module_init(ipts_mei_init);
+module_exit(ipts_mei_exit);
+
diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h
new file mode 100644
index 000000000000..2e179cbb9af3
--- /dev/null
+++ b/drivers/misc/ipts/protocol.h
@@ -0,0 +1,319 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#ifndef _IPTS_PROTOCOL_H_
+#define _IPTS_PROTOCOL_H_
+
+#include <linux/types.h>
+
+/*
+ * The MEI client ID for IPTS functionality.
+ */
+#define IPTS_MEI_UUID UUID_LE(0x3e8d0870, 0x271a, 0x4208, \
+ 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04)
+
+/*
+ * Queries the device for vendor specific information.
+ *
+ * The command must not contain any payload.
+ * The response will contain struct ipts_get_device_info_rsp as payload.
+ */
+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001
+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001
+
+/*
+ * Sets the mode that IPTS will operate in.
+ *
+ * The command must contain struct ipts_set_mode_cmd as payload.
+ * The response will not contain any payload.
+ */
+#define IPTS_CMD_SET_MODE 0x00000002
+#define IPTS_RSP_SET_MODE 0x80000002
+
+/*
+ * Configures the memory buffers that the ME will use
+ * for passing data to the host.
+ *
+ * The command must contain struct ipts_set_mem_window_cmd as payload.
+ * The response will not contain any payload.
+ */
+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003
+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003
+
+/*
+ * Signals that the host is ready to receive data to the ME.
+ *
+ * The command must not contain any payload.
+ * The response will not contain any payload.
+ */
+#define IPTS_CMD_READY_FOR_DATA 0x00000005
+#define IPTS_RSP_READY_FOR_DATA 0x80000005
+
+/*
+ * Signals that a buffer can be refilled to the ME.
+ *
+ * The command must contain struct ipts_feedback_cmd as payload.
+ * The response will not contain any payload.
+ */
+#define IPTS_CMD_FEEDBACK 0x00000006
+#define IPTS_RSP_FEEDBACK 0x80000006
+
+/*
+ * Resets the data flow from the ME to the hosts and
+ * clears the buffers that were set with SET_MEM_WINDOW.
+ *
+ * The command must not contain any payload.
+ * The response will not contain any payload.
+ */
+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007
+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007
+
+/*
+ * Singletouch mode is a fallback that does not support
+ * a stylus or more than one touch input. The data is
+ * received as a HID report with report ID 64.
+ */
+#define IPTS_MODE_SINGLETOUCH 0x0
+
+/*
+ * Multitouch mode is the "proper" operation mode for IPTS. It will
+ * return stylus data as well as capacitive heatmap touch data.
+ * This data needs to be processed in userspace before it can be used.
+ */
+#define IPTS_MODE_MULTITOUCH 0x1
+
+/*
+ * Operation completed successfully.
+ */
+#define IPTS_STATUS_SUCCESS 0x0
+
+/*
+ * Command contained a payload with invalid parameters.
+ */
+#define IPTS_STATUS_INVALID_PARAMS 0x1
+
+/*
+ * ME was unable to validate buffer addresses supplied by the host.
+ */
+#define IPTS_STATUS_ACCESS_DENIED 0x2
+
+/*
+ * Command contained a payload with an invalid size.
+ */
+#define IPTS_STATUS_CMD_SIZE_ERROR 0x3
+
+/*
+ * Buffer addresses have not been set, or the
+ * device is not ready for operation yet.
+ */
+#define IPTS_STATUS_NOT_READY 0x4
+
+/*
+ * There is an outstanding command of the same type. The host must
+ * wait for a response before sending another command of the same type.
+ */
+#define IPTS_STATUS_REQUEST_OUTSTANDING 0x5
+
+/*
+ * No sensor could be found. Either no sensor is connected, it has not
+ * been initialized yet, or the system is improperly configured.
+ */
+#define IPTS_STATUS_NO_SENSOR_FOUND 0x6
+
+/*
+ * Not enough free memory for requested operation.
+ */
+#define IPTS_STATUS_OUT_OF_MEMORY 0x7
+
+/*
+ * An unexpected error occured.
+ */
+#define IPTS_STATUS_INTERNAL_ERROR 0x8
+
+/*
+ * The sensor has been disabled / reset and must be reinitialized.
+ */
+#define IPTS_STATUS_SENSOR_DISABLED 0x9
+
+/*
+ * Compatibility revision check between sensor and ME failed.
+ * The host can ignore this error and attempt to continue.
+ */
+#define IPTS_STATUS_COMPAT_CHECK_FAIL 0xA
+
+/*
+ * The sensor went through a reset initiated by the ME / the host.
+ */
+#define IPTS_STATUS_SENSOR_EXPECTED_RESET 0xB
+
+/*
+ * The sensor went through an unexpected reset.
+ */
+#define IPTS_STATUS_SENSOR_UNEXPECTED_RESET 0xC
+
+/*
+ * Requested sensor reset failed to complete.
+ */
+#define IPTS_STATUS_RESET_FAILED 0xD
+
+/*
+ * The operation timed out.
+ */
+#define IPTS_STATUS_TIMEOUT 0xE
+
+/*
+ * Test mode pattern did not match expected values.
+ */
+#define IPTS_STATUS_TEST_MODE_FAIL 0xF
+
+/*
+ * The sensor reported fatal error during reset sequence.
+ * Futher progress is not possible.
+ */
+#define IPTS_STATUS_SENSOR_FAIL_FATAL 0x10
+
+/*
+ * The sensor reported fatal error during reset sequence.
+ * The host can attempt to continue.
+ */
+#define IPTS_STATUS_SENSOR_FAIL_NONFATAL 0x11
+
+/*
+ * The sensor reported invalid capabilities.
+ */
+#define IPTS_STATUS_INVALID_DEVICE_CAPS 0x12
+
+/*
+ * The command cannot be completed until Quiesce IO flow has completed.
+ */
+#define IPTS_STATUS_QUIESCE_IO_IN_PROGRESS 0x13
+
+/*
+ * The amount of buffers that is used for IPTS
+ */
+#define IPTS_BUFFERS 16
+
+#define IPTS_WORKQUEUE_SIZE 8192
+#define IPTS_WORKQUEUE_ITEM_SIZE 16
+
+/**
+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command.
+ *
+ * @mode: The mode that IPTS should operate in. (IPTS_MODE_*)
+ *
+ * This driver only supports multitouch mode. Singletouch mode
+ * requires a different control flow that is not implemented.
+ */
+struct ipts_set_mode_cmd {
+ u32 mode;
+ u8 reserved[12];
+} __packed;
+
+/**
+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command.
+ *
+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses.
+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses.
+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address.
+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address.
+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address.
+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address.
+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses.
+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses.
+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address.
+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address.
+ * @workqueue_item_size: Constant value. (IPTS_WORKQUEUE_ITEM_SIZE)
+ * @workqueue_size: Constant value. (IPTS_WORKQUEUE_SIZE)
+ *
+ * The data buffers are buffers that get filled with touch data by the ME.
+ * The doorbell buffer is a u32 that gets incremented by the ME once a data
+ * buffer has been filled with new data.
+ *
+ * The other buffers are required for using GuC submission with binary
+ * firmware. Since support for GuC submission has been dropped from i915,
+ * they are not used anymore, but they need to be allocated to ensure proper
+ * operation.
+ */
+struct ipts_set_mem_window_cmd {
+ u32 data_buffer_addr_lower[IPTS_BUFFERS];
+ u32 data_buffer_addr_upper[IPTS_BUFFERS];
+ u32 workqueue_addr_lower;
+ u32 workqueue_addr_upper;
+ u32 doorbell_addr_lower;
+ u32 doorbell_addr_upper;
+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS];
+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS];
+ u32 host2me_addr_lower;
+ u32 host2me_addr_upper;
+ u32 host2me_size;
+ u8 reserved1;
+ u8 workqueue_item_size;
+ u16 workqueue_size;
+ u8 reserved[32];
+} __packed;
+
+/**
+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command.
+ *
+ * @buffer: The buffer that the ME should refill.
+ */
+struct ipts_feedback_cmd {
+ u32 buffer;
+ u8 reserved[12];
+} __packed;
+
+/**
+ * struct ipts_command - A message sent from the host to the ME.
+ *
+ * @code: The message code describing the command (IPTS_CMD_*)
+ * @payload: Payload for the command, or 0 if no payload is required.
+ */
+struct ipts_command {
+ u32 code;
+ u8 payload[320];
+} __packed;
+
+/**
+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response.
+ *
+ * @vendor_id: Vendor ID of the touch sensor.
+ * @device_id: Device ID of the touch sensor.
+ * @hw_rev: Hardware revision of the touch sensor.
+ * @fw_rev: Firmware revision of the touch sensor.
+ * @data_size: Required size of one data buffer.
+ * @feedback_size: Required size of one feedback buffer.
+ * @mode: Current operation mode of IPTS (IPTS_MODE_*)
+ * @max_contacts: The amount of concurrent touches supported by the sensor.
+ */
+struct ipts_get_device_info_rsp {
+ u16 vendor_id;
+ u16 device_id;
+ u32 hw_rev;
+ u32 fw_rev;
+ u32 data_size;
+ u32 feedback_size;
+ u32 mode;
+ u8 max_contacts;
+ u8 reserved[19];
+} __packed;
+
+/**
+ * struct ipts_response - A message sent from the ME to the host.
+ *
+ * @code: The message code describing the response (IPTS_RSP_*)
+ * @status: The status code returned by the command. (IPTS_STATUS_*)
+ * @payload: Payload returned by the command.
+ */
+struct ipts_response {
+ u32 code;
+ u32 status;
+ u8 payload[80];
+} __packed;
+
+#endif /* _IPTS_PROTOCOL_H_ */
+
diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c
new file mode 100644
index 000000000000..3660a1dcfff9
--- /dev/null
+++ b/drivers/misc/ipts/receiver.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#include <linux/mei_cl_bus.h>
+#include <linux/types.h>
+
+#include "context.h"
+#include "control.h"
+#include "protocol.h"
+#include "resources.h"
+
+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts,
+ struct ipts_response *rsp)
+{
+ struct ipts_set_mode_cmd cmd;
+
+ memcpy(&ipts->device_info, rsp->payload,
+ sizeof(struct ipts_get_device_info_rsp));
+
+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd));
+ cmd.mode = IPTS_MODE_MULTITOUCH;
+
+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE,
+ &cmd, sizeof(struct ipts_set_mode_cmd));
+}
+
+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts)
+{
+ int i, ret;
+ struct ipts_set_mem_window_cmd cmd;
+
+ ret = ipts_resources_alloc(ipts);
+ if (ret) {
+ dev_err(ipts->dev, "Failed to allocate resources\n");
+ return ret;
+ }
+
+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd));
+
+ for (i = 0; i < IPTS_BUFFERS; i++) {
+ cmd.data_buffer_addr_lower[i] =
+ lower_32_bits(ipts->data[i].dma_address);
+
+ cmd.data_buffer_addr_upper[i] =
+ upper_32_bits(ipts->data[i].dma_address);
+
+ cmd.feedback_buffer_addr_lower[i] =
+ lower_32_bits(ipts->feedback[i].dma_address);
+
+ cmd.feedback_buffer_addr_upper[i] =
+ upper_32_bits(ipts->feedback[i].dma_address);
+ }
+
+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address);
+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address);
+
+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address);
+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address);
+
+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address);
+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address);
+
+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE;
+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE;
+
+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW,
+ &cmd, sizeof(struct ipts_set_mem_window_cmd));
+}
+
+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts)
+{
+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n",
+ ipts->device_info.vendor_id,
+ ipts->device_info.device_id);
+ ipts->status = IPTS_HOST_STATUS_STARTED;
+
+ return ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0);
+}
+
+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts)
+{
+ if (ipts->restart)
+ return ipts_control_start(ipts);
+
+ ipts->status = IPTS_HOST_STATUS_STOPPED;
+ return 0;
+}
+
+static bool ipts_receiver_handle_error(struct ipts_context *ipts,
+ struct ipts_response *rsp)
+{
+ bool error;
+
+ switch (rsp->status) {
+ case IPTS_STATUS_SUCCESS:
+ case IPTS_STATUS_COMPAT_CHECK_FAIL:
+ error = false;
+ break;
+ case IPTS_STATUS_INVALID_PARAMS:
+ error = rsp->code != IPTS_RSP_FEEDBACK;
+ break;
+ case IPTS_STATUS_SENSOR_DISABLED:
+ error = ipts->status != IPTS_HOST_STATUS_STOPPING;
+ break;
+ default:
+ error = true;
+ break;
+ }
+
+ if (!error)
+ return false;
+
+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n",
+ rsp->code, rsp->status);
+
+ if (rsp->code == IPTS_STATUS_SENSOR_UNEXPECTED_RESET) {
+ dev_err(ipts->dev, "Sensor was reset\n");
+
+ if (ipts_control_restart(ipts))
+ dev_err(ipts->dev, "Failed to restart IPTS\n");
+ }
+
+ return true;
+}
+
+static void ipts_receiver_handle_response(struct ipts_context *ipts,
+ struct ipts_response *rsp)
+{
+ int ret;
+
+ if (ipts_receiver_handle_error(ipts, rsp))
+ return;
+
+ switch (rsp->code) {
+ case IPTS_RSP_GET_DEVICE_INFO:
+ ret = ipts_receiver_handle_get_device_info(ipts, rsp);
+ break;
+ case IPTS_RSP_SET_MODE:
+ ret = ipts_receiver_handle_set_mode(ipts);
+ break;
+ case IPTS_RSP_SET_MEM_WINDOW:
+ ret = ipts_receiver_handle_set_mem_window(ipts);
+ break;
+ case IPTS_RSP_CLEAR_MEM_WINDOW:
+ ret = ipts_receiver_handle_clear_mem_window(ipts);
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ if (!ret)
+ return;
+
+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n",
+ rsp->code, ret);
+
+ if (ipts_control_stop(ipts))
+ dev_err(ipts->dev, "Failed to stop IPTS\n");
+}
+
+void ipts_receiver_callback(struct mei_cl_device *cldev)
+{
+ int ret;
+ struct ipts_response rsp;
+ struct ipts_context *ipts;
+
+ ipts = mei_cldev_get_drvdata(cldev);
+
+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response));
+ if (ret <= 0) {
+ dev_err(ipts->dev, "Error while reading response: %d\n", ret);
+ return;
+ }
+
+ ipts_receiver_handle_response(ipts, &rsp);
+}
+
diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h
new file mode 100644
index 000000000000..c061d57a9320
--- /dev/null
+++ b/drivers/misc/ipts/receiver.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#ifndef _IPTS_RECEIVER_H_
+#define _IPTS_RECEIVER_H_
+
+#include <linux/mei_cl_bus.h>
+
+void ipts_receiver_callback(struct mei_cl_device *cldev);
+
+#endif /* _IPTS_RECEIVER_H_ */
+
diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c
new file mode 100644
index 000000000000..bcf4dd8d7e95
--- /dev/null
+++ b/drivers/misc/ipts/resources.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#include <linux/dma-mapping.h>
+
+#include "context.h"
+
+void ipts_resources_free(struct ipts_context *ipts)
+{
+ int i;
+ struct ipts_buffer_info *buffers;
+
+ u32 data_buffer_size = ipts->device_info.data_size;
+ u32 feedback_buffer_size = ipts->device_info.feedback_size;
+
+ buffers = ipts->data;
+ for (i = 0; i < IPTS_BUFFERS; i++) {
+ if (!buffers[i].address)
+ continue;
+
+ dma_free_coherent(ipts->dev, data_buffer_size,
+ buffers[i].address, buffers[i].dma_address);
+
+ buffers[i].address = NULL;
+ buffers[i].dma_address = 0;
+ }
+
+ buffers = ipts->feedback;
+ for (i = 0; i < IPTS_BUFFERS; i++) {
+ if (!buffers[i].address)
+ continue;
+
+ dma_free_coherent(ipts->dev, feedback_buffer_size,
+ buffers[i].address, buffers[i].dma_address);
+
+ buffers[i].address = NULL;
+ buffers[i].dma_address = 0;
+ }
+
+ if (ipts->doorbell.address) {
+ dma_free_coherent(ipts->dev, sizeof(u32),
+ ipts->doorbell.address,
+ ipts->doorbell.dma_address);
+
+ ipts->doorbell.address = NULL;
+ ipts->doorbell.dma_address = 0;
+ }
+
+ if (ipts->workqueue.address) {
+ dma_free_coherent(ipts->dev, sizeof(u32),
+ ipts->workqueue.address,
+ ipts->workqueue.dma_address);
+
+ ipts->workqueue.address = NULL;
+ ipts->workqueue.dma_address = 0;
+ }
+
+ if (ipts->host2me.address) {
+ dma_free_coherent(ipts->dev, feedback_buffer_size,
+ ipts->host2me.address,
+ ipts->host2me.dma_address);
+
+ ipts->host2me.address = NULL;
+ ipts->host2me.dma_address = 0;
+ }
+}
+
+int ipts_resources_alloc(struct ipts_context *ipts)
+{
+ int i;
+ struct ipts_buffer_info *buffers;
+
+ u32 data_buffer_size = ipts->device_info.data_size;
+ u32 feedback_buffer_size = ipts->device_info.feedback_size;
+
+ buffers = ipts->data;
+ for (i = 0; i < IPTS_BUFFERS; i++) {
+ buffers[i].address = dma_alloc_coherent(ipts->dev,
+ data_buffer_size,
+ &buffers[i].dma_address,
+ GFP_KERNEL);
+
+ if (!buffers[i].address)
+ goto release_resources;
+ }
+
+ buffers = ipts->feedback;
+ for (i = 0; i < IPTS_BUFFERS; i++) {
+ buffers[i].address = dma_alloc_coherent(ipts->dev,
+ feedback_buffer_size,
+ &buffers[i].dma_address,
+ GFP_KERNEL);
+
+ if (!buffers[i].address)
+ goto release_resources;
+ }
+
+ ipts->doorbell.address = dma_alloc_coherent(ipts->dev,
+ sizeof(u32),
+ &ipts->doorbell.dma_address,
+ GFP_KERNEL);
+
+ if (!ipts->doorbell.address)
+ goto release_resources;
+
+ ipts->workqueue.address = dma_alloc_coherent(ipts->dev,
+ sizeof(u32),
+ &ipts->workqueue.dma_address,
+ GFP_KERNEL);
+
+ if (!ipts->workqueue.address)
+ goto release_resources;
+
+ ipts->host2me.address = dma_alloc_coherent(ipts->dev,
+ feedback_buffer_size,
+ &ipts->host2me.dma_address,
+ GFP_KERNEL);
+
+ if (!ipts->workqueue.address)
+ goto release_resources;
+
+ return 0;
+
+release_resources:
+
+ ipts_resources_free(ipts);
+ return -ENOMEM;
+}
+
diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h
new file mode 100644
index 000000000000..8f55af7aae0f
--- /dev/null
+++ b/drivers/misc/ipts/resources.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#ifndef _IPTS_RESOURCES_H_
+#define _IPTS_RESOURCES_H_
+
+#include "context.h"
+
+int ipts_resources_alloc(struct ipts_context *ipts);
+void ipts_resources_free(struct ipts_context *ipts);
+
+#endif /* _IPTS_RESOURCES_H_ */
+
diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c
new file mode 100644
index 000000000000..1b59dbc9a1ad
--- /dev/null
+++ b/drivers/misc/ipts/uapi.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/types.h>
+
+#include "context.h"
+#include "control.h"
+#include "protocol.h"
+#include "uapi.h"
+
+struct ipts_uapi uapi;
+
+static ssize_t ipts_uapi_read(struct file *file, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ int buffer;
+ int maxbytes;
+ struct ipts_context *ipts = uapi.ipts;
+
+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev);
+
+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED)
+ return -ENODEV;
+
+ maxbytes = ipts->device_info.data_size - *offset;
+ if (maxbytes <= 0 || count > maxbytes)
+ return -EINVAL;
+
+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count))
+ return -EFAULT;
+
+ return count;
+}
+
+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts,
+ unsigned long arg)
+{
+ void __user *buffer = (void __user *)arg;
+ u8 ready = ipts->status == IPTS_HOST_STATUS_STARTED;
+
+ if (copy_to_user(buffer, &ready, sizeof(u8)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts,
+ unsigned long arg)
+{
+ struct ipts_device_info info;
+ void __user *buffer = (void __user *)arg;
+
+ if (ipts->status != IPTS_HOST_STATUS_STARTED)
+ return -ENODEV;
+
+ info.vendor = ipts->device_info.vendor_id;
+ info.product = ipts->device_info.device_id;
+ info.version = ipts->device_info.fw_rev;
+ info.buffer_size = ipts->device_info.data_size;
+ info.max_contacts = ipts->device_info.max_contacts;
+
+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts,
+ unsigned long arg)
+{
+ void __user *buffer = (void __user *)arg;
+
+ if (ipts->status != IPTS_HOST_STATUS_STARTED)
+ return -ENODEV;
+
+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts,
+ struct file *file)
+{
+ int ret;
+ struct ipts_feedback_cmd cmd;
+
+ if (ipts->status != IPTS_HOST_STATUS_STARTED)
+ return -ENODEV;
+
+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd));
+ cmd.buffer = MINOR(file->f_path.dentry->d_inode->i_rdev);
+
+ ret = ipts_control_send(ipts, IPTS_CMD_FEEDBACK,
+ &cmd, sizeof(struct ipts_feedback_cmd));
+
+ if (ret)
+ return -EFAULT;
+
+ return 0;
+}
+
+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct ipts_context *ipts = uapi.ipts;
+
+ if (!ipts)
+ return -ENODEV;
+
+ switch (cmd) {
+ case IPTS_IOCTL_GET_DEVICE_READY:
+ return ipts_uapi_ioctl_get_device_ready(ipts, arg);
+ case IPTS_IOCTL_GET_DEVICE_INFO:
+ return ipts_uapi_ioctl_get_device_info(ipts, arg);
+ case IPTS_IOCTL_GET_DOORBELL:
+ return ipts_uapi_ioctl_get_doorbell(ipts, arg);
+ case IPTS_IOCTL_SEND_FEEDBACK:
+ return ipts_uapi_ioctl_send_feedback(ipts, file);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations ipts_uapi_fops = {
+ .owner = THIS_MODULE,
+ .read = ipts_uapi_read,
+ .unlocked_ioctl = ipts_uapi_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = ipts_uapi_ioctl,
+#endif
+};
+
+void ipts_uapi_link(struct ipts_context *ipts)
+{
+ uapi.ipts = ipts;
+}
+
+void ipts_uapi_unlink(void)
+{
+ uapi.ipts = NULL;
+}
+
+int ipts_uapi_init(void)
+{
+ int i, major;
+
+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts");
+ uapi.class = class_create(THIS_MODULE, "ipts");
+
+ major = MAJOR(uapi.dev);
+
+ cdev_init(&uapi.cdev, &ipts_uapi_fops);
+ uapi.cdev.owner = THIS_MODULE;
+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS);
+
+ for (i = 0; i < IPTS_BUFFERS; i++) {
+ device_create(uapi.class, NULL,
+ MKDEV(major, i), NULL, "ipts/%d", i);
+ }
+
+ return 0;
+}
+
+void ipts_uapi_free(void)
+{
+ int i;
+ int major;
+
+ major = MAJOR(uapi.dev);
+
+ for (i = 0; i < IPTS_BUFFERS; i++)
+ device_destroy(uapi.class, MKDEV(major, i));
+
+ cdev_del(&uapi.cdev);
+
+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK);
+ class_destroy(uapi.class);
+}
+
diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h
new file mode 100644
index 000000000000..4c667bb6a7f2
--- /dev/null
+++ b/drivers/misc/ipts/uapi.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2016 Intel Corporation
+ * Copyright (c) 2020 Dorian Stoll
+ *
+ * Linux driver for Intel Precise Touch & Stylus
+ */
+
+#ifndef _IPTS_UAPI_H_
+#define _IPTS_UAPI_H_
+
+#include <linux/types.h>
+
+#include "context.h"
+
+struct ipts_uapi {
+ dev_t dev;
+ struct class *class;
+ struct cdev cdev;
+
+ struct ipts_context *ipts;
+};
+
+struct ipts_device_info {
+ __u16 vendor;
+ __u16 product;
+ __u32 version;
+ __u32 buffer_size;
+ __u8 max_contacts;
+
+ /* For future expansion */
+ __u8 reserved[19];
+};
+
+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8)
+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info)
+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32)
+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04)
+
+void ipts_uapi_link(struct ipts_context *ipts);
+void ipts_uapi_unlink(void);
+
+int ipts_uapi_init(void);
+void ipts_uapi_free(void);
+
+#endif /* _IPTS_UAPI_H_ */
+
--
2.28.0