Changes: - SAM: - Fix a bug preventing SSAM client devices to be registered on the Surface Book 3. - Introduce SSAM device domains. - Rename modules, drivers, and source directories. - Split-up main header file. - Update and improve Kconfig options and help texts. - Internal restructuring, cleanup, and documentation. - GPE: - Cleanup, fixes, and restructuring. Links: - SAM:4b3ee5ad71
- GPE:7c017f4d52
- kernel:6ec5f0ebb0
1411 lines
39 KiB
Diff
1411 lines
39 KiB
Diff
From eae15ff3bcf50d098fbda757e4dbd28e35798820 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 3/6] ipts
|
|
|
|
---
|
|
drivers/misc/Kconfig | 1 +
|
|
drivers/misc/Makefile | 1 +
|
|
drivers/misc/ipts/Kconfig | 13 ++
|
|
drivers/misc/ipts/Makefile | 11 ++
|
|
drivers/misc/ipts/context.h | 125 ++++++++++++++
|
|
drivers/misc/ipts/control.c | 63 ++++++++
|
|
drivers/misc/ipts/control.h | 17 ++
|
|
drivers/misc/ipts/init.c | 87 ++++++++++
|
|
drivers/misc/ipts/protocol.h | 236 +++++++++++++++++++++++++++
|
|
drivers/misc/ipts/receiver.c | 202 +++++++++++++++++++++++
|
|
drivers/misc/ipts/receiver.h | 10 ++
|
|
drivers/misc/ipts/resources.c | 133 +++++++++++++++
|
|
drivers/misc/ipts/resources.h | 11 ++
|
|
drivers/misc/ipts/uapi.c | 297 ++++++++++++++++++++++++++++++++++
|
|
drivers/misc/ipts/uapi.h | 11 ++
|
|
drivers/misc/mei/hw-me-regs.h | 3 +
|
|
drivers/misc/mei/pci-me.c | 3 +
|
|
17 files changed, 1224 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/init.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..7dce12245a4f
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/Kconfig
|
|
@@ -0,0 +1,13 @@
|
|
+# 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.
|
|
diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile
|
|
new file mode 100644
|
|
index 000000000000..a7232badd8b8
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/Makefile
|
|
@@ -0,0 +1,11 @@
|
|
+# 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 += init.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..d24fd6ac026b
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/context.h
|
|
@@ -0,0 +1,125 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+
|
|
+#ifndef _IPTS_CONTEXT_H_
|
|
+#define _IPTS_CONTEXT_H_
|
|
+
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/mei_cl_bus.h>
|
|
+#include <linux/miscdevice.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#include "protocol.h"
|
|
+
|
|
+/*
|
|
+ * enum ipts_host_states - States of the IPTS driver
|
|
+ *
|
|
+ * IPTS_HOST_STATUS_STOPPED:
|
|
+ *
|
|
+ * The driver was either shut down or encountered a fatal error, causing
|
|
+ * it to disable itself. In this state no messages from the ME will be read,
|
|
+ * and no data can be read by userspace.
|
|
+ *
|
|
+ * IPTS_HOST_STATUS_STARTING:
|
|
+ *
|
|
+ * The driver is currently going through the initialization sequence.
|
|
+ * ME messages will be read, but no data can be read by userspace.
|
|
+ *
|
|
+ * IPTS_HOST_STATUS_STARTED:
|
|
+ *
|
|
+ * The driver completely initialized the device and receives data from
|
|
+ * it. Userspace can now read data.
|
|
+ *
|
|
+ * IPTS_HOST_STATUS_RESTARTING:
|
|
+ *
|
|
+ * A sensor error triggered a restart. Restarting IPTS means to stash all
|
|
+ * current operations using QUIESCE_IO, and then rerun the initialization
|
|
+ * sequence after the command returned. Since the same command is also used
|
|
+ * during shutdown, this mode tells the response handler for QUIESCE_IO if
|
|
+ * it should start re-initialization.
|
|
+ */
|
|
+enum ipts_host_status {
|
|
+ IPTS_HOST_STATUS_STOPPED,
|
|
+ IPTS_HOST_STATUS_STARTING,
|
|
+ IPTS_HOST_STATUS_STARTED,
|
|
+ IPTS_HOST_STATUS_RESTARTING
|
|
+};
|
|
+
|
|
+/*
|
|
+ * struct ipts_buffer_info - Buffer for passing data between ME and host.
|
|
+ *
|
|
+ * @address: The virtual kernelspace address for the host to access the buffer.
|
|
+ * @dma_address: The physical address for the ME to access the buffer.
|
|
+ */
|
|
+struct ipts_buffer_info {
|
|
+ u8 *address;
|
|
+ dma_addr_t dma_address;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * struct ipts_uapi - Context for the userspace interface
|
|
+ *
|
|
+ * @device: The character device that IPTS data can be read from.
|
|
+ * @doorbell_thread: Polls the doorbell value and signals changes to userspace.
|
|
+ * @doorbell: The last transaction that was passed to userspace.
|
|
+ * @active: Whether a client has activated and locked the data stream.
|
|
+ */
|
|
+struct ipts_uapi {
|
|
+ struct miscdevice device;
|
|
+ struct task_struct *db_thread;
|
|
+
|
|
+ u32 doorbell;
|
|
+ bool active;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * struct ipts_context - Context for the IPTS driver
|
|
+ *
|
|
+ * @cldev: The MEI client device for IPTS.
|
|
+ * @dev: The Linux driver model device, used for logging.
|
|
+ * @device_info: Information about the device we are connected to.
|
|
+ *
|
|
+ * @status: Current state of the driver.
|
|
+ * @uapi: The context for the userspace interface.
|
|
+ *
|
|
+ * @data: The IPTS data buffers. They get filled with touch data that is
|
|
+ * forwarded to userspace and parsed into input events.
|
|
+ *
|
|
+ * @doorbell: An unsigned 32-bit integer that will be incremented after one
|
|
+ * data buffer has been filled up. Always corresponds to the data
|
|
+ * buffer that will be filled *next*.
|
|
+ *
|
|
+ * The following buffers are a leftover from when IPTS used binary firmware
|
|
+ * with GuC submission. They are not used by the host but they need to be
|
|
+ * allocated to ensure proper operation.
|
|
+ *
|
|
+ * @feedback: Buffers that contain payload data for the FEEDBACK command.
|
|
+ * The command works with an empty buffer, so these are not used.
|
|
+ *
|
|
+ * @workqueue: Buffer that was used to synchronize the ME with the firmware
|
|
+ * running on the GuC. Just like the GuC, this buffer is not
|
|
+ * used anymore.
|
|
+ *
|
|
+ * @host2me: A special channel for sending feedback that is not linked to one
|
|
+ * of the data buffers. It is identified by using IPTS_BUFFERS as
|
|
+ * the buffer index, instead of 0 < n < IPTS_BUFFERS. In theory it
|
|
+ * allows for advanced interaction with the sensor, but these
|
|
+ * usages were never used or documented by intel, therefor it
|
|
+ * cannot be used.
|
|
+ */
|
|
+struct ipts_context {
|
|
+ struct mei_cl_device *cldev;
|
|
+ struct device *dev;
|
|
+ struct ipts_device_info device_info;
|
|
+
|
|
+ enum ipts_host_status status;
|
|
+ struct ipts_uapi uapi;
|
|
+
|
|
+ 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..857bcf498752
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/control.c
|
|
@@ -0,0 +1,63 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+
|
|
+#include <linux/mei_cl_bus.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#include "context.h"
|
|
+#include "protocol.h"
|
|
+#include "resources.h"
|
|
+#include "uapi.h"
|
|
+
|
|
+int ipts_control_send(struct ipts_context *ipts,
|
|
+ u32 cmd, void *data, u32 size)
|
|
+{
|
|
+ int ret;
|
|
+ struct ipts_command msg;
|
|
+
|
|
+ msg.code = cmd;
|
|
+
|
|
+ // Copy message payload
|
|
+ if (data && size > 0)
|
|
+ memcpy(&msg.data, data, size);
|
|
+
|
|
+ ret = mei_cldev_send(ipts->cldev, (u8 *)&msg, sizeof(msg.code) + size);
|
|
+ if (ret >= 0)
|
|
+ return 0;
|
|
+
|
|
+ if (cmd == IPTS_CMD(FEEDBACK) && ret == -IPTS_ME_STATUS_NOT_READY)
|
|
+ return 0;
|
|
+
|
|
+ dev_err(ipts->dev, "MEI error while sending: 0x%X:%d\n", cmd, ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+int ipts_control_start(struct ipts_context *ipts)
|
|
+{
|
|
+ ipts->status = IPTS_HOST_STATUS_STARTING;
|
|
+ ipts_uapi_init(ipts);
|
|
+
|
|
+ return ipts_control_send(ipts, IPTS_CMD(NOTIFY_DEV_READY), NULL, 0);
|
|
+}
|
|
+
|
|
+void ipts_control_stop(struct ipts_context *ipts)
|
|
+{
|
|
+ ipts->status = IPTS_HOST_STATUS_STOPPED;
|
|
+
|
|
+ ipts_control_send(ipts, IPTS_CMD(QUIESCE_IO), NULL, 0);
|
|
+ ipts_control_send(ipts, IPTS_CMD(CLEAR_MEM_WINDOW), NULL, 0);
|
|
+
|
|
+ ipts_uapi_free(ipts);
|
|
+ ipts_resources_free(ipts);
|
|
+}
|
|
+
|
|
+int ipts_control_restart(struct ipts_context *ipts)
|
|
+{
|
|
+ if (ipts->status == IPTS_HOST_STATUS_RESTARTING)
|
|
+ return 0;
|
|
+
|
|
+ dev_info(ipts->dev, "Restarting IPTS\n");
|
|
+ ipts->status = IPTS_HOST_STATUS_RESTARTING;
|
|
+
|
|
+ return ipts_control_send(ipts, IPTS_CMD(QUIESCE_IO), NULL, 0);
|
|
+}
|
|
diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h
|
|
new file mode 100644
|
|
index 000000000000..718cde10dd2c
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/control.h
|
|
@@ -0,0 +1,17 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+
|
|
+#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 *data, u32 size);
|
|
+
|
|
+int ipts_control_start(struct ipts_context *ipts);
|
|
+void ipts_control_stop(struct ipts_context *ipts);
|
|
+int ipts_control_restart(struct ipts_context *ipts);
|
|
+
|
|
+#endif /* _IPTS_CONTROL_H_ */
|
|
diff --git a/drivers/misc/ipts/init.c b/drivers/misc/ipts/init.c
|
|
new file mode 100644
|
|
index 000000000000..c2f237feed11
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/init.c
|
|
@@ -0,0 +1,87 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+
|
|
+#include <linux/dma-mapping.h>
|
|
+#include <linux/mei_cl_bus.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mod_devicetable.h>
|
|
+
|
|
+#include "context.h"
|
|
+#include "control.h"
|
|
+#include "receiver.h"
|
|
+
|
|
+#define IPTS_MEI_UUID UUID_LE(0x3e8d0870, 0x271a, 0x4208, \
|
|
+ 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04)
|
|
+
|
|
+static int ipts_init_probe(struct mei_cl_device *cldev,
|
|
+ const struct mei_cl_device_id *id)
|
|
+{
|
|
+ int ret;
|
|
+ struct ipts_context *ipts = NULL;
|
|
+
|
|
+ dev_info(&cldev->dev, "Probing IPTS\n");
|
|
+
|
|
+ // Setup the DMA bit mask
|
|
+ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) {
|
|
+ dev_info(&cldev->dev, "IPTS using DMA_BIT_MASK(64)\n");
|
|
+ } else if (!dma_coerce_mask_and_coherent(&cldev->dev,
|
|
+ DMA_BIT_MASK(32))) {
|
|
+ dev_info(&cldev->dev, "IPTS using DMA_BIT_MASK(32)\n");
|
|
+ } else {
|
|
+ dev_err(&cldev->dev, "No suitable DMA for IPTS available\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ ret = mei_cldev_enable(cldev);
|
|
+ if (ret) {
|
|
+ dev_err(&cldev->dev, "Cannot enable IPTS\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ipts = devm_kzalloc(&cldev->dev,
|
|
+ sizeof(struct ipts_context), GFP_KERNEL);
|
|
+ if (!ipts) {
|
|
+ mei_cldev_disable(cldev);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ ipts->cldev = cldev;
|
|
+ ipts->dev = &cldev->dev;
|
|
+
|
|
+ mei_cldev_set_drvdata(cldev, ipts);
|
|
+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback);
|
|
+
|
|
+ ipts_control_start(ipts);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ipts_init_remove(struct mei_cl_device *cldev)
|
|
+{
|
|
+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev);
|
|
+
|
|
+ dev_info(&cldev->dev, "Removing IPTS\n");
|
|
+
|
|
+ ipts_control_stop(ipts);
|
|
+ mei_cldev_disable(cldev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct mei_cl_device_id ipts_device_id[] = {
|
|
+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(mei, ipts_device_id);
|
|
+
|
|
+static struct mei_cl_driver ipts_driver = {
|
|
+ .id_table = ipts_device_id,
|
|
+ .name = "ipts",
|
|
+ .probe = ipts_init_probe,
|
|
+ .remove = ipts_init_remove,
|
|
+};
|
|
+module_mei_cl_driver(ipts_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("IPTS touchscreen driver");
|
|
+MODULE_AUTHOR("Dorian Stoll <dorian.stoll@tmsp.io>");
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h
|
|
new file mode 100644
|
|
index 000000000000..c8b412899ec4
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/protocol.h
|
|
@@ -0,0 +1,236 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+
|
|
+#ifndef _IPTS_PROTOCOL_H_
|
|
+#define _IPTS_PROTOCOL_H_
|
|
+
|
|
+#include <linux/build_bug.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#define IPTS_WORKQUEUE_SIZE 8192
|
|
+#define IPTS_WORKQUEUE_ITEM_SIZE 16
|
|
+
|
|
+/*
|
|
+ * How many data / feedback buffers IPTS uses
|
|
+ */
|
|
+#define IPTS_BUFFERS 16
|
|
+
|
|
+/*
|
|
+ * Helpers to avoid writing boilerplate code.
|
|
+ * The response to a command code is always 0x8000000x, where x
|
|
+ * is the command code itself. Instead of writing two definitions,
|
|
+ * we use macros to calculate the value on the fly instead.
|
|
+ */
|
|
+#define IPTS_CMD(COMMAND) IPTS_EVT_##COMMAND
|
|
+#define IPTS_RSP(COMMAND) (IPTS_CMD(COMMAND) + 0x80000000)
|
|
+
|
|
+/*
|
|
+ * enum ipts_evt_code - Events that can be sent and received from the ME
|
|
+ *
|
|
+ * Events can describe either a command (sent from host to ME) or a
|
|
+ * response (sent from ME to host). These values should not be used
|
|
+ * directly, instead they should be wrapped with the appropreate
|
|
+ * IPTS_CMD / IPTS_RSP macro, to clearly document the wanted event type.
|
|
+ */
|
|
+enum ipts_evt_code {
|
|
+ IPTS_EVT_GET_DEVICE_INFO = 1,
|
|
+ IPTS_EVT_SET_MODE,
|
|
+ IPTS_EVT_SET_MEM_WINDOW,
|
|
+ IPTS_EVT_QUIESCE_IO,
|
|
+ IPTS_EVT_READY_FOR_DATA,
|
|
+ IPTS_EVT_FEEDBACK,
|
|
+ IPTS_EVT_CLEAR_MEM_WINDOW,
|
|
+ IPTS_EVT_NOTIFY_DEV_READY,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * enum ipts_me_status - Status codes returned in response to a command.
|
|
+ *
|
|
+ * These codes are returned by the ME to indicate whether a command was
|
|
+ * executed successfully.
|
|
+ *
|
|
+ * Some of these errors are less serious than others, and some need to be
|
|
+ * ignored to ensure proper operation. See also ipts_receiver_handle_error.
|
|
+ */
|
|
+enum ipts_me_status {
|
|
+ IPTS_ME_STATUS_SUCCESS = 0,
|
|
+ IPTS_ME_STATUS_INVALID_PARAMS,
|
|
+ IPTS_ME_STATUS_ACCESS_DENIED,
|
|
+ IPTS_ME_STATUS_CMD_SIZE_ERROR,
|
|
+ IPTS_ME_STATUS_NOT_READY,
|
|
+ IPTS_ME_STATUS_REQUEST_OUTSTANDING,
|
|
+ IPTS_ME_STATUS_NO_SENSOR_FOUND,
|
|
+ IPTS_ME_STATUS_OUT_OF_MEMORY,
|
|
+ IPTS_ME_STATUS_INTERNAL_ERROR,
|
|
+ IPTS_ME_STATUS_SENSOR_DISABLED,
|
|
+ IPTS_ME_STATUS_COMPAT_CHECK_FAIL,
|
|
+ IPTS_ME_STATUS_SENSOR_EXPECTED_RESET,
|
|
+ IPTS_ME_STATUS_SENSOR_UNEXPECTED_RESET,
|
|
+ IPTS_ME_STATUS_RESET_FAILED,
|
|
+ IPTS_ME_STATUS_TIMEOUT,
|
|
+ IPTS_ME_STATUS_TEST_MODE_FAIL,
|
|
+ IPTS_ME_STATUS_SENSOR_FAIL_FATAL,
|
|
+ IPTS_ME_STATUS_SENSOR_FAIL_NONFATAL,
|
|
+ IPTS_ME_STATUS_INVALID_DEVICE_CAPS,
|
|
+ IPTS_ME_STATUS_QUIESCE_IO_IN_PROGRESS,
|
|
+ IPTS_ME_STATUS_MAX
|
|
+};
|
|
+
|
|
+/*
|
|
+ * enum ipts_sensor_mode - The sensor mode for IPTS to use
|
|
+ *
|
|
+ * IPTS_SENSOR_MODE_SINGLETOUCH:
|
|
+ *
|
|
+ * Singletouch mode is a fallback mode that does not support the stylus
|
|
+ * or more than one touch input. The data is received as a HID report with
|
|
+ * report ID 64.
|
|
+ *
|
|
+ * IPTS_SENSOR_MODE_MULTITOUCH:
|
|
+ *
|
|
+ * Multitouch mode is the proper operation mode for IPTS. It will return
|
|
+ * stylus data, as well as touch data as a raw heatmap directly from
|
|
+ * the sensor. This data needs to be processed before it can be used
|
|
+ * for input devices.
|
|
+ *
|
|
+ * This driver only supports multitouch mode.
|
|
+ */
|
|
+enum ipts_sensor_mode {
|
|
+ IPTS_SENSOR_MODE_SINGLETOUCH = 0,
|
|
+ IPTS_SENSOR_MODE_MULTITOUCH,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * struct ipts_set_mode_cmd - Parameters for the SET_MODE command.
|
|
+ *
|
|
+ * @sensor_mode: The mode which the touch sensor should operate in
|
|
+ * (from enum ipts_sensor_mode).
|
|
+ *
|
|
+ * On newer generations of IPTS (surface gen7) this command will only accept
|
|
+ * IPTS_SENSOR_MODE_MULTITOUCH, and fail if anything else is sent.
|
|
+ */
|
|
+struct ipts_set_mode_cmd {
|
|
+ u32 sensor_mode;
|
|
+ u8 reserved[12];
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct ipts_set_mode_cmd) == 16);
|
|
+
|
|
+/*
|
|
+ * struct ipts_set_mem_window_cmd - Parameters for the SET_MEM_WINDOW command.
|
|
+ *
|
|
+ * This passes the physical addresses of buffers to the ME, which are
|
|
+ * the used to exchange data between host and ME.
|
|
+ *
|
|
+ * Some of these buffers are not used by the host. They are a leftover from
|
|
+ * when IPTS used binary firmware with GuC submission. They need to be
|
|
+ * allocated and passed, otherwise the command will not return successfully.
|
|
+ *
|
|
+ * For a description of the various buffers, please check out the ipts_context
|
|
+ * struct and it's documentation.
|
|
+ */
|
|
+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;
|
|
+
|
|
+static_assert(sizeof(struct ipts_set_mem_window_cmd) == 320);
|
|
+
|
|
+/*
|
|
+ * struct ipts_feedback_cmd - Parameters for the FEEDBACK command.
|
|
+ *
|
|
+ * This command is sent to indicate that the data in a buffer has been
|
|
+ * processed by the host, and that the ME can safely overwrite the data.
|
|
+ *
|
|
+ * @buffer: The buffer to be refilled
|
|
+ */
|
|
+struct ipts_feedback_cmd {
|
|
+ u32 buffer;
|
|
+ u8 reserved[12];
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct ipts_feedback_cmd) == 16);
|
|
+
|
|
+/*
|
|
+ * struct ipts_command - Describes a command sent from the host to the ME.
|
|
+ *
|
|
+ * @code: The command code. (IPTS_CMD(EVENT))
|
|
+ * @set_mode: The parameters for the SET_MODE command
|
|
+ * @set_mem_window: The parameters for the SET_MEM_WINDOW command
|
|
+ * @feedback: The parameters for the FEEDBACK command.
|
|
+ *
|
|
+ * This struct should always be initialized with 0, to prevent the ME
|
|
+ * from interpreting random bytes as a parameter.
|
|
+ *
|
|
+ * The ME will react to a command by sending a response, indicating if
|
|
+ * the command was successfully, and returning queried data.
|
|
+ */
|
|
+struct ipts_command {
|
|
+ u32 code;
|
|
+ union {
|
|
+ struct ipts_set_mode_cmd set_mode;
|
|
+ struct ipts_set_mem_window_cmd set_mem_window;
|
|
+ struct ipts_feedback_cmd feedback;
|
|
+ } data;
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct ipts_command) == 324);
|
|
+
|
|
+/*
|
|
+ * struct ipts_device_info - Returned by the GET_DEVICE_INFO command.
|
|
+ *
|
|
+ * @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
|
|
+ * @max_touch_points: The amount of concurrent touches supported by the sensor
|
|
+ */
|
|
+struct ipts_device_info {
|
|
+ u16 vendor_id;
|
|
+ u16 device_id;
|
|
+ u32 hw_rev;
|
|
+ u32 fw_rev;
|
|
+ u32 data_size;
|
|
+ u32 feedback_size;
|
|
+ u8 reserved1[4];
|
|
+ u8 max_touch_points;
|
|
+ u8 reserved[19];
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct ipts_device_info) == 44);
|
|
+
|
|
+/*
|
|
+ * struct ipts_response - Describes the response from the ME to a command.
|
|
+ *
|
|
+ * @code: The response code. (0x80000000 + command code that was sent)
|
|
+ * @status: The return value of the command. (from enum ipts_me_status)
|
|
+ * @device_info: The data that was queried by the GET_DEVICE_INFO command.
|
|
+ *
|
|
+ * Theoretically all commands could return data but only the data from
|
|
+ * GET_DEVICE_INFO is relevant for the host.
|
|
+ */
|
|
+struct ipts_response {
|
|
+ u32 code;
|
|
+ u32 status;
|
|
+ union {
|
|
+ struct ipts_device_info device_info;
|
|
+ u8 reserved[80];
|
|
+ } data;
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct ipts_response) == 88);
|
|
+
|
|
+#endif /* _IPTS_PROTOCOL_H_ */
|
|
diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c
|
|
new file mode 100644
|
|
index 000000000000..bf78b64249a5
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/receiver.c
|
|
@@ -0,0 +1,202 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+
|
|
+#include <linux/mei_cl_bus.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#include "context.h"
|
|
+#include "control.h"
|
|
+#include "protocol.h"
|
|
+#include "resources.h"
|
|
+#include "uapi.h"
|
|
+
|
|
+static int ipts_receiver_handle_notify_dev_ready(struct ipts_context *ipts)
|
|
+{
|
|
+ return ipts_control_send(ipts, IPTS_CMD(GET_DEVICE_INFO), NULL, 0);
|
|
+}
|
|
+
|
|
+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts,
|
|
+ struct ipts_response *msg)
|
|
+{
|
|
+ memcpy(&ipts->device_info, &msg->data.device_info,
|
|
+ sizeof(struct ipts_device_info));
|
|
+
|
|
+ dev_info(ipts->dev, "Device %04hX:%04hX found\n",
|
|
+ ipts->device_info.vendor_id,
|
|
+ ipts->device_info.device_id);
|
|
+
|
|
+ return ipts_control_send(ipts, IPTS_CMD(CLEAR_MEM_WINDOW), NULL, 0);
|
|
+}
|
|
+
|
|
+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts)
|
|
+{
|
|
+ struct ipts_set_mode_cmd sensor_mode_cmd;
|
|
+
|
|
+ memset(&sensor_mode_cmd, 0, sizeof(struct ipts_set_mode_cmd));
|
|
+ sensor_mode_cmd.sensor_mode = IPTS_SENSOR_MODE_MULTITOUCH;
|
|
+
|
|
+ return ipts_control_send(ipts, IPTS_CMD(SET_MODE),
|
|
+ &sensor_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_init(ipts);
|
|
+ if (ret)
|
|
+ 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.host2me_size = ipts->device_info.data_size;
|
|
+
|
|
+ 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)
|
|
+{
|
|
+ ipts->status = IPTS_HOST_STATUS_STARTED;
|
|
+ dev_info(ipts->dev, "IPTS enabled\n");
|
|
+
|
|
+ return ipts_control_send(ipts, IPTS_CMD(READY_FOR_DATA), NULL, 0);
|
|
+}
|
|
+
|
|
+static int ipts_receiver_handle_quiesce_io(struct ipts_context *ipts)
|
|
+{
|
|
+ if (ipts->status != IPTS_HOST_STATUS_RESTARTING)
|
|
+ return 0;
|
|
+
|
|
+ ipts_uapi_free(ipts);
|
|
+ ipts_resources_free(ipts);
|
|
+
|
|
+ return ipts_control_start(ipts);
|
|
+}
|
|
+
|
|
+static bool ipts_receiver_handle_error(struct ipts_context *ipts,
|
|
+ struct ipts_response *msg)
|
|
+{
|
|
+ bool error;
|
|
+ bool restart;
|
|
+
|
|
+ switch (msg->status) {
|
|
+ case IPTS_ME_STATUS_SUCCESS:
|
|
+ case IPTS_ME_STATUS_COMPAT_CHECK_FAIL:
|
|
+ error = false;
|
|
+ restart = false;
|
|
+ break;
|
|
+ case IPTS_ME_STATUS_INVALID_PARAMS:
|
|
+ error = msg->code != IPTS_RSP(FEEDBACK);
|
|
+ restart = false;
|
|
+ break;
|
|
+ case IPTS_ME_STATUS_SENSOR_DISABLED:
|
|
+ error = msg->code != IPTS_RSP(READY_FOR_DATA);
|
|
+ restart = false;
|
|
+ break;
|
|
+ case IPTS_ME_STATUS_TIMEOUT:
|
|
+ error = msg->code != IPTS_RSP(CLEAR_MEM_WINDOW);
|
|
+ restart = false;
|
|
+ break;
|
|
+ case IPTS_ME_STATUS_SENSOR_EXPECTED_RESET:
|
|
+ case IPTS_ME_STATUS_SENSOR_UNEXPECTED_RESET:
|
|
+ error = true;
|
|
+ restart = true;
|
|
+ break;
|
|
+ default:
|
|
+ error = true;
|
|
+ restart = false;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!error)
|
|
+ return false;
|
|
+
|
|
+ dev_err(ipts->dev, "0x%08x failed: %d\n", msg->code, msg->status);
|
|
+
|
|
+ if (restart) {
|
|
+ dev_err(ipts->dev, "Sensor reset: %d\n", msg->status);
|
|
+ ipts_control_restart(ipts);
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static void ipts_receiver_handle_response(struct ipts_context *ipts,
|
|
+ struct ipts_response *msg)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ if (ipts_receiver_handle_error(ipts, msg))
|
|
+ return;
|
|
+
|
|
+ switch (msg->code) {
|
|
+ case IPTS_RSP(NOTIFY_DEV_READY):
|
|
+ ret = ipts_receiver_handle_notify_dev_ready(ipts);
|
|
+ break;
|
|
+ case IPTS_RSP(GET_DEVICE_INFO):
|
|
+ ret = ipts_receiver_handle_get_device_info(ipts, msg);
|
|
+ break;
|
|
+ case IPTS_RSP(CLEAR_MEM_WINDOW):
|
|
+ ret = ipts_receiver_handle_clear_mem_window(ipts);
|
|
+ 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(QUIESCE_IO):
|
|
+ ret = ipts_receiver_handle_quiesce_io(ipts);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!ret)
|
|
+ return;
|
|
+
|
|
+ dev_err(ipts->dev, "Detected MEI bus error\n");
|
|
+ dev_err(ipts->dev, "Stopping IPTS\n");
|
|
+
|
|
+ ipts->status = IPTS_HOST_STATUS_STOPPED;
|
|
+}
|
|
+
|
|
+void ipts_receiver_callback(struct mei_cl_device *cldev)
|
|
+{
|
|
+ struct ipts_response msg;
|
|
+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev);
|
|
+
|
|
+ if (mei_cldev_recv(ipts->cldev, (u8 *)&msg, sizeof(msg)) <= 0) {
|
|
+ dev_err(ipts->dev, "Error while reading MEI message\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (ipts->status == IPTS_HOST_STATUS_STOPPED)
|
|
+ return;
|
|
+
|
|
+ ipts_receiver_handle_response(ipts, &msg);
|
|
+}
|
|
diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h
|
|
new file mode 100644
|
|
index 000000000000..d7939ddbaae9
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/receiver.h
|
|
@@ -0,0 +1,10 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+
|
|
+#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..9f2b60bb7a70
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/resources.c
|
|
@@ -0,0 +1,133 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+
|
|
+#include <linux/dma-mapping.h>
|
|
+
|
|
+#include "context.h"
|
|
+
|
|
+void ipts_resources_free(struct ipts_context *ipts)
|
|
+{
|
|
+ int i;
|
|
+ u32 touch_buffer_size;
|
|
+ u32 feedback_buffer_size;
|
|
+ struct ipts_buffer_info *buffers;
|
|
+
|
|
+ touch_buffer_size = ipts->device_info.data_size;
|
|
+ feedback_buffer_size = ipts->device_info.feedback_size;
|
|
+
|
|
+ buffers = ipts->data;
|
|
+ for (i = 0; i < IPTS_BUFFERS; i++) {
|
|
+ if (!buffers[i].address)
|
|
+ continue;
|
|
+
|
|
+ dmam_free_coherent(ipts->dev, touch_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;
|
|
+
|
|
+ dmam_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) {
|
|
+ dmam_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) {
|
|
+ dmam_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) {
|
|
+ dmam_free_coherent(ipts->dev, touch_buffer_size,
|
|
+ ipts->host2me.address,
|
|
+ ipts->host2me.dma_address);
|
|
+
|
|
+ ipts->host2me.address = NULL;
|
|
+ ipts->host2me.dma_address = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+int ipts_resources_init(struct ipts_context *ipts)
|
|
+{
|
|
+ int i;
|
|
+ u32 touch_buffer_size;
|
|
+ u32 feedback_buffer_size;
|
|
+ struct ipts_buffer_info *buffers;
|
|
+
|
|
+ touch_buffer_size = ipts->device_info.data_size;
|
|
+ feedback_buffer_size = ipts->device_info.feedback_size;
|
|
+
|
|
+ buffers = ipts->data;
|
|
+ for (i = 0; i < IPTS_BUFFERS; i++) {
|
|
+ buffers[i].address = dmam_alloc_coherent(ipts->dev,
|
|
+ touch_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 = dmam_alloc_coherent(ipts->dev,
|
|
+ feedback_buffer_size,
|
|
+ &buffers[i].dma_address,
|
|
+ GFP_KERNEL);
|
|
+
|
|
+ if (!buffers[i].address)
|
|
+ goto release_resources;
|
|
+ }
|
|
+
|
|
+ ipts->doorbell.address = dmam_alloc_coherent(ipts->dev,
|
|
+ sizeof(u32),
|
|
+ &ipts->doorbell.dma_address,
|
|
+ GFP_KERNEL);
|
|
+
|
|
+ if (!ipts->doorbell.address)
|
|
+ goto release_resources;
|
|
+
|
|
+ ipts->workqueue.address = dmam_alloc_coherent(ipts->dev,
|
|
+ sizeof(u32),
|
|
+ &ipts->workqueue.dma_address,
|
|
+ GFP_KERNEL);
|
|
+
|
|
+ if (!ipts->workqueue.address)
|
|
+ goto release_resources;
|
|
+
|
|
+ ipts->host2me.address = dmam_alloc_coherent(ipts->dev,
|
|
+ touch_buffer_size,
|
|
+ &ipts->host2me.dma_address,
|
|
+ GFP_KERNEL);
|
|
+
|
|
+ if (!ipts->workqueue.address)
|
|
+ goto release_resources;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+release_resources:
|
|
+
|
|
+ dev_err(ipts->dev, "Failed to allocate buffers\n");
|
|
+ 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..cf9807b0dbe6
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/resources.h
|
|
@@ -0,0 +1,11 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+
|
|
+#ifndef _IPTS_RESOURCES_H_
|
|
+#define _IPTS_RESOURCES_H_
|
|
+
|
|
+#include "context.h"
|
|
+
|
|
+int ipts_resources_init(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..f6f7b2cabd83
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/uapi.c
|
|
@@ -0,0 +1,297 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+
|
|
+#include <linux/delay.h>
|
|
+#include <linux/ioctl.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/ktime.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/uaccess.h>
|
|
+
|
|
+#include "context.h"
|
|
+#include "control.h"
|
|
+#include "protocol.h"
|
|
+
|
|
+/*
|
|
+ * struct ipts_info - Information about an IPTS device
|
|
+ *
|
|
+ * @vendor: Vendor ID of the touch sensor
|
|
+ * @product: Device ID of the touch sensor
|
|
+ * @version: Revision of the touch sensor firmware
|
|
+ * @buffer_size: The maximum size of one touch data payload
|
|
+ * @max_touch_points: The amount of concurrent touches supported by the sensor
|
|
+ */
|
|
+struct ipts_info {
|
|
+ __u16 vendor;
|
|
+ __u16 product;
|
|
+ __u32 version;
|
|
+ __u32 buffer_size;
|
|
+ __u8 max_touch_points;
|
|
+
|
|
+ /* For future expansion */
|
|
+ __u8 reserved[19];
|
|
+};
|
|
+
|
|
+#define IPTS_UAPI_INFO _IOR(0x86, 0x01, struct ipts_info)
|
|
+#define IPTS_UAPI_START _IO(0x86, 0x02)
|
|
+#define IPTS_UAPI_STOP _IO(0x86, 0x03)
|
|
+
|
|
+/*
|
|
+ * struct ipts_uapi_client - A userspace client that has opened the device.
|
|
+ *
|
|
+ * @ipts: The IPTS driver context, to access the doorbell and data buffers.
|
|
+ * @offset: How much of the current data buffer has been read by the client.
|
|
+ * @active: Whether this client is the active one. Because the data from the
|
|
+ * hardware is not buffered, and sending feedback is linked to
|
|
+ * reading it from userspace, there can only be one active client.
|
|
+ * All other clients can access the device info, but will only
|
|
+ * read 0 from the device.
|
|
+ */
|
|
+struct ipts_uapi_client {
|
|
+ struct ipts_context *ipts;
|
|
+ u32 offset;
|
|
+ bool active;
|
|
+};
|
|
+
|
|
+DECLARE_WAIT_QUEUE_HEAD(ipts_uapi_wq);
|
|
+
|
|
+static int ipts_uapi_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct ipts_uapi_client *client;
|
|
+
|
|
+ struct ipts_uapi *uapi = container_of(file->private_data,
|
|
+ struct ipts_uapi, device);
|
|
+ struct ipts_context *ipts = container_of(uapi,
|
|
+ struct ipts_context, uapi);
|
|
+
|
|
+ if (ipts->status != IPTS_HOST_STATUS_STARTED)
|
|
+ return -ENODEV;
|
|
+
|
|
+ client = kzalloc(sizeof(struct ipts_uapi_client), GFP_KERNEL);
|
|
+ if (!client)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ client->ipts = ipts;
|
|
+
|
|
+ file->private_data = client;
|
|
+ nonseekable_open(inode, file);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ipts_uapi_close(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct ipts_uapi_client *client = file->private_data;
|
|
+ struct ipts_context *ipts = client->ipts;
|
|
+
|
|
+ if (client->active)
|
|
+ ipts->uapi.active = false;
|
|
+
|
|
+ kfree(client);
|
|
+ file->private_data = NULL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t ipts_uapi_read(struct file *file, char __user *buffer,
|
|
+ size_t count, loff_t *offs)
|
|
+{
|
|
+ u32 available;
|
|
+ u32 to_read;
|
|
+
|
|
+ char *data;
|
|
+ u8 buffer_id;
|
|
+
|
|
+ int ret;
|
|
+ struct ipts_feedback_cmd cmd;
|
|
+
|
|
+ struct ipts_uapi_client *client = file->private_data;
|
|
+ struct ipts_context *ipts = client->ipts;
|
|
+ u32 *doorbell = (u32 *)ipts->doorbell.address;
|
|
+
|
|
+ if (ipts->status != IPTS_HOST_STATUS_STARTED)
|
|
+ return 0;
|
|
+
|
|
+ if (!client->active)
|
|
+ return 0;
|
|
+
|
|
+ available = ipts->device_info.data_size - client->offset;
|
|
+ to_read = available < count ? available : count;
|
|
+
|
|
+ if (ipts->uapi.doorbell == *doorbell)
|
|
+ return 0;
|
|
+
|
|
+ buffer_id = ipts->uapi.doorbell % IPTS_BUFFERS;
|
|
+ data = ipts->data[buffer_id].address;
|
|
+
|
|
+ if (copy_to_user(buffer, data + client->offset, to_read))
|
|
+ return -EFAULT;
|
|
+
|
|
+ client->offset += to_read;
|
|
+ if (client->offset < ipts->device_info.data_size)
|
|
+ return to_read;
|
|
+
|
|
+ client->offset = 0;
|
|
+ ipts->uapi.doorbell++;
|
|
+
|
|
+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd));
|
|
+ cmd.buffer = buffer_id;
|
|
+
|
|
+ ret = ipts_control_send(ipts, IPTS_CMD(FEEDBACK),
|
|
+ &cmd, sizeof(struct ipts_feedback_cmd));
|
|
+
|
|
+ if (ret)
|
|
+ return -EFAULT;
|
|
+
|
|
+ return to_read;
|
|
+}
|
|
+
|
|
+static __poll_t ipts_uapi_poll(struct file *file, struct poll_table_struct *pt)
|
|
+{
|
|
+ struct ipts_uapi_client *client = file->private_data;
|
|
+ struct ipts_context *ipts = client->ipts;
|
|
+ u32 *doorbell = (u32 *)ipts->doorbell.address;
|
|
+
|
|
+ poll_wait(file, &ipts_uapi_wq, pt);
|
|
+
|
|
+ if (ipts->status != IPTS_HOST_STATUS_STARTED)
|
|
+ return EPOLLHUP | EPOLLERR;
|
|
+
|
|
+ if (ipts->uapi.doorbell != *doorbell)
|
|
+ return EPOLLIN | EPOLLRDNORM;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long ipts_uapi_ioctl_info(struct ipts_uapi_client *client,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ int ret;
|
|
+ struct ipts_info info;
|
|
+
|
|
+ void __user *buffer = (void __user *)arg;
|
|
+ struct ipts_context *ipts = client->ipts;
|
|
+
|
|
+ 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_touch_points = ipts->device_info.max_touch_points;
|
|
+
|
|
+ ret = copy_to_user(buffer, &info, sizeof(info));
|
|
+ if (ret)
|
|
+ return -EFAULT;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long ipts_uapi_ioctl_start(struct ipts_uapi_client *client)
|
|
+{
|
|
+ struct ipts_context *ipts = client->ipts;
|
|
+
|
|
+ if (ipts->uapi.active || client->active)
|
|
+ return -EFAULT;
|
|
+
|
|
+ ipts->uapi.active = true;
|
|
+ client->active = true;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long ipts_uapi_ioctl_stop(struct ipts_uapi_client *client)
|
|
+{
|
|
+ struct ipts_context *ipts = client->ipts;
|
|
+
|
|
+ if (!ipts->uapi.active || !client->active)
|
|
+ return -EFAULT;
|
|
+
|
|
+ ipts->uapi.active = false;
|
|
+ client->active = false;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ struct ipts_uapi_client *client = file->private_data;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case IPTS_UAPI_INFO:
|
|
+ return ipts_uapi_ioctl_info(client, arg);
|
|
+ case IPTS_UAPI_START:
|
|
+ return ipts_uapi_ioctl_start(client);
|
|
+ case IPTS_UAPI_STOP:
|
|
+ return ipts_uapi_ioctl_stop(client);
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+int ipts_uapi_doorbell_loop(void *data)
|
|
+{
|
|
+ u32 doorbell;
|
|
+ time64_t timeout, seconds;
|
|
+ struct ipts_context *ipts;
|
|
+
|
|
+ timeout = ktime_get_seconds() + 5;
|
|
+ ipts = (struct ipts_context *)data;
|
|
+
|
|
+ while (!kthread_should_stop()) {
|
|
+ if (ipts->status != IPTS_HOST_STATUS_STARTED) {
|
|
+ msleep(1000);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ seconds = ktime_get_seconds();
|
|
+
|
|
+ /*
|
|
+ * IPTS will increment the doorbell after if filled up one of
|
|
+ * the data buffers. If the doorbell didn't change, there is
|
|
+ * no work for us to do. Otherwise, the value of the doorbell
|
|
+ * will stand for the *next* buffer thats going to be filled.
|
|
+ */
|
|
+ doorbell = *(u32 *)ipts->doorbell.address;
|
|
+ if (doorbell != ipts->uapi.doorbell) {
|
|
+ wake_up_interruptible(&ipts_uapi_wq);
|
|
+ timeout = seconds + 5;
|
|
+ }
|
|
+
|
|
+ if (timeout > seconds)
|
|
+ usleep_range(5000, 15000);
|
|
+ else
|
|
+ msleep(200);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct file_operations ipts_uapi_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = ipts_uapi_open,
|
|
+ .release = ipts_uapi_close,
|
|
+ .read = ipts_uapi_read,
|
|
+ .poll = ipts_uapi_poll,
|
|
+ .unlocked_ioctl = ipts_uapi_ioctl,
|
|
+ .llseek = no_llseek,
|
|
+};
|
|
+
|
|
+int ipts_uapi_init(struct ipts_context *ipts)
|
|
+{
|
|
+ ipts->uapi.device.name = "ipts";
|
|
+ ipts->uapi.device.minor = MISC_DYNAMIC_MINOR;
|
|
+ ipts->uapi.device.fops = &ipts_uapi_fops;
|
|
+
|
|
+ ipts->uapi.db_thread = kthread_run(ipts_uapi_doorbell_loop,
|
|
+ (void *)ipts, "ipts_uapi_doorbell_loop");
|
|
+
|
|
+ return misc_register(&ipts->uapi.device);
|
|
+}
|
|
+
|
|
+void ipts_uapi_free(struct ipts_context *ipts)
|
|
+{
|
|
+ wake_up_interruptible(&ipts_uapi_wq);
|
|
+ misc_deregister(&ipts->uapi.device);
|
|
+ kthread_stop(ipts->uapi.db_thread);
|
|
+}
|
|
diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h
|
|
new file mode 100644
|
|
index 000000000000..7d7eabc74b17
|
|
--- /dev/null
|
|
+++ b/drivers/misc/ipts/uapi.h
|
|
@@ -0,0 +1,11 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+
|
|
+#ifndef _IPTS_UAPI_H_
|
|
+#define _IPTS_UAPI_H_
|
|
+
|
|
+#include "context.h"
|
|
+
|
|
+int ipts_uapi_init(struct ipts_context *ipts);
|
|
+void ipts_uapi_free(struct ipts_context *ipts);
|
|
+
|
|
+#endif /* _IPTS_UAPI_H_ */
|
|
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
|
|
|