From eae15ff3bcf50d098fbda757e4dbd28e35798820 Mon Sep 17 00:00:00 2001 From: Dorian Stoll 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 +#include +#include +#include + +#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 +#include + +#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 + +#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 +#include +#include +#include + +#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 "); +MODULE_AUTHOR("Maximilian Luz "); +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 +#include + +#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 +#include + +#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 + +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 + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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