diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 6ce92830b5d1f..e53ad2c4582fa 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1336,4 +1336,8 @@ source "drivers/hid/amd-sfh-hid/Kconfig" source "drivers/hid/surface-hid/Kconfig" +source "drivers/hid/ipts/Kconfig" + +source "drivers/hid/ithc/Kconfig" + endmenu diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index b0bef80981394..1181fc7b2a2ff 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -159,3 +159,6 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + +obj-$(CONFIG_HID_IPTS) += ipts/ +obj-$(CONFIG_HID_ITHC) += ithc/ diff --git a/drivers/misc/ipts/Kconfig b/drivers/hid/ipts/Kconfig similarity index 60% rename from drivers/misc/ipts/Kconfig rename to drivers/hid/ipts/Kconfig index 83e2a930c396c..297401bd388dd 100644 --- a/drivers/misc/ipts/Kconfig +++ b/drivers/hid/ipts/Kconfig @@ -1,8 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-or-later -config MISC_IPTS +config HID_IPTS tristate "Intel Precise Touch & Stylus" depends on INTEL_MEI + depends on HID help Say Y here if your system has a touchscreen using Intels Precise Touch & Stylus (IPTS) technology. @@ -11,7 +12,3 @@ config MISC_IPTS 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/hid/ipts/Makefile similarity index 62% rename from drivers/misc/ipts/Makefile rename to drivers/hid/ipts/Makefile index 8f58b9adbc94f..898b4a9e8237c 100644 --- a/drivers/misc/ipts/Makefile +++ b/drivers/hid/ipts/Makefile @@ -3,10 +3,10 @@ # Makefile for the IPTS touchscreen driver # -obj-$(CONFIG_MISC_IPTS) += ipts.o -ipts-objs := control.o +obj-$(CONFIG_HID_IPTS) += ipts.o +ipts-objs := cmd.o +ipts-objs += control.o +ipts-objs += hid.o ipts-objs += mei.o ipts-objs += receiver.o ipts-objs += resources.o -ipts-objs += uapi.o - diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c new file mode 100644 index 0000000000000..b9e59c98af852 --- /dev/null +++ b/drivers/hid/ipts/cmd.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include + +#include "context.h" +#include "spec-device.h" + +static int ipts_cmd_get_errno(struct ipts_response rsp, enum ipts_status expect) +{ + if (rsp.status == IPTS_STATUS_SUCCESS) + return 0; + + /* + * If a status code was expected, dont produce an error. + */ + if (rsp.status == expect) + return 0; + + /* + * Some devices will always return this error. It is allowed + * to ignore it and to try continuing. + */ + if (rsp.status == IPTS_STATUS_COMPAT_CHECK_FAIL) + return 0; + + /* + * Return something, this is not going to be checked. + * Any error will just cause the driver to stop. + */ + return -EINVAL; +} + +static int _ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, void *data, + size_t size, bool block, enum ipts_status expect) +{ + int ret; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (size > sizeof(rsp.payload)) + return -EINVAL; + + if (block) + ret = mei_cldev_recv(ipts->cldev, (u8 *)&rsp, sizeof(rsp)); + else + ret = mei_cldev_recv_nonblock(ipts->cldev, (u8 *)&rsp, sizeof(rsp)); + + if (ret == -EAGAIN) + return ret; + + if (ret <= 0) { + dev_err(ipts->dev, "Error while reading response: %d\n", ret); + return ret; + } + + /* + * In a response, the command code will have the most significant bit + * flipped to 1. We check for this and then set the bit to 0. + */ + if ((rsp.cmd & IPTS_RSP_BIT) == 0) { + dev_err(ipts->dev, "Invalid command code received: 0x%02X\n", rsp.cmd); + return -EINVAL; + } + + rsp.cmd = rsp.cmd & ~(IPTS_RSP_BIT); + if (rsp.cmd != code) { + dev_err(ipts->dev, "Received response to wrong command: 0x%02X\n", rsp.cmd); + return -EINVAL; + } + + ret = ipts_cmd_get_errno(rsp, expect); + if (ret) { + dev_err(ipts->dev, "Command 0x%02X failed: 0x%02X\n", rsp.cmd, rsp.status); + return ret; + } + + if (data && size > 0) + memcpy(data, rsp.payload, size); + + return 0; +} + +int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) +{ + int ret; + struct ipts_command cmd = { 0 }; + + if (!ipts) + return -EFAULT; + + cmd.cmd = code; + + if (data && size > 0) + memcpy(cmd.payload, data, size); + + ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.cmd) + size); + if (ret >= 0) + return 0; + + dev_err(ipts->dev, "Error while sending: 0x%02X:%d\n", code, ret); + return ret; +} + +int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) +{ + return _ipts_cmd_recv(ipts, code, data, size, true, IPTS_STATUS_SUCCESS); +} + +int ipts_cmd_recv_nonblock(struct ipts_context *ipts, enum ipts_command_code code, void *data, + size_t size) +{ + return _ipts_cmd_recv(ipts, code, data, size, false, IPTS_STATUS_SUCCESS); +} + +int ipts_cmd_recv_expect(struct ipts_context *ipts, enum ipts_command_code code, void *data, + size_t size, enum ipts_status expect) +{ + return _ipts_cmd_recv(ipts, code, data, size, true, expect); +} + +int ipts_cmd_run(struct ipts_context *ipts, enum ipts_command_code code, void *in, size_t insize, + void *out, size_t outsize) +{ + int ret; + + ret = ipts_cmd_send(ipts, code, in, insize); + if (ret) + return ret; + + ret = ipts_cmd_recv(ipts, code, out, outsize); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h new file mode 100644 index 0000000000000..578dc17b1c443 --- /dev/null +++ b/drivers/hid/ipts/cmd.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CMD_H +#define IPTS_CMD_H + +#include + +#include "context.h" +#include "spec-device.h" + +/* + * Executes the specified command with the given payload on the device. + */ +int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); + +/* + * Receives the response to the given command and copies the payload to the given buffer. + * This function will block until a message has been received. + */ +int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); + +/* + * Receives the response to the given command and copies the payload to the given buffer. + * This function will not block. If no data is available, -EAGAIN will be returned. + */ +int ipts_cmd_recv_nonblock(struct ipts_context *ipts, enum ipts_command_code code, void *data, + size_t size); + +/* + * Receives the response to the given command and copies the payload to the given buffer. + * This function will block until a message has been received. + * If the command finished with the expected status code, no error will be produced. + */ +int ipts_cmd_recv_expect(struct ipts_context *ipts, enum ipts_command_code code, void *data, + size_t size, enum ipts_status expect); + +/* + * Executes the specified command with the given payload on the device. Then + * receives the response to the command and copies the payload to the given buffer. + * This function will block until the command has been completed. + */ +int ipts_cmd_run(struct ipts_context *ipts, enum ipts_command_code code, void *in, size_t insize, + void *out, size_t outsize); + +#endif /* IPTS_CMD_H */ diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h new file mode 100644 index 0000000000000..bdcd3938650d8 --- /dev/null +++ b/drivers/hid/ipts/context.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CONTEXT_H +#define IPTS_CONTEXT_H + +#include +#include +#include +#include +#include +#include +#include + +#include "resources.h" +#include "spec-device.h" + +struct ipts_context { + struct device *dev; + struct mei_cl_device *cldev; + + enum ipts_mode mode; + + struct mutex feature_lock; + struct completion feature_event; + + /* + * These are not inside of struct ipts_resources + * because they don't own the memory they point to. + */ + struct ipts_buffer feature_report; + struct ipts_buffer descriptor; + + struct hid_device *hid; + struct ipts_device_info info; + struct ipts_resources resources; + + struct task_struct *event_loop; + struct task_struct *doorbell_loop; +}; + +#endif /* IPTS_CONTEXT_H */ diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c new file mode 100644 index 0000000000000..21e054cd42ef7 --- /dev/null +++ b/drivers/hid/ipts/control.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "context.h" +#include "control.h" +#include "desc.h" +#include "hid.h" +#include "receiver.h" +#include "resources.h" +#include "spec-data.h" +#include "spec-device.h" + +static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) +{ + if (!info) + return -EFAULT; + + return ipts_cmd_run(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0, info, sizeof(*info)); +} + +static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) +{ + struct ipts_set_mode cmd = { 0 }; + + cmd.mode = mode; + return ipts_cmd_run(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd), NULL, 0); +} + +static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) +{ + struct ipts_mem_window cmd = { 0 }; + + if (!res) + return -EFAULT; + + for (int i = 0; i < IPTS_BUFFERS; i++) { + cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); + cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); + cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); + cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); + } + + cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); + cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); + + cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); + cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); + + cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); + cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); + + cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; + cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; + + return ipts_cmd_run(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd), NULL, 0); +} + +static int ipts_control_reset_sensor(struct ipts_context *ipts) +{ + struct ipts_feedback_header *header; + + if (!ipts) + return -EFAULT; + + memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); + header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; + + header->cmd_type = IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET; + header->buffer = IPTS_HID2ME_BUFFER; + + return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); +} + +static int ipts_control_get_descriptor(struct ipts_context *ipts) +{ + int ret; + struct ipts_data_header *header; + struct ipts_get_descriptor cmd = { 0 }; + + if (!ipts) + return -EFAULT; + + memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); + + cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); + cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); + cmd.magic = 8; + + ret = ipts_cmd_run(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd), NULL, 0); + if (ret) + return ret; + + header = (struct ipts_data_header *)ipts->resources.descriptor.address; + + if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { + ipts->descriptor.address = &header->data[8]; + ipts->descriptor.size = header->size - 8; + + return 0; + } + + return -EINVAL; +} + +int ipts_control_request_flush(struct ipts_context *ipts) +{ + struct ipts_quiesce_io cmd = { 0 }; + + return ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); +} + +int ipts_control_wait_flush(struct ipts_context *ipts) +{ + return ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, NULL, 0); +} + +int ipts_control_request_data(struct ipts_context *ipts) +{ + return ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); +} + +int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) +{ + if (!shutdown) + return ipts_cmd_recv_nonblock(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); + + return ipts_cmd_recv_expect(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0, + IPTS_STATUS_SENSOR_DISABLED); +} + +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) +{ + int ret; + struct ipts_feedback cmd = { 0 }; + + cmd.buffer = buffer; + + ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); + if (ret) + return ret; + + return ipts_cmd_recv_expect(ipts, IPTS_CMD_FEEDBACK, NULL, 0, IPTS_STATUS_INVALID_PARAMS); +} + +int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) +{ + if (!ipts) + return -EFAULT; + + /* + * IPTS expects structured data in the feedback buffer matching the buffer that will be refilled. + * We don't know what that data looks like, so we just keep the buffer empty. + * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. + * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling + * the buffers on some devices. + */ + + return ipts_control_send_feedback(ipts, buffer); +} + +int ipts_control_start(struct ipts_context *ipts) +{ + int ret; + struct ipts_device_info info; + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "Starting IPTS\n"); + + ret = ipts_control_get_device_info(ipts, &info); + if (ret) { + dev_err(ipts->dev, "Failed to get device info: %d\n", ret); + return ret; + } + + ipts->info = info; + + ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); + if (ret) { + dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); + return ret; + } + + dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); + + /* + * Handle newer devices + */ + if (info.intf_eds > 1) { + /* + * Fetching the descriptor will only work on newer devices. + * For older devices, a fallback descriptor will be used. + */ + ret = ipts_control_get_descriptor(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); + return ret; + } + + /* + * Newer devices can be directly initialized in doorbell mode. + */ + ipts->mode = IPTS_MODE_DOORBELL; + } + + ret = ipts_control_set_mode(ipts, ipts->mode); + if (ret) { + dev_err(ipts->dev, "Failed to set mode: %d\n", ret); + return ret; + } + + ret = ipts_control_set_mem_window(ipts, &ipts->resources); + if (ret) { + dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); + return ret; + } + + ipts_receiver_start(ipts); + + ret = ipts_control_request_data(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request data: %d\n", ret); + return ret; + } + + ret = ipts_hid_init(ipts, info); + if (ret) { + dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); + return ret; + } + + return 0; +} + +static int _ipts_control_stop(struct ipts_context *ipts) +{ + int ret; + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "Stopping IPTS\n"); + ipts_receiver_stop(ipts); + + ret = ipts_control_reset_sensor(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to reset sensor: %d\n", ret); + return ret; + } + + ipts_resources_free(&ipts->resources); + return 0; +} + +int ipts_control_stop(struct ipts_context *ipts) +{ + int ret; + + ret = _ipts_control_stop(ipts); + if (ret) + return ret; + + ipts_hid_free(ipts); + + return 0; +} + +int ipts_control_restart(struct ipts_context *ipts) +{ + int ret; + + ret = _ipts_control_stop(ipts); + if (ret) + return ret; + + /* + * Give the sensor some time to come back from resetting + */ + msleep(1000); + + ret = ipts_control_start(ipts); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h new file mode 100644 index 0000000000000..3c85837d7d65d --- /dev/null +++ b/drivers/hid/ipts/control.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CONTROL_H +#define IPTS_CONTROL_H + +#include + +#include "context.h" +#include "spec-device.h" + +/* + * Sends a request to stop the data flow to the device. + * All outstanding data needs to be acknowledged using + * feedback before the request can be finalized. + */ +int ipts_control_request_flush(struct ipts_context *ipts); + +/* + * Waits until the flushing request has been finalized. + */ +int ipts_control_wait_flush(struct ipts_context *ipts); + +/* + * Notify the device that the driver can receive new data. + */ +int ipts_control_request_data(struct ipts_context *ipts); + +/* + * Wait until new data is available on the device. + * In doorbell mode, this function will never return while the data + * flow is active. Instead, the doorbell will be incremented when new + * data is available. + * + * If shutdown == true, the function will block, and errors that are + * produced by the device due to a disabled sensor will be ignored. + * + * If shutdown == false, the function will not block. If no data is + * available, -EAGAIN will be returned. + */ +int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown); + +/* + * Submits the given feedback buffer to the hardware. + */ +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); + +/* + * Acknowledges that the data in a buffer has been processed. + */ +int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer); + +/* + * Initializes the IPTS device and starts the data flow. + */ +int ipts_control_start(struct ipts_context *ipts); + +/* + * Stops the data flow and resets the device. + */ +int ipts_control_stop(struct ipts_context *ipts); + +/* + * Stops the device and immideately starts it again. + */ +int ipts_control_restart(struct ipts_context *ipts); + +#endif /* IPTS_CONTROL_H */ diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h new file mode 100644 index 0000000000000..5794a5e2f475b --- /dev/null +++ b/drivers/hid/ipts/desc.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_DESC_H +#define IPTS_DESC_H + +#include + +#define IPTS_HID_REPORT_SINGLETOUCH 64 +#define IPTS_HID_REPORT_DATA 65 +#define IPTS_HID_REPORT_SET_MODE 66 + +#define IPTS_HID_REPORT_DATA_SIZE 7485 + +/* + * HID descriptor for singletouch data. + * This descriptor should be present on all IPTS devices. + */ +static const u8 ipts_singletouch_descriptor[] = { + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x04, /* Usage (Touchscreen), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x40, /* Report ID (64), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x75, 0x10, /* Report Size (16), */ + 0x95, 0x01, /* Report Count (1), */ + 0xA4, /* Push, */ + 0x55, 0x0E, /* Unit Exponent (14), */ + 0x65, 0x11, /* Unit (Centimeter), */ + 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ + 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x31, /* Usage (Y), */ + 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ + 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ + 0x81, 0x02, /* Input (Variable), */ + 0xB4, /* Pop, */ + 0xC0, /* End Collection */ +}; + +/* + * Fallback HID descriptor for older devices that do not have + * the ability to query their HID descriptor. + */ +static const u8 ipts_fallback_descriptor[] = { + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x41, /* Report ID (65), */ + 0x09, 0x56, /* Usage (Scan Time), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x10, /* Report Size (16), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x61, /* Usage (Gesture Char Quality), */ + 0x75, 0x08, /* Report Size (8), */ + 0x96, 0x3D, 0x1D, /* Report Count (7485), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x85, 0x42, /* Report ID (66), */ + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ + 0x09, 0xC8, /* Usage (C8h), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x01, /* Report Count (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ +}; + +#endif /* IPTS_DESC_H */ diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c new file mode 100644 index 0000000000000..309fa8957f0b3 --- /dev/null +++ b/drivers/hid/ipts/hid.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "desc.h" +#include "hid.h" +#include "spec-data.h" +#include "spec-device.h" +#include "spec-hid.h" + +static int ipts_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void ipts_hid_stop(struct hid_device *hid) +{ +} + +static int ipts_hid_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_data_type type, + void *data, size_t size) +{ + struct ipts_feedback_header *header; + + if (!ipts) + return -EFAULT; + + memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); + header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; + + header->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; + header->buffer = IPTS_HID2ME_BUFFER; + header->data_type = type; + header->size = size; + + if (size + sizeof(*header) > ipts->resources.hid2me.size) + return -EINVAL; + + if (data && size > 0) + memcpy(header->payload, data, size); + + return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); +} + +static int ipts_hid_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) +{ + if (!ipts) + return -EFAULT; + + if (ipts->mode == mode) + return 0; + + /* + * This is only allowed on older devices. + */ + if (ipts->info.intf_eds > 1) + return 0; + + ipts->mode = mode; + return ipts_control_restart(ipts); +} + +static int ipts_hid_parse(struct hid_device *hid) +{ + int ret; + u8 *buffer; + size_t size; + struct ipts_context *ipts; + bool has_native_descriptor; + + if (!hid) + return -EFAULT; + + ipts = hid->driver_data; + + if (!ipts) + return -EFAULT; + + size = sizeof(ipts_singletouch_descriptor); + has_native_descriptor = ipts->descriptor.address && ipts->descriptor.size > 0; + + if (has_native_descriptor) + size += ipts->descriptor.size; + else + size += sizeof(ipts_fallback_descriptor); + + buffer = kzalloc(size, GFP_KERNEL); + memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); + + if (has_native_descriptor) { + memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, + ipts->descriptor.size); + } else { + memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, + sizeof(ipts_fallback_descriptor)); + } + + ret = hid_parse_report(hid, buffer, size); + kfree(buffer); + + if (ret) { + dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); + return ret; + } + + return 0; +} + +static int ipts_hid_get_feature(struct ipts_context *ipts, unsigned char reportnum, __u8 *buf, + size_t size, enum ipts_feedback_data_type type) +{ + int ret; + + mutex_lock(&ipts->feature_lock); + + memset(buf, 0, size); + buf[0] = reportnum; + + memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); + reinit_completion(&ipts->feature_event); + + ret = ipts_hid_hid2me_feedback(ipts, type, buf, size); + if (ret) { + dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); + goto out; + } + + ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); + if (ret == 0) { + dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); + ret = -EIO; + goto out; + } + + if (!ipts->feature_report.address) { + ret = -EFAULT; + goto out; + } + + if (ipts->feature_report.size > size) { + ret = -ETOOSMALL; + goto out; + } + + ret = ipts->feature_report.size; + memcpy(buf, ipts->feature_report.address, ipts->feature_report.size); + +out: + mutex_unlock(&ipts->feature_lock); + return ret; +} + +static int ipts_hid_set_feature(struct ipts_context *ipts, unsigned char reportnum, __u8 *buf, + size_t size, enum ipts_feedback_data_type type) +{ + int ret; + + buf[0] = reportnum; + + ret = ipts_hid_hid2me_feedback(ipts, type, buf, size); + if (ret) + dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); + + return ret; +} + +static int ipts_hid_raw_request(struct hid_device *hid, unsigned char reportnum, __u8 *buf, + size_t size, unsigned char rtype, int reqtype) +{ + int ret; + enum ipts_feedback_data_type type; + struct ipts_context *ipts; + + if (!hid) + return -EFAULT; + + ipts = hid->driver_data; + + if (!ipts) + return -EFAULT; + + if (!buf) + return -EFAULT; + + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) + type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; + else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) + type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; + else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) + type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; + else + return -EIO; + + // Implemente mode switching report for older devices without native HID support + if (type == IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES && reportnum == IPTS_HID_REPORT_SET_MODE) { + ret = ipts_hid_switch_mode(ipts, buf[1]); + if (ret) { + dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); + return ret; + } + } + + if (reqtype == HID_REQ_GET_REPORT) + return ipts_hid_get_feature(ipts, reportnum, buf, size, type); + else + return ipts_hid_set_feature(ipts, reportnum, buf, size, type); +} + +static int ipts_hid_output_report(struct hid_device *hid, __u8 *data, size_t size) +{ + struct ipts_context *ipts; + + if (!hid) + return -EFAULT; + + ipts = hid->driver_data; + + if (!ipts) + return -EFAULT; + + if (!data) + return -EFAULT; + + return ipts_hid_hid2me_feedback(ipts, IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT, data, size); +} + +static struct hid_ll_driver ipts_hid_driver = { + .start = ipts_hid_start, + .stop = ipts_hid_stop, + .open = ipts_hid_start, + .close = ipts_hid_stop, + .parse = ipts_hid_parse, + .raw_request = ipts_hid_raw_request, + .output_report = ipts_hid_output_report, +}; + +int ipts_hid_input_data(struct ipts_context *ipts, int buffer) +{ + int ret; + u8 *temp; + struct ipts_hid_header *frame; + struct ipts_data_header *header; + + if (!ipts) + return -EFAULT; + + if (!ipts->hid) + return -ENODEV; + + header = (struct ipts_data_header *)ipts->resources.data[buffer].address; + + if (header->size == 0) + return 0; + + if (header->type == IPTS_DATA_TYPE_HID) + return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); + + if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { + ipts->feature_report.address = header->data; + ipts->feature_report.size = header->size; + + complete_all(&ipts->feature_event); + return 0; + } + + if (header->type != IPTS_DATA_TYPE_FRAME) + return 0; + + if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) + return -ERANGE; + + temp = kzalloc(IPTS_HID_REPORT_DATA_SIZE, GFP_KERNEL); + if (!temp) + return -ENOMEM; + + /* + * Synthesize a HID report matching the devices that natively send HID reports + */ + temp[0] = IPTS_HID_REPORT_DATA; + + frame = (struct ipts_hid_header *)&temp[3]; + frame->type = IPTS_HID_FRAME_TYPE_RAW; + frame->size = header->size + sizeof(*frame); + + memcpy(frame->data, header->data, header->size); + + ret = hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); + kfree(temp); + + return ret; +} + +int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) +{ + int ret; + + if (!ipts) + return -EFAULT; + + if (ipts->hid) + return 0; + + ipts->hid = hid_allocate_device(); + if (IS_ERR(ipts->hid)) { + dev_err(ipts->dev, "Failed to allocate HID device: %ld\n", PTR_ERR(ipts->hid)); + return PTR_ERR(ipts->hid); + } + + ipts->hid->driver_data = ipts; + ipts->hid->dev.parent = ipts->dev; + ipts->hid->ll_driver = &ipts_hid_driver; + + ipts->hid->vendor = info.vendor; + ipts->hid->product = info.product; + ipts->hid->group = HID_GROUP_MULTITOUCH; + + snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, + info.product); + + ret = hid_add_device(ipts->hid); + if (ret) { + dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); + ipts_hid_free(ipts); + return ret; + } + + return 0; +} + +void ipts_hid_free(struct ipts_context *ipts) +{ + hid_destroy_device(ipts->hid); + ipts->hid = NULL; +} diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h new file mode 100644 index 0000000000000..7b23942e42136 --- /dev/null +++ b/drivers/hid/ipts/hid.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_HID_H +#define IPTS_HID_H + +#include + +#include "context.h" +#include "spec-device.h" + +int ipts_hid_input_data(struct ipts_context *ipts, int buffer); + +int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); +void ipts_hid_free(struct ipts_context *ipts); + +#endif /* IPTS_HID_H */ diff --git a/drivers/misc/ipts/mei.c b/drivers/hid/ipts/mei.c similarity index 56% rename from drivers/misc/ipts/mei.c rename to drivers/hid/ipts/mei.c index 59ecf13e00d22..cd75fc18d48c7 100644 --- a/drivers/misc/ipts/mei.c +++ b/drivers/hid/ipts/mei.c @@ -1,44 +1,51 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2016 Intel Corporation - * Copyright (c) 2020 Dorian Stoll + * Copyright (c) 2020-2022 Dorian Stoll * * Linux driver for Intel Precise Touch & Stylus */ +#include #include +#include #include #include #include #include +#include #include +#include +#include #include "context.h" #include "control.h" -#include "protocol.h" #include "receiver.h" -#include "uapi.h" +#include "spec-device.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) 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) + if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) 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) +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 = ipts_mei_set_dma_mask(cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); + return ret; } ret = mei_cldev_enable(cldev); @@ -47,7 +54,7 @@ static int ipts_mei_probe(struct mei_cl_device *cldev, return ret; } - ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); + ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); if (!ipts) { mei_cldev_disable(cldev); return -ENOMEM; @@ -55,34 +62,36 @@ static int ipts_mei_probe(struct mei_cl_device *cldev, ipts->cldev = cldev; ipts->dev = &cldev->dev; - ipts->status = IPTS_HOST_STATUS_STOPPED; + ipts->mode = IPTS_MODE_EVENT; + + mutex_init(&ipts->feature_lock); + init_completion(&ipts->feature_event); mei_cldev_set_drvdata(cldev, ipts); - mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); - return ipts_control_start(ipts); + ret = ipts_control_start(ipts); + if (ret) { + dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); + return ret; + } + + return 0; } static void ipts_mei_remove(struct mei_cl_device *cldev) { - int i; + int ret; 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); - } + ret = ipts_control_stop(ipts); + if (ret) + dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); mei_cldev_disable(cldev); - kfree(ipts); } static struct mei_cl_device_id ipts_mei_device_id_table[] = { - { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, + { .uuid = IPTS_MEI_UUID, .version = MEI_CL_VERSION_ANY }, {}, }; MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); @@ -93,33 +102,8 @@ static struct mei_cl_driver ipts_mei_driver = { .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_mei_cl_driver(ipts_mei_driver); MODULE_DESCRIPTION("IPTS touchscreen driver"); MODULE_AUTHOR("Dorian Stoll "); MODULE_LICENSE("GPL"); - -module_init(ipts_mei_init); -module_exit(ipts_mei_exit); diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c new file mode 100644 index 0000000000000..8dfa77345ac15 --- /dev/null +++ b/drivers/hid/ipts/receiver.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "context.h" +#include "control.h" +#include "hid.h" +#include "resources.h" +#include "spec-device.h" + +static void ipts_receiver_next_doorbell(struct ipts_context *ipts) +{ + u32 *doorbell = (u32 *)ipts->resources.doorbell.address; + *doorbell = *doorbell + 1; +} + +static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) +{ + u32 *doorbell = (u32 *)ipts->resources.doorbell.address; + return *doorbell; +} + +static void ipts_receiver_backoff(time64_t last, u32 n) +{ + /* + * If the last change was less than n seconds ago, + * sleep for a shorter period so that new data can be + * processed quickly. If there was no change for more than + * n seconds, sleep longer to avoid wasting CPU cycles. + */ + if (last + n > ktime_get_seconds()) + msleep(20); + else + msleep(200); +} + +static int ipts_receiver_event_loop(void *data) +{ + int ret; + u32 buffer; + + struct ipts_context *ipts = data; + time64_t last = ktime_get_seconds(); + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "IPTS running in event mode\n"); + + while (!kthread_should_stop()) { + for (int i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_control_wait_data(ipts, false); + if (ret == -EAGAIN) + break; + + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + continue; + } + + buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; + ipts_receiver_next_doorbell(ipts); + + ret = ipts_hid_input_data(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); + + ret = ipts_control_refill_buffer(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); + + ret = ipts_control_request_data(ipts); + if (ret) + dev_err(ipts->dev, "Failed to request data: %d\n", ret); + + last = ktime_get_seconds(); + } + + ipts_receiver_backoff(last, 5); + } + + ret = ipts_control_request_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request flush: %d\n", ret); + return ret; + } + + ret = ipts_control_wait_data(ipts, true); + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + return ret; + } + + ret = ipts_control_wait_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); + return ret; + } + + return 0; +} + +static int ipts_receiver_doorbell_loop(void *data) +{ + int ret; + u32 buffer; + + u32 doorbell = 0; + u32 lastdb = 0; + + struct ipts_context *ipts = data; + time64_t last = ktime_get_seconds(); + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "IPTS running in doorbell mode\n"); + + while (true) { + if (kthread_should_stop()) { + ret = ipts_control_request_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request flush: %d\n", ret); + return ret; + } + } + + doorbell = ipts_receiver_current_doorbell(ipts); + + /* + * After filling up one of the data buffers, IPTS will increment + * the doorbell. The value of the doorbell stands for the *next* + * buffer that IPTS is going to fill. + */ + while (lastdb != doorbell) { + buffer = lastdb % IPTS_BUFFERS; + + ret = ipts_hid_input_data(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); + + ret = ipts_control_refill_buffer(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); + + last = ktime_get_seconds(); + lastdb++; + } + + if (kthread_should_stop()) + break; + + ipts_receiver_backoff(last, 5); + } + + ret = ipts_control_wait_data(ipts, true); + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + return ret; + } + + ret = ipts_control_wait_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); + return ret; + } + + return 0; +} + +void ipts_receiver_start(struct ipts_context *ipts) +{ + if (!ipts) + return; + + if (ipts->mode == IPTS_MODE_EVENT) + ipts->event_loop = kthread_run(ipts_receiver_event_loop, ipts, "ipts_event"); + + if (ipts->mode == IPTS_MODE_DOORBELL) + ipts->doorbell_loop = kthread_run(ipts_receiver_doorbell_loop, ipts, "ipts_db"); +} + +void ipts_receiver_stop(struct ipts_context *ipts) +{ + if (!ipts) + return; + + if (ipts->event_loop) { + kthread_stop(ipts->event_loop); + ipts->event_loop = NULL; + } + + if (ipts->doorbell_loop) { + kthread_stop(ipts->doorbell_loop); + ipts->doorbell_loop = NULL; + } +} diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h new file mode 100644 index 0000000000000..d9c059c9265d9 --- /dev/null +++ b/drivers/hid/ipts/receiver.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_RECEIVER_H +#define IPTS_RECEIVER_H + +#include "context.h" + +void ipts_receiver_start(struct ipts_context *ipts); +void ipts_receiver_stop(struct ipts_context *ipts); + +#endif /* IPTS_RECEIVER_H */ diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c new file mode 100644 index 0000000000000..d549aede66257 --- /dev/null +++ b/drivers/hid/ipts/resources.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include + +#include "resources.h" +#include "spec-device.h" + +static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) +{ + if (!buffer) + return -EFAULT; + + if (buffer->address) + return 0; + + buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); + + if (!buffer->address) + return -ENOMEM; + + buffer->size = size; + buffer->device = dev; + + return 0; +} + +static void ipts_resources_free_buffer(struct ipts_buffer *buffer) +{ + if (!buffer || !buffer->address) + return; + + dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); + + buffer->address = NULL; + buffer->size = 0; + + buffer->dma_address = 0; + buffer->device = NULL; +} + +int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) +{ + int ret; + + if (!res) + return -EFAULT; + + for (int i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); + if (ret) + goto err; + } + + for (int i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); + if (ret) + goto err; + } + + ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); + if (ret) + goto err; + + return 0; + +err: + + ipts_resources_free(res); + return ret; +} + +void ipts_resources_free(struct ipts_resources *res) +{ + if (!res) + return; + + for (int i = 0; i < IPTS_BUFFERS; i++) + ipts_resources_free_buffer(&res->data[i]); + + for (int i = 0; i < IPTS_BUFFERS; i++) + ipts_resources_free_buffer(&res->feedback[i]); + + ipts_resources_free_buffer(&res->doorbell); + ipts_resources_free_buffer(&res->workqueue); + ipts_resources_free_buffer(&res->hid2me); + ipts_resources_free_buffer(&res->descriptor); +} diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h new file mode 100644 index 0000000000000..876c51089ece6 --- /dev/null +++ b/drivers/hid/ipts/resources.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_RESOURCES_H +#define IPTS_RESOURCES_H + +#include +#include + +#include "spec-device.h" + +struct ipts_buffer { + u8 *address; + size_t size; + + dma_addr_t dma_address; + struct device *device; +}; + +struct ipts_resources { + struct ipts_buffer data[IPTS_BUFFERS]; + struct ipts_buffer feedback[IPTS_BUFFERS]; + + struct ipts_buffer doorbell; + struct ipts_buffer workqueue; + struct ipts_buffer hid2me; + + struct ipts_buffer descriptor; +}; + +int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); +void ipts_resources_free(struct ipts_resources *res); + +#endif /* IPTS_RESOURCES_H */ diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h new file mode 100644 index 0000000000000..2119272a438ed --- /dev/null +++ b/drivers/hid/ipts/spec-data.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_DATA_H +#define IPTS_SPEC_DATA_H + +#include +#include + +/** + * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. + */ +enum ipts_feedback_cmd_type { + IPTS_FEEDBACK_CMD_TYPE_NONE = 0, + IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, + IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, + IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, + IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, + IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, + IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, +}; + +/** + * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. + * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. + * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. + * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. + * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. + * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. + */ +enum ipts_feedback_data_type { + IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, + IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, + IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, + IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, + IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, +}; + +/** + * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. + * @cmd_type: A command that should be executed on the sensor. + * @size: The size of the payload to be written. + * @buffer: The ID of the buffer that contains this feedback data. + * @protocol: The protocol version of the EDS. + * @data_type: The type of data that the buffer contains. + * @spi_offset: The offset at which to write the payload data to the sensor. + * @payload: Payload for the feedback command, or 0 if no payload is sent. + */ +struct ipts_feedback_header { + enum ipts_feedback_cmd_type cmd_type; + u32 size; + u32 buffer; + u32 protocol; + enum ipts_feedback_data_type data_type; + u32 spi_offset; + u8 reserved[40]; + u8 payload[]; +} __packed; + +static_assert(sizeof(struct ipts_feedback_header) == 64); + +/** + * enum ipts_data_type - Defines what type of data a buffer contains. + * @IPTS_DATA_TYPE_FRAME: Raw data frame. + * @IPTS_DATA_TYPE_ERROR: Error data. + * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. + * @IPTS_DATA_TYPE_HID: A HID report. + * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. + */ +enum ipts_data_type { + IPTS_DATA_TYPE_FRAME = 0x00, + IPTS_DATA_TYPE_ERROR = 0x01, + IPTS_DATA_TYPE_VENDOR = 0x02, + IPTS_DATA_TYPE_HID = 0x03, + IPTS_DATA_TYPE_GET_FEATURES = 0x04, + IPTS_DATA_TYPE_DESCRIPTOR = 0x05, +}; + +/** + * struct ipts_data_header - Header that is prefixed to the data in a data buffer. + * @type: What data the buffer contains. + * @size: How much data the buffer contains. + * @buffer: Which buffer the data is in. + */ +struct ipts_data_header { + enum ipts_data_type type; + u32 size; + u32 buffer; + u8 reserved[52]; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_data_header) == 64); + +#endif /* IPTS_SPEC_DATA_H */ diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h new file mode 100644 index 0000000000000..8cb5f0b5889d8 --- /dev/null +++ b/drivers/hid/ipts/spec-device.h @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_DEVICE_H +#define IPTS_SPEC_DEVICE_H + +#include +#include + +/* + * The amount of buffers that IPTS can use for data transfer. + */ +#define IPTS_BUFFERS 16 + +/* + * The buffer ID that is used for HID2ME feedback + */ +#define IPTS_HID2ME_BUFFER IPTS_BUFFERS + +/** + * enum ipts_command - Commands that can be sent to the IPTS hardware. + * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. + * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. + * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. + * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. + * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. + * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. + * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. + * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. + * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. + */ +enum ipts_command_code { + IPTS_CMD_GET_DEVICE_INFO = 0x01, + IPTS_CMD_SET_MODE = 0x02, + IPTS_CMD_SET_MEM_WINDOW = 0x03, + IPTS_CMD_QUIESCE_IO = 0x04, + IPTS_CMD_READY_FOR_DATA = 0x05, + IPTS_CMD_FEEDBACK = 0x06, + IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, + IPTS_CMD_RESET_SENSOR = 0x0B, + IPTS_CMD_GET_DESCRIPTOR = 0x0F, +}; + +/** + * enum ipts_status - Possible status codes returned by the IPTS device. + * @IPTS_STATUS_SUCCESS: Operation completed successfully. + * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. + * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. + * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. + * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. + * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. + * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. + * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. + * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. + * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. + * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. + * The host can ignore this error and attempt to continue. + * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. + * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. + * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. + * @IPTS_STATUS_TIMEOUT: The operation timed out. + * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. + * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. + * Further progress is not possible. + * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. + * The driver can attempt to continue. + * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. + * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. + */ +enum ipts_status { + IPTS_STATUS_SUCCESS = 0x00, + IPTS_STATUS_INVALID_PARAMS = 0x01, + IPTS_STATUS_ACCESS_DENIED = 0x02, + IPTS_STATUS_CMD_SIZE_ERROR = 0x03, + IPTS_STATUS_NOT_READY = 0x04, + IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, + IPTS_STATUS_NO_SENSOR_FOUND = 0x06, + IPTS_STATUS_OUT_OF_MEMORY = 0x07, + IPTS_STATUS_INTERNAL_ERROR = 0x08, + IPTS_STATUS_SENSOR_DISABLED = 0x09, + IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, + IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, + IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, + IPTS_STATUS_RESET_FAILED = 0x0D, + IPTS_STATUS_TIMEOUT = 0x0E, + IPTS_STATUS_TEST_MODE_FAIL = 0x0F, + IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, + IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, + IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, + IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, +}; + +/** + * struct ipts_command - Message that is sent to the device for calling a command. + * @cmd: The command that will be called. + * @payload: Payload containing parameters for the called command. + */ +struct ipts_command { + enum ipts_command_code cmd; + u8 payload[320]; +} __packed; + +static_assert(sizeof(struct ipts_command) == 324); + +/** + * enum ipts_mode - Configures what data the device produces and how its sent. + * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. + * Older device will return singletouch data in this mode. + * @IPTS_MODE_DOORBELL: The device will notify the driver by incrementing the doorbell value. + * Older devices will return multitouch data in this mode. + */ +enum ipts_mode { + IPTS_MODE_EVENT = 0x00, + IPTS_MODE_DOORBELL = 0x01, +}; + +/** + * struct ipts_set_mode - Payload for the SET_MODE command. + * @mode: Changes the mode that IPTS will operate in. + */ +struct ipts_set_mode { + enum ipts_mode mode; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_set_mode) == 16); + +#define IPTS_WORKQUEUE_SIZE 8192 +#define IPTS_WORKQUEUE_ITEM_SIZE 16 + +/** + * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. + * @data_addr_lower: Lower 32 bits of the data buffer addresses. + * @data_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. + * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. + * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. + * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. + * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. + * @hid2me_size: Size of the hid2me feedback buffer. + * @workqueue_item_size: Magic value. Must be 16. + * @workqueue_size: Magic value. Must be 8192. + * + * The workqueue related items in this struct are required for using + * GuC submission with binary processing firmware. Since this driver does + * not use GuC submission and instead exports raw data to userspace, these + * items are not actually used, but they need to be allocated and passed + * to the device, otherwise initialization will fail. + */ +struct ipts_mem_window { + u32 data_addr_lower[IPTS_BUFFERS]; + u32 data_addr_upper[IPTS_BUFFERS]; + u32 workqueue_addr_lower; + u32 workqueue_addr_upper; + u32 doorbell_addr_lower; + u32 doorbell_addr_upper; + u32 feedback_addr_lower[IPTS_BUFFERS]; + u32 feedback_addr_upper[IPTS_BUFFERS]; + u32 hid2me_addr_lower; + u32 hid2me_addr_upper; + u32 hid2me_size; + u8 reserved1; + u8 workqueue_item_size; + u16 workqueue_size; + u8 reserved[32]; +} __packed; + +static_assert(sizeof(struct ipts_mem_window) == 320); + +/** + * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. + */ +struct ipts_quiesce_io { + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_quiesce_io) == 12); + +/** + * struct ipts_feedback - Payload for the FEEDBACK command. + * @buffer: The buffer that the device should refill. + */ +struct ipts_feedback { + u32 buffer; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_feedback) == 16); + +/** + * enum ipts_reset_type - Possible ways of resetting the device. + * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. + * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. + */ +enum ipts_reset_type { + IPTS_RESET_TYPE_HARD = 0x00, + IPTS_RESET_TYPE_SOFT = 0x01, +}; + +/** + * struct ipts_reset - Payload for the RESET_SENSOR command. + * @type: How the device should get reset. + */ +struct ipts_reset_sensor { + enum ipts_reset_type type; + u8 reserved[4]; +} __packed; + +static_assert(sizeof(struct ipts_reset_sensor) == 8); + +/** + * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. + * @addr_lower: The lower 32 bits of the descriptor buffer address. + * @addr_upper: The upper 32 bits of the descriptor buffer address. + * @magic: A magic value. Must be 8. + */ +struct ipts_get_descriptor { + u32 addr_lower; + u32 addr_upper; + u32 magic; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_get_descriptor) == 24); + +/* + * The type of a response is indicated by a + * command code, with the most significant bit flipped to 1. + */ +#define IPTS_RSP_BIT BIT(31) + +/** + * struct ipts_response - Data returned from the device in response to a command. + * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). + * @status: The return code of the command. + * @payload: The data that was produced by the command. + */ +struct ipts_response { + enum ipts_command_code cmd; + enum ipts_status status; + u8 payload[80]; +} __packed; + +static_assert(sizeof(struct ipts_response) == 88); + +/** + * struct ipts_device_info - Vendor information of the IPTS device. + * @vendor: Vendor ID of this device. + * @product: Product ID of this device. + * @hw_version: Hardware revision of this device. + * @fw_version: Firmware revision of this device. + * @data_size: Requested size for a data buffer. + * @feedback_size: Requested size for a feedback buffer. + * @mode: Mode that the device currently operates in. + * @max_contacts: Maximum amount of concurrent touches the sensor can process. + */ +struct ipts_device_info { + u16 vendor; + u16 product; + u32 hw_version; + u32 fw_version; + u32 data_size; + u32 feedback_size; + enum ipts_mode mode; + u8 max_contacts; + u8 reserved1[3]; + u8 sensor_min_eds; + u8 sensor_maj_eds; + u8 me_min_eds; + u8 me_maj_eds; + u8 intf_eds; + u8 reserved2[11]; +} __packed; + +static_assert(sizeof(struct ipts_device_info) == 44); + +#endif /* IPTS_SPEC_DEVICE_H */ diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h new file mode 100644 index 0000000000000..daead5ef3c11a --- /dev/null +++ b/drivers/hid/ipts/spec-hid.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2022 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_HID_H +#define IPTS_SPEC_HID_H + +#include +#include + +/* + * Made-up type for passing raw IPTS data in a HID report. + */ +#define IPTS_HID_FRAME_TYPE_RAW 0xEE + +/** + * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. + * @size: Size of the data inside the report, including this header. + * @type: What type of data does this report contain. + */ +struct ipts_hid_header { + u32 size; + u8 reserved1; + u8 type; + u8 reserved2; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_hid_header) == 7); + +#endif /* IPTS_SPEC_HID_H */ diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild new file mode 100644 index 0000000000000..429dcc67209ce --- /dev/null +++ b/drivers/hid/ithc/Kbuild @@ -0,0 +1,6 @@ +obj-m := ithc.o + +ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-api.o ithc-debug.o + +ccflags-y := -std=gnu11 -Wno-declaration-after-statement + diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig new file mode 100644 index 0000000000000..ede7130236096 --- /dev/null +++ b/drivers/hid/ithc/Kconfig @@ -0,0 +1,12 @@ +config HID_ITHC + tristate "Intel Touch Host Controller" + depends on PCI + depends on HID + help + Say Y here if your system has a touchscreen using Intels + Touch Host Controller (ITHC / IPTS) technology. + + If unsure say N. + + To compile this driver as a module, choose M here: the + module will be called ithc. diff --git a/drivers/hid/ithc/ithc-api.c b/drivers/hid/ithc/ithc-api.c new file mode 100644 index 0000000000000..136205ecd631c --- /dev/null +++ b/drivers/hid/ithc/ithc-api.c @@ -0,0 +1,123 @@ +#include "ithc.h" + +// Userspace API: +// All data received on the active DMA RX channel is made available to userspace through character device /dev/ithc (/dev/ithc[01] if both channels are active). +// The chardev is read-only and has no ioctls. Multitouch mode is automatically activated when the device is opened and deactivated when it is closed. +// Reading from the chardev will obtain one or more messages containing data, or block if there is no new data. +// Only complete messages can be read; if the provided user buffer is too small, read() will return -EMSGSIZE instead of performing a partial read. +// Each message has a header (struct ithc_api_header) containing a sequential message number and the size of the data. + +static loff_t ithc_api_llseek(struct file *f, loff_t offset, int whence) { + struct ithc_api *a = container_of(f->private_data, struct ithc_api, m); + return generic_file_llseek_size(f, offset, whence, MAX_LFS_FILESIZE, READ_ONCE(a->rx->pos)); +} + +static ssize_t ithc_api_read_unlocked(struct file *f, char __user *buf, size_t size, loff_t *offset) { + struct ithc_api *a = container_of(f->private_data, struct ithc_api, m); + loff_t pos = READ_ONCE(a->rx->pos); + unsigned n = a->rx->num_received; + unsigned newest = (n + NUM_RX_ALLOC - 1) % NUM_RX_ALLOC; + unsigned oldest = (n + NUM_RX_DEV) % NUM_RX_ALLOC; + unsigned i = newest; + + // scan backwards to find which buf we need to copy first + while (1) { + pos -= sizeof(struct ithc_api_header) + a->rx->bufs[i].data_size; + if (pos <= *offset) break; + if (i == oldest) break; + i = i ? i-1 : NUM_RX_ALLOC-1; + n--; + } + + // copy as many bufs as possible + size_t nread = 0; + while (nread + sizeof(struct ithc_api_header) + a->rx->bufs[i].data_size <= size) { + struct ithc_dma_data_buffer *b = &a->rx->bufs[i]; + if (b->active_idx >= 0) { pci_err(a->ithc->pci, "tried to access active buffer\n"); return -EFAULT; } // should be impossible + struct ithc_api_header hdr = { .hdr_size = sizeof hdr, .msg_num = n, .size = b->data_size }; + if (copy_to_user(buf + nread, &hdr, sizeof hdr)) return -EFAULT; + nread += sizeof hdr; + + if (copy_to_user(buf + nread, b->addr, b->data_size)) return -EFAULT; + nread += b->data_size; + + if (i == newest) break; + if (++i == NUM_RX_ALLOC) i = 0; + n++; + } + if (!nread) return -EMSGSIZE; // user buffer is too small + + *offset = pos + nread; + return nread; +} +static ssize_t ithc_api_read(struct file *f, char __user *buf, size_t size, loff_t *offset) { + struct ithc_api *a = container_of(f->private_data, struct ithc_api, m); + if (f->f_pos >= READ_ONCE(a->rx->pos)) { + if (f->f_flags & O_NONBLOCK) return -EWOULDBLOCK; + if (wait_event_interruptible(a->rx->wait, f->f_pos < READ_ONCE(a->rx->pos))) return -ERESTARTSYS; + } + // lock to avoid buffers getting transferred to device while we're reading them + if (mutex_lock_interruptible(&a->rx->mutex)) return -ERESTARTSYS; + int ret = ithc_api_read_unlocked(f, buf, size, offset); + mutex_unlock(&a->rx->mutex); + return ret; +} + +static __poll_t ithc_api_poll(struct file *f, struct poll_table_struct *pt) { + struct ithc_api *a = container_of(f->private_data, struct ithc_api, m); + poll_wait(f, &a->rx->wait, pt); + if (f->f_pos < READ_ONCE(a->rx->pos)) return POLLIN; + return 0; +} + +static int ithc_api_open(struct inode *n, struct file *f) { + struct ithc_api *a = container_of(f->private_data, struct ithc_api, m); + struct ithc *ithc = a->ithc; + if (atomic_fetch_inc(&a->open_count) == 0) CHECK(ithc_set_multitouch, ithc, true); + return 0; +} + +static int ithc_api_release(struct inode *n, struct file *f) { + struct ithc_api *a = container_of(f->private_data, struct ithc_api, m); + struct ithc *ithc = a->ithc; + int c = atomic_dec_return(&a->open_count); + if (c == 0) CHECK(ithc_set_multitouch, ithc, false); + if (c < 0) pci_err(ithc->pci, "open/release mismatch\n"); + return 0; +} + +static const struct file_operations dev_fops = { + .owner = THIS_MODULE, + .llseek = ithc_api_llseek, + .read = ithc_api_read, + .poll = ithc_api_poll, + .open = ithc_api_open, + .release = ithc_api_release, +}; + +static void ithc_api_devres_release(struct device *dev, void *res) { + struct ithc_api *a = res; + misc_deregister(&a->m); +} + +int ithc_api_init(struct ithc *ithc, struct ithc_dma_rx *rx, const char *name) { + struct ithc_api *a = devres_alloc(ithc_api_devres_release, sizeof *a, GFP_KERNEL); + if (!a) return -ENOMEM; + a->ithc = ithc; + a->rx = rx; + a->m.minor = MISC_DYNAMIC_MINOR; + a->m.name = name; + a->m.fops = &dev_fops; + a->m.mode = 0440; + a->m.parent = &ithc->pci->dev; + int r = CHECK(misc_register, &a->m); + if (r) { + devres_free(a); + return r; + } + pci_info(ithc->pci, "registered device %s\n", name); + devres_add(&ithc->pci->dev, a); + rx->api = a; + return 0; +} + diff --git a/drivers/hid/ithc/ithc-api.h b/drivers/hid/ithc/ithc-api.h new file mode 100644 index 0000000000000..94fde10b01c1c --- /dev/null +++ b/drivers/hid/ithc/ithc-api.h @@ -0,0 +1,16 @@ +struct ithc_api { + struct ithc *ithc; + struct ithc_dma_rx *rx; + struct miscdevice m; + atomic_t open_count; +}; + +struct ithc_api_header { + u8 hdr_size; + u8 reserved[3]; + u32 msg_num; + u32 size; +}; + +int ithc_api_init(struct ithc *ithc, struct ithc_dma_rx *rx, const char *name); + diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c new file mode 100644 index 0000000000000..b2a2d2e9250e0 --- /dev/null +++ b/drivers/hid/ithc/ithc-debug.c @@ -0,0 +1,95 @@ +#include "ithc.h" + +void ithc_log_regs(struct ithc *ithc) { + if (!ithc->prev_regs) return; + u32 *cur = (void*)ithc->regs, *prev = (void*)ithc->prev_regs; + for (int i = 1024; i < sizeof *ithc->regs / 4; i++) { + u32 x = readl(cur + i); + if (x != prev[i]) { + pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); + prev[i] = x; + } + } +} + +static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, loff_t *offset) { + struct ithc *ithc = file_inode(f)->i_private; + char cmd[256]; + if (!ithc || !ithc->pci) return -ENODEV; + if (!len) return -EINVAL; + if (len >= sizeof cmd) return -EINVAL; + if (copy_from_user(cmd, buf, len)) return -EFAULT; + cmd[len] = 0; + if (cmd[len-1] == '\n') cmd[len-1] = 0; + pci_info(ithc->pci, "debug command: %s\n", cmd); + u32 n = 0; + const char *s = cmd + 1; + u32 a[32]; + while (*s && *s != '\n') { + if (n >= ARRAY_SIZE(a)) return -EINVAL; + if (*s++ != ' ') return -EINVAL; + char *e; + a[n++] = simple_strtoul(s, &e, 0); + if (e == s) return -EINVAL; + s = e; + } + ithc_log_regs(ithc); + switch(cmd[0]) { + case 'x': // reset + ithc_reset(ithc); + break; + case 'w': // write register: offset mask value + if (n != 3 || (a[0] & 3)) return -EINVAL; + pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", a[0], a[2], a[1]); + bitsl(((u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); + break; + case 'r': // read register: offset + if (n != 1 || (a[0] & 3)) return -EINVAL; + pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], readl(((u32 *)ithc->regs) + a[0] / 4)); + break; + case 's': // spi command: cmd offset len data... + // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // set touch cfg: s 6 12 4 XX + if (n < 3 || a[2] > (n - 3) * 4) return -EINVAL; + pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); + if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) + for (u32 i = 0; i < (a[2] + 3) / 4; i++) pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); + break; + case 'd': // dma command: cmd len data... + // get report descriptor: d 7 8 0 0 + // enable multitouch: d 3 2 0x0105 + if (n < 2 || a[1] > (n - 2) * 4) return -EINVAL; + pci_info(ithc->pci, "debug dma command %u with %u bytes of data\n", a[0], a[1]); + if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) pci_err(ithc->pci, "dma tx failed\n"); + break; + default: + return -EINVAL; + } + ithc_log_regs(ithc); + return len; +} + +static const struct file_operations ithc_debugfops_cmd = { + .owner = THIS_MODULE, + .write = ithc_debugfs_cmd_write, +}; + +static void ithc_debugfs_devres_release(struct device *dev, void *res) { + struct dentry **dbgm = res; + if (*dbgm) debugfs_remove_recursive(*dbgm); +} + +int ithc_debug_init(struct ithc *ithc) { + struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof *dbgm, GFP_KERNEL); + if (!dbgm) return -ENOMEM; + devres_add(&ithc->pci->dev, dbgm); + struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); + if (IS_ERR(dbg)) return PTR_ERR(dbg); + *dbgm = dbg; + + struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); + if (IS_ERR(cmd)) return PTR_ERR(cmd); + + return 0; +} + diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c new file mode 100644 index 0000000000000..983b4fe5ace07 --- /dev/null +++ b/drivers/hid/ithc/ithc-dma.c @@ -0,0 +1,276 @@ +#include "ithc.h" + +static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, unsigned num_buffers, unsigned num_pages, enum dma_data_direction dir) { + p->num_pages = num_pages; + p->dir = dir; + p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); + p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); + if (!p->addr) return -ENOMEM; + if (p->dma_addr & (PAGE_SIZE - 1)) return -EFAULT; + return 0; +} + +struct ithc_sg_table { + void *addr; + struct sg_table sgt; + enum dma_data_direction dir; +}; +static void ithc_dma_sgtable_free(struct sg_table *sgt) { + struct scatterlist *sg; + int i; + for_each_sgtable_sg(sgt, sg, i) { + struct page *p = sg_page(sg); + if (p) __free_page(p); + } + sg_free_table(sgt); +} +static void ithc_dma_data_devres_release(struct device *dev, void *res) { + struct ithc_sg_table *sgt = res; + if (sgt->addr) vunmap(sgt->addr); + dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); + ithc_dma_sgtable_free(&sgt->sgt); +} + +static int ithc_dma_data_alloc(struct ithc* ithc, struct ithc_dma_prd_buffer *prds, struct ithc_dma_data_buffer *b) { + // We don't use dma_alloc_coherent for data buffers, because they don't have to be contiguous (we can use one PRD per page) or coherent (they are unidirectional). + // Instead we use an sg_table of individually allocated pages (5.13 has dma_alloc_noncontiguous for this, but we'd like to support 5.10 for now). + struct page *pages[16]; + if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) return -EINVAL; + b->active_idx = -1; + struct ithc_sg_table *sgt = devres_alloc(ithc_dma_data_devres_release, sizeof *sgt, GFP_KERNEL); + if (!sgt) return -ENOMEM; + sgt->dir = prds->dir; + if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { + struct scatterlist *sg; + int i; + bool ok = true; + for_each_sgtable_sg(&sgt->sgt, sg, i) { + struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); // don't need __GFP_DMA for PCI DMA + if (!p) { ok = false; break; } + sg_set_page(sg, p, PAGE_SIZE, 0); + } + if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { + devres_add(&ithc->pci->dev, sgt); + b->sgt = &sgt->sgt; + b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); + if (!b->addr) return -ENOMEM; + return 0; + } + ithc_dma_sgtable_free(&sgt->sgt); + } + devres_free(sgt); + return -ENOMEM; +} + +static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, struct ithc_dma_data_buffer *b, unsigned idx) { + struct ithc_phys_region_desc *prd = prds->addr; + prd += idx * prds->num_pages; + if (b->active_idx >= 0) { pci_err(ithc->pci, "buffer already active\n"); return -EINVAL; } + b->active_idx = idx; + if (prds->dir == DMA_TO_DEVICE) { + if (b->data_size > PAGE_SIZE) return -EINVAL; + prd->addr = sg_dma_address(b->sgt->sgl) >> 10; + prd->size = b->data_size | PRD_END_FLAG; + flush_kernel_vmap_range(b->addr, b->data_size); + } else if (prds->dir == DMA_FROM_DEVICE) { + struct scatterlist *sg; + int i; + for_each_sgtable_dma_sg(b->sgt, sg, i) { + prd->addr = sg_dma_address(sg) >> 10; + prd->size = sg_dma_len(sg); + prd++; + } + prd[-1].size |= PRD_END_FLAG; + } + dma_wmb(); // for the prds + dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); + return 0; +} + +static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, struct ithc_dma_data_buffer *b, unsigned idx) { + struct ithc_phys_region_desc *prd = prds->addr; + prd += idx * prds->num_pages; + if (b->active_idx != idx) { pci_err(ithc->pci, "wrong buffer index\n"); return -EINVAL; } + b->active_idx = -1; + if (prds->dir == DMA_FROM_DEVICE) { + dma_rmb(); // for the prds + b->data_size = 0; + struct scatterlist *sg; + int i; + for_each_sgtable_dma_sg(b->sgt, sg, i) { + unsigned size = prd->size; + b->data_size += size & PRD_SIZE_MASK; + if (size & PRD_END_FLAG) break; + if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { pci_err(ithc->pci, "truncated prd\n"); break; } + prd++; + } + invalidate_kernel_vmap_range(b->addr, b->data_size); + } + dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); + return 0; +} + +int ithc_dma_rx_init(struct ithc *ithc, u8 channel, const char *devname) { + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + mutex_init(&rx->mutex); + init_waitqueue_head(&rx->wait); + u32 buf_size = DEVCFG_DMA_RX_SIZE(ithc->config.dma_buf_sizes); + unsigned num_pages = (buf_size + PAGE_SIZE - 1) / PAGE_SIZE; + pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", NUM_RX_ALLOC, buf_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_ALLOC, num_pages, DMA_FROM_DEVICE); + for (unsigned i = 0; i < NUM_RX_ALLOC; i++) + CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); + writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); + lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); + writeb(NUM_RX_DEV - 1, &ithc->regs->dma_rx[channel].num_bufs); + writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); + u8 head = readb(&ithc->regs->dma_rx[channel].head); + if (head) { pci_err(ithc->pci, "head is nonzero (%u)\n", head); return -EIO; } + for (unsigned i = 0; i < NUM_RX_DEV; i++) + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); + writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); + ithc_api_init(ithc, rx, devname); + return 0; +} +void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) { + bitsb_set(&ithc->regs->dma_rx[channel].control, DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); + CHECK(waitl, ithc, &ithc->regs->dma_rx[1].status, DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); +} + +int ithc_dma_tx_init(struct ithc *ithc) { + struct ithc_dma_tx *tx = &ithc->dma_tx; + mutex_init(&tx->mutex); + tx->max_size = DEVCFG_DMA_TX_SIZE(ithc->config.dma_buf_sizes); + unsigned num_pages = (tx->max_size + PAGE_SIZE - 1) / PAGE_SIZE; + pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", tx->max_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); + CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); + lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); + writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + return 0; +} + +static int ithc_dma_rx_process_buf(struct ithc *ithc, struct ithc_dma_data_buffer *data, u8 channel, u8 buf) { + if (buf >= NUM_RX_DEV) { + pci_err(ithc->pci, "invalid dma ringbuffer index\n"); + return -EINVAL; + } + ithc_set_active(ithc); + u32 len = data->data_size; + struct ithc_dma_rx_header *hdr = data->addr; + u8 *hiddata = (void *)(hdr + 1); + struct ithc_hid_report_singletouch *st = (void *)hiddata; + if (len >= sizeof *hdr && hdr->code == DMA_RX_CODE_RESET) { + CHECK(ithc_reset, ithc); + } else if (len < sizeof *hdr || len != sizeof *hdr + hdr->data_size) { + if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { + // When the CPU enters a low power state during DMA, we can get truncated messages. + // Typically this will be a single touch HID report that is only 1 byte, or a multitouch report that is 257 bytes. + // See also ithc_set_active(). + } else { + pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u, code %u, data size %u\n", channel, buf, len, hdr->code, hdr->data_size); + print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, hdr, min(len, 0x400u), 0); + } + } else if (ithc->input && hdr->code == DMA_RX_CODE_INPUT_REPORT && hdr->data_size == sizeof *st && st->report_id == HID_REPORT_ID_SINGLETOUCH) { + input_report_key(ithc->input, BTN_TOUCH, st->button); + input_report_abs(ithc->input, ABS_X, st->x); + input_report_abs(ithc->input, ABS_Y, st->y); + input_sync(ithc->input); + } else if (ithc->hid && hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { + CHECK(hid_parse_report, ithc->hid, hiddata + 8, hdr->data_size - 8); + WRITE_ONCE(ithc->hid_parse_done, true); + wake_up(&ithc->wait_hid_parse); + } else if (ithc->hid && hdr->code == DMA_RX_CODE_INPUT_REPORT) { + CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); + } else if (ithc->hid && hdr->code == DMA_RX_CODE_FEATURE_REPORT) { + bool done = false; + mutex_lock(&ithc->hid_get_feature_mutex); + if (ithc->hid_get_feature_buf) { + if (hdr->data_size < ithc->hid_get_feature_size) ithc->hid_get_feature_size = hdr->data_size; + memcpy(ithc->hid_get_feature_buf, hiddata, ithc->hid_get_feature_size); + ithc->hid_get_feature_buf = NULL; + done = true; + } + mutex_unlock(&ithc->hid_get_feature_mutex); + if (done) wake_up(&ithc->wait_hid_get_feature); + else CHECK(hid_input_report, ithc->hid, HID_FEATURE_REPORT, hiddata, hdr->data_size, 1); + } else { + pci_dbg(ithc->pci, "unhandled dma rx data! channel %u, buffer %u, size %u, code %u\n", channel, buf, len, hdr->code); + print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, hdr, min(len, 0x400u), 0); + } + return 0; +} + +static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) { + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + // buf idx tail..tail+NUM_RX_DEV-1 are mapped as dev idx tail%NUM_RX_DEV.. + // tail+NUM_RX_DEV = oldest unmapped buffer + unsigned n = rx->num_received; + u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); + while (1) { + u8 tail = n % NUM_RX_DEV; + u8 tail_wrap = tail | ((n / NUM_RX_DEV) & 1 ? 0 : DMA_RX_WRAP_FLAG); + writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); + // ringbuffer is full if tail_wrap == head_wrap + // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG + if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) return 0; + + // take the buffer that the device just filled + struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_ALLOC]; + CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); + // give the oldest buffer we have back to the device to replace the buffer we took + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[(n + NUM_RX_DEV) % NUM_RX_ALLOC], tail); + rx->num_received = ++n; + WRITE_ONCE(rx->pos, READ_ONCE(rx->pos) + sizeof(struct ithc_api_header) + b->data_size); + + // process data + CHECK(ithc_dma_rx_process_buf, ithc, b, channel, tail); + + wake_up(&rx->wait); + } +} +int ithc_dma_rx(struct ithc *ithc, u8 channel) { + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + mutex_lock(&rx->mutex); + int ret = ithc_dma_rx_unlocked(ithc, channel); + mutex_unlock(&rx->mutex); + return ret; +} + +static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) { + pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); + struct ithc_dma_tx_header *hdr; + u8 padding = datasize & 3 ? 4 - (datasize & 3) : 0; + unsigned fullsize = sizeof *hdr + datasize + padding; + if (fullsize > ithc->dma_tx.max_size || fullsize > PAGE_SIZE) return -EINVAL; + CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + + ithc->dma_tx.buf.data_size = fullsize; + hdr = ithc->dma_tx.buf.addr; + hdr->code = cmdcode; + hdr->data_size = datasize; + u8 *dest = (void *)(hdr + 1); + memcpy(dest, data, datasize); + dest += datasize; + for (u8 p = 0; p < padding; p++) *dest++ = 0; + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + + bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); + CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); + writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); + return 0; +} +int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) { + mutex_lock(&ithc->dma_tx.mutex); + int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); + mutex_unlock(&ithc->dma_tx.mutex); + return ret; +} + +int ithc_set_multitouch(struct ithc *ithc, bool enable) { + pci_info(ithc->pci, "%s multi-touch mode\n", enable ? "enabling" : "disabling"); + struct ithc_hid_feature_multitouch data = { .feature_id = HID_FEATURE_ID_MULTITOUCH, .enable = enable }; + return ithc_dma_tx(ithc, DMA_TX_CODE_SET_FEATURE, sizeof data, &data); +} + diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h new file mode 100644 index 0000000000000..cba2cac28d734 --- /dev/null +++ b/drivers/hid/ithc/ithc-dma.h @@ -0,0 +1,80 @@ +#define PRD_SIZE_MASK 0xffffff +#define PRD_END_FLAG 0x1000000 +struct ithc_phys_region_desc { + u64 addr; // physical addr/1024 + u32 size; // num bytes, PRD_END_FLAG marks last prd for data split over multiple prds + u32 unused; +}; + +#define DMA_RX_CODE_INPUT_REPORT 3 +#define DMA_RX_CODE_FEATURE_REPORT 4 +#define DMA_RX_CODE_REPORT_DESCRIPTOR 5 +#define DMA_RX_CODE_RESET 7 +struct ithc_dma_rx_header { + u32 code; + u32 data_size; + u32 _unknown[14]; +}; + +#define DMA_TX_CODE_SET_FEATURE 3 +#define DMA_TX_CODE_GET_FEATURE 4 +#define DMA_TX_CODE_OUTPUT_REPORT 5 +#define DMA_TX_CODE_GET_REPORT_DESCRIPTOR 7 +struct ithc_dma_tx_header { + u32 code; + u32 data_size; +}; + +#define HID_REPORT_ID_SINGLETOUCH 0x40 +struct ithc_hid_report_singletouch { + u8 report_id; + u8 button; + u16 x; + u16 y; +}; + +#define HID_FEATURE_ID_MULTITOUCH 5 +struct ithc_hid_feature_multitouch { + u8 feature_id; + u8 enable; +}; + +struct ithc_dma_prd_buffer { + void *addr; + dma_addr_t dma_addr; + u32 size; + u32 num_pages; // per data buffer + enum dma_data_direction dir; +}; + +struct ithc_dma_data_buffer { + void *addr; + struct sg_table *sgt; + int active_idx; + u32 data_size; +}; + +struct ithc_dma_tx { + struct mutex mutex; + u32 max_size; + struct ithc_dma_prd_buffer prds; + struct ithc_dma_data_buffer buf; +}; + +struct ithc_dma_rx { + struct mutex mutex; + u32 num_received; + loff_t pos; + struct ithc_api *api; + struct ithc_dma_prd_buffer prds; + struct ithc_dma_data_buffer bufs[NUM_RX_ALLOC]; + wait_queue_head_t wait; +}; + +int ithc_dma_rx_init(struct ithc *ithc, u8 channel, const char *devname); +void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); +int ithc_dma_tx_init(struct ithc *ithc); +int ithc_dma_rx(struct ithc *ithc, u8 channel); +int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); +int ithc_set_multitouch(struct ithc *ithc, bool enable); + diff --git a/drivers/hid/ithc/ithc-dump.c b/drivers/hid/ithc/ithc-dump.c new file mode 100644 index 0000000000000..03caca329a42f --- /dev/null +++ b/drivers/hid/ithc/ithc-dump.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Simple debugging utility which dumps data from /dev/ithc. + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "usage: %s DEVICE\n", argv[0]); + return 1; + } + + int fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror("open"); + return 1; + } + //lseek(fd, 0, SEEK_END); + + uint8_t buf[0x10000]; + uint32_t next = 0; + while (1) { + ssize_t r = read(fd, buf, sizeof buf); + if (r < 0) { + perror("read"); + return 1; + } + ssize_t i = 0; + while (i < r) { + uint32_t *hdr = (void *)(buf + i); + // api header + uint32_t hdr_size = hdr[0] & 0xff; + uint32_t msg_num = hdr[1]; + uint32_t size = hdr[2]; + if (msg_num != next) fprintf(stderr, "missed messages %"PRIu32" to %"PRIu32"\n", next, msg_num-1); + next = msg_num + 1; + printf("%10"PRIu32" %5"PRIu32":", msg_num, size); + i += hdr_size; + hdr += hdr_size / 4; + // device header + uint32_t nhdr = 16; + if (size < nhdr * 4) nhdr = size / 4; + for (uint32_t j = 0; j < nhdr; j++) printf(j == 1 ? " %4"PRIu32 : " %"PRIu32, hdr[j]); + i += 4 * nhdr; + size -= 4 * nhdr; + // device data + printf(" |"); + uint32_t nprint = 32; + if (size < nprint) nprint = size; + for (uint32_t j = 0; j < nprint; j++) printf(" %02x", buf[i + j]); + if (nprint < size) printf(" + %"PRIu32" B", size - nprint); + i += size; + printf("\n"); + } + if (i != r) { + fprintf(stderr, "position mismatch\n"); + return 1; + } + } + + return 0; +} diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c new file mode 100644 index 0000000000000..370906f78c3f3 --- /dev/null +++ b/drivers/hid/ithc/ithc-main.c @@ -0,0 +1,566 @@ +#include "ithc.h" + +MODULE_DESCRIPTION("Intel Touch Host Controller driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +// Lakefield +#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 +#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 +// Tiger Lake +#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 +#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 +#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 +#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 +// Alder Lake +#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 +#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 +#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 +#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 +#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 +#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 +// Raptor Lake +#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 +#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 +// Meteor Lake +#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 +#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a + +static const struct pci_device_id ithc_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT2) }, + {} +}; +MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); + +// Module parameters + +static bool ithc_use_polling = false; +module_param_named(poll, ithc_use_polling, bool, 0); +MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); + +static bool ithc_use_hid = true; +module_param_named(hid, ithc_use_hid, bool, 0); +MODULE_PARM_DESC(hid, "Operate as an HID transport driver instead of an input driver"); + +static bool ithc_use_rx0 = false; +module_param_named(rx0, ithc_use_rx0, bool, 0); +MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); + +static bool ithc_use_rx1 = true; +module_param_named(rx1, ithc_use_rx1, bool, 0); +MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); + +static bool ithc_log_regs_enabled = false; +module_param_named(logregs, ithc_log_regs_enabled, bool, 0); +MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); + +// Sysfs attributes + +static bool ithc_is_config_valid(struct ithc *ithc) { + return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; +} + +static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct ithc *ithc = dev_get_drvdata(dev); + if (!ithc || !ithc_is_config_valid(ithc)) return -ENODEV; + return sprintf(buf, "0x%04x", ithc->config.vendor_id); +} +DEVICE_ATTR_RO(vendor); +static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct ithc *ithc = dev_get_drvdata(dev); + if (!ithc || !ithc_is_config_valid(ithc)) return -ENODEV; + return sprintf(buf, "0x%04x", ithc->config.product_id); +} +DEVICE_ATTR_RO(product); +static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct ithc *ithc = dev_get_drvdata(dev); + if (!ithc || !ithc_is_config_valid(ithc)) return -ENODEV; + return sprintf(buf, "%u", ithc->config.revision); +} +DEVICE_ATTR_RO(revision); +static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct ithc *ithc = dev_get_drvdata(dev); + if (!ithc || !ithc_is_config_valid(ithc)) return -ENODEV; + u32 v = ithc->config.fw_version; + return sprintf(buf, "%i.%i.%i.%i", v >> 24, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff); +} +DEVICE_ATTR_RO(fw_version); + +static const struct attribute_group *ithc_attribute_groups[] = { + &(const struct attribute_group){ + .name = DEVNAME, + .attrs = (struct attribute *[]){ + &dev_attr_vendor.attr, + &dev_attr_product.attr, + &dev_attr_revision.attr, + &dev_attr_fw_version.attr, + NULL + }, + }, + NULL +}; + +// HID/input setup + +static int ithc_hid_start(struct hid_device *hdev) { return 0; } +static void ithc_hid_stop(struct hid_device *hdev) { } +static int ithc_hid_open(struct hid_device *hdev) { return 0; } +static void ithc_hid_close(struct hid_device *hdev) { } + +static int ithc_hid_parse(struct hid_device *hdev) { + struct ithc *ithc = hdev->driver_data; + u64 val = 0; + WRITE_ONCE(ithc->hid_parse_done, false); + CHECK_RET(ithc_dma_tx, ithc, DMA_TX_CODE_GET_REPORT_DESCRIPTOR, sizeof val, &val); + if (!wait_event_timeout(ithc->wait_hid_parse, READ_ONCE(ithc->hid_parse_done), msecs_to_jiffies(1000))) return -ETIMEDOUT; + return 0; +} + +static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len, unsigned char rtype, int reqtype) { + struct ithc *ithc = hdev->driver_data; + if (!buf || !len) return -EINVAL; + u32 code; + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) code = DMA_TX_CODE_OUTPUT_REPORT; + else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) code = DMA_TX_CODE_SET_FEATURE; + else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) code = DMA_TX_CODE_GET_FEATURE; + else { + pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", rtype, reqtype, reportnum); + return -EINVAL; + } + buf[0] = reportnum; + if (reqtype == HID_REQ_GET_REPORT) { + mutex_lock(&ithc->hid_get_feature_mutex); + ithc->hid_get_feature_buf = buf; + ithc->hid_get_feature_size = len; + mutex_unlock(&ithc->hid_get_feature_mutex); + int r = CHECK(ithc_dma_tx, ithc, code, 1, buf); + if (!r) { + r = wait_event_interruptible_timeout(ithc->wait_hid_get_feature, !ithc->hid_get_feature_buf, msecs_to_jiffies(1000)); + if (!r) r = -ETIMEDOUT; + else if (r < 0) r = -EINTR; + else r = 0; + } + mutex_lock(&ithc->hid_get_feature_mutex); + ithc->hid_get_feature_buf = NULL; + if (!r) r = ithc->hid_get_feature_size; + mutex_unlock(&ithc->hid_get_feature_mutex); + return r; + } + CHECK_RET(ithc_dma_tx, ithc, code, len, buf); + return 0; +} + +static struct hid_ll_driver ithc_ll_driver = { + .start = ithc_hid_start, + .stop = ithc_hid_stop, + .open = ithc_hid_open, + .close = ithc_hid_close, + .parse = ithc_hid_parse, + .raw_request = ithc_hid_raw_request, +}; + +static void ithc_hid_devres_release(struct device *dev, void *res) { + struct hid_device **hidm = res; + if (*hidm) hid_destroy_device(*hidm); +} + +static int ithc_hid_init(struct ithc *ithc) { + struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof *hidm, GFP_KERNEL); + if (!hidm) return -ENOMEM; + devres_add(&ithc->pci->dev, hidm); + struct hid_device *hid = hid_allocate_device(); + if (IS_ERR(hid)) return PTR_ERR(hid); + *hidm = hid; + + strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); + strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); + hid->ll_driver = &ithc_ll_driver; + hid->bus = BUS_PCI; + hid->vendor = ithc->config.vendor_id; + hid->product = ithc->config.product_id; + hid->version = 0x100; + hid->dev.parent = &ithc->pci->dev; + hid->driver_data = ithc; + + ithc->hid = hid; + return 0; +} + +static int ithc_input_init(struct ithc *ithc) { + struct input_dev *d = devm_input_allocate_device(&ithc->pci->dev); + if (!d) return -ENOMEM; + + d->name = DEVFULLNAME; + d->phys = ithc->phys; + d->id.bustype = BUS_PCI; + d->id.vendor = ithc->config.vendor_id; + d->id.product = ithc->config.product_id; + d->id.version = ithc->config.revision; + d->dev.parent = &ithc->pci->dev; + + __set_bit(INPUT_PROP_DIRECT, d->propbit); + input_set_capability(d, EV_KEY, BTN_TOUCH); + input_set_abs_params(d, ABS_X, 0, 32767, 0, 0); + input_set_abs_params(d, ABS_Y, 0, 32767, 0, 0); + + CHECK_RET(input_register_device, d); + + ithc->input = d; + return 0; +} + +// Interrupts/polling + +static void ithc_activity_timer_callback(struct timer_list *t) { + struct ithc *ithc = container_of(t, struct ithc, activity_timer); + cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); +} + +void ithc_set_active(struct ithc *ithc) { + // When CPU usage is very low, the CPU can enter various low power states (C2-C10). + // This disrupts DMA, causing truncated DMA messages. ERROR_FLAG_DMA_UNKNOWN_12 will be set when this happens. + // The amount of truncated messages can become very high, resulting in user-visible effects (laggy/stuttering cursor). + // To avoid this, we use a CPU latency QoS request to prevent the CPU from entering low power states during touch interactions. + cpu_latency_qos_update_request(&ithc->activity_qos, 0); + mod_timer(&ithc->activity_timer, jiffies + msecs_to_jiffies(1000)); +} + +static int ithc_set_device_enabled(struct ithc *ithc, bool enable) { + u32 x = ithc->config.touch_cfg = (ithc->config.touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | DEVCFG_TOUCH_UNKNOWN_2 + | (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_UNKNOWN_3 | DEVCFG_TOUCH_UNKNOWN_4 : 0); + return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, offsetof(struct ithc_device_config, touch_cfg), sizeof x, &x); +} + +static void ithc_disable_interrupts(struct ithc *ithc) { + writel(0, &ithc->regs->error_control); + bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); + bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); +} + +static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned channel) { + writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_UNKNOWN_4 | DMA_RX_STATUS_HAVE_DATA, &ithc->regs->dma_rx[channel].status); +} + +static void ithc_clear_interrupts(struct ithc *ithc) { + writel(0xffffffff, &ithc->regs->error_flags); + writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + ithc_clear_dma_rx_interrupts(ithc, 0); + ithc_clear_dma_rx_interrupts(ithc, 1); + writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, &ithc->regs->dma_tx.status); +} + +static void ithc_process(struct ithc *ithc) { + ithc_log_regs(ithc); + + // read and clear error bits + u32 err = readl(&ithc->regs->error_flags); + if (err) { + if (err & ~ERROR_FLAG_DMA_UNKNOWN_12) pci_err(ithc->pci, "error flags: 0x%08x\n", err); + writel(err, &ithc->regs->error_flags); + } + + // process DMA rx + if (ithc_use_rx0) { + ithc_clear_dma_rx_interrupts(ithc, 0); + ithc_dma_rx(ithc, 0); + } + if (ithc_use_rx1) { + ithc_clear_dma_rx_interrupts(ithc, 1); + ithc_dma_rx(ithc, 1); + } + + ithc_log_regs(ithc); +} + +static irqreturn_t ithc_interrupt_thread(int irq, void *arg) { + struct ithc *ithc = arg; + pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", + readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), + readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), + readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), + readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), + readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); + ithc_process(ithc); + return IRQ_HANDLED; +} + +static int ithc_poll_thread(void *arg) { + struct ithc *ithc = arg; + unsigned sleep = 100; + while (!kthread_should_stop()) { + u32 n = ithc->dma_rx[1].num_received; + ithc_process(ithc); + if (n != ithc->dma_rx[1].num_received) sleep = 20; + else sleep = min(200u, sleep + (sleep >> 4) + 1); + msleep_interruptible(sleep); + } + return 0; +} + +// Device initialization and shutdown + +static void ithc_disable(struct ithc *ithc) { + bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); + CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); + bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); + bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); + bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); + bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); + ithc_disable_interrupts(ithc); + ithc_clear_interrupts(ithc); +} + +static int ithc_init_device(struct ithc *ithc) { + ithc_log_regs(ithc); + bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; + ithc_disable(ithc); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); + ithc_set_spi_config(ithc, 10, 0); + bitsl_set(&ithc->regs->dma_rx[0].unknown_init_bits, 0x80000000); // seems to help with reading config + + if (was_enabled) if (msleep_interruptible(100)) return -EINTR; + bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); + if (!waitl(ithc, &ithc->regs->state, 0xf, 2)) break; + if (retries > 5) { + pci_err(ithc->pci, "too many retries, failed to reset device\n"); + return -ETIMEDOUT; + } + pci_err(ithc->pci, "invalid state, retrying reset\n"); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); + if (msleep_interruptible(1000)) return -EINTR; + } + ithc_log_regs(ithc); + + CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); + + // read config + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + memset(&ithc->config, 0, sizeof ithc->config); + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof ithc->config, &ithc->config); + u32 *p = (void *)&ithc->config; + pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + if (ithc_is_config_valid(ithc)) break; + if (retries > 10) { + pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ithc->config.device_id); + return -EIO; + } + pci_err(ithc->pci, "failed to read config, retrying\n"); + if (msleep_interruptible(100)) return -EINTR; + } + ithc_log_regs(ithc); + + CHECK_RET(ithc_set_spi_config, ithc, DEVCFG_SPI_MAX_FREQ(ithc->config.spi_config), DEVCFG_SPI_MODE(ithc->config.spi_config)); + CHECK_RET(ithc_set_device_enabled, ithc, true); + ithc_log_regs(ithc); + return 0; +} + +int ithc_reset(struct ithc *ithc) { + pci_err(ithc->pci, "reset\n"); + CHECK(ithc_init_device, ithc); + if (ithc_use_rx0) { + ithc_dma_rx_enable(ithc, 0); + if (ithc->dma_rx[0].api && atomic_read(&ithc->dma_rx[0].api->open_count)) CHECK(ithc_set_multitouch, ithc, true); + } + if (ithc_use_rx1) { + ithc_dma_rx_enable(ithc, 1); + if (ithc->dma_rx[1].api && atomic_read(&ithc->dma_rx[1].api->open_count)) CHECK(ithc_set_multitouch, ithc, true); + } + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "reset completed\n"); + return 0; +} + +static void ithc_stop(void *res) { + struct ithc *ithc = res; + pci_dbg(ithc->pci, "stopping\n"); + ithc_log_regs(ithc); + if (ithc->poll_thread) CHECK(kthread_stop, ithc->poll_thread); + if (ithc->irq >= 0) disable_irq(ithc->irq); + CHECK(ithc_set_device_enabled, ithc, false); + ithc_disable(ithc); + del_timer_sync(&ithc->activity_timer); + cpu_latency_qos_remove_request(&ithc->activity_qos); + // clear dma config + for(unsigned i = 0; i < 2; i++) { + CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); + lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); + writeb(0, &ithc->regs->dma_rx[i].num_bufs); + writeb(0, &ithc->regs->dma_rx[i].num_prds); + } + lo_hi_writeq(0, &ithc->regs->dma_tx.addr); + writeb(0, &ithc->regs->dma_tx.num_prds); + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "stopped\n"); +} + +static void ithc_clear_drvdata(void *res) { + struct pci_dev *pci = res; + pci_set_drvdata(pci, NULL); +} + +static int ithc_start(struct pci_dev *pci) { + pci_dbg(pci, "starting\n"); + if (pci_get_drvdata(pci)) { + pci_err(pci, "device already initialized\n"); + return -EINVAL; + } + if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) return -ENOMEM; + + struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof *ithc, GFP_KERNEL); + if (!ithc) return -ENOMEM; + ithc->irq = -1; + ithc->pci = pci; + snprintf(ithc->phys, sizeof ithc->phys, "pci-%s/" DEVNAME, pci_name(pci)); + init_waitqueue_head(&ithc->wait_hid_parse); + init_waitqueue_head(&ithc->wait_hid_get_feature); + mutex_init(&ithc->hid_get_feature_mutex); + pci_set_drvdata(pci, ithc); + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); + if (ithc_log_regs_enabled) ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof *ithc->prev_regs, GFP_KERNEL); + + CHECK_RET(pcim_enable_device, pci); + pci_set_master(pci); + CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); + CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); + CHECK_RET(pci_set_power_state, pci, PCI_D0); + ithc->regs = pcim_iomap_table(pci)[0]; + + if (!ithc_use_polling) { + CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); + ithc->irq = CHECK(pci_irq_vector, pci, 0); + if (ithc->irq < 0) return ithc->irq; + } + + CHECK_RET(ithc_init_device, ithc); + CHECK(devm_device_add_groups, &pci->dev, ithc_attribute_groups); + if (ithc_use_rx0) CHECK_RET(ithc_dma_rx_init, ithc, 0, ithc_use_rx1 ? DEVNAME "0" : DEVNAME); + if (ithc_use_rx1) CHECK_RET(ithc_dma_rx_init, ithc, 1, ithc_use_rx0 ? DEVNAME "1" : DEVNAME); + CHECK_RET(ithc_dma_tx_init, ithc); + + if (ithc_use_hid) CHECK_RET(ithc_hid_init, ithc); + else CHECK_RET(ithc_input_init, ithc); + + cpu_latency_qos_add_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); + timer_setup(&ithc->activity_timer, ithc_activity_timer_callback, 0); + + // add ithc_stop callback AFTER setting up DMA buffers, so that polling/irqs/DMA are disabled BEFORE the buffers are freed + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); + + if (ithc_use_polling) { + pci_info(pci, "using polling instead of irq\n"); + // use a thread instead of simple timer because we want to be able to sleep + ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); + if (IS_ERR(ithc->poll_thread)) { + int err = PTR_ERR(ithc->poll_thread); + ithc->poll_thread = NULL; + return err; + } + } else { + CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); + } + + if (ithc_use_rx0) ithc_dma_rx_enable(ithc, 0); + if (ithc_use_rx1) ithc_dma_rx_enable(ithc, 1); + + // hid_add_device can only be called after irq/polling is started and DMA is enabled, because it calls ithc_hid_parse which reads the report descriptor via DMA + if (ithc->hid) CHECK_RET(hid_add_device, ithc->hid); + + CHECK(ithc_debug_init, ithc); + + pci_dbg(pci, "started\n"); + return 0; +} + +static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) { + pci_dbg(pci, "device probe\n"); + return ithc_start(pci); +} + +static void ithc_remove(struct pci_dev *pci) { + pci_dbg(pci, "device remove\n"); + // all cleanup is handled by devres +} + +static int ithc_suspend(struct device *dev) { + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm suspend\n"); + devres_release_group(dev, ithc_start); + return 0; +} + +static int ithc_resume(struct device *dev) { + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm resume\n"); + return ithc_start(pci); +} + +static int ithc_freeze(struct device *dev) { + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm freeze\n"); + devres_release_group(dev, ithc_start); + return 0; +} + +static int ithc_thaw(struct device *dev) { + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm thaw\n"); + return ithc_start(pci); +} + +static int ithc_restore(struct device *dev) { + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm restore\n"); + return ithc_start(pci); +} + +static struct pci_driver ithc_driver = { + .name = DEVNAME, + .id_table = ithc_pci_tbl, + .probe = ithc_probe, + .remove = ithc_remove, + .driver.pm = &(const struct dev_pm_ops) { + .suspend = ithc_suspend, + .resume = ithc_resume, + .freeze = ithc_freeze, + .thaw = ithc_thaw, + .restore = ithc_restore, + }, + //.dev_groups = ithc_attribute_groups, // could use this (since 5.14), however the attributes won't have valid values until config has been read anyway +}; + +static int __init ithc_init(void) { + return pci_register_driver(&ithc_driver); +} + +static void __exit ithc_exit(void) { + pci_unregister_driver(&ithc_driver); +} + +module_init(ithc_init); +module_exit(ithc_exit); + diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c new file mode 100644 index 0000000000000..18e58dd6ad191 --- /dev/null +++ b/drivers/hid/ithc/ithc-regs.c @@ -0,0 +1,64 @@ +#include "ithc.h" + +#define reg_num(r) (0x1fff & (u16)(u64)(r)) + +void bitsl(u32 *reg, u32 mask, u32 val) { + if (val & ~mask) pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", reg_num(reg), val, mask); + writel((readl(reg) & ~mask) | (val & mask), reg); +} + +void bitsb(u8 *reg, u8 mask, u8 val) { + if (val & ~mask) pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", reg_num(reg), val, mask); + writeb((readb(reg) & ~mask) | (val & mask), reg); +} + +int waitl(struct ithc *ithc, u32 *reg, u32 mask, u32 val) { + pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", reg_num(reg), mask, val); + u32 x; + if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { + pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", reg_num(reg), mask, val); + return -ETIMEDOUT; + } + pci_dbg(ithc->pci, "done waiting\n"); + return 0; +} + +int waitb(struct ithc *ithc, u8 *reg, u8 mask, u8 val) { + pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", reg_num(reg), mask, val); + u8 x; + if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { + pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", reg_num(reg), mask, val); + return -ETIMEDOUT; + } + pci_dbg(ithc->pci, "done waiting\n"); + return 0; +} + +int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode) { + pci_dbg(ithc->pci, "setting SPI speed to %i, mode %i\n", speed, mode); + if (mode == 3) mode = 2; + bitsl(&ithc->regs->spi_config, + SPI_CONFIG_MODE(0xff) | SPI_CONFIG_SPEED(0xff) | SPI_CONFIG_UNKNOWN_18(0xff) | SPI_CONFIG_SPEED2(0xff), + SPI_CONFIG_MODE(mode) | SPI_CONFIG_SPEED(speed) | SPI_CONFIG_UNKNOWN_18(0) | SPI_CONFIG_SPEED2(speed)); + return 0; +} + +int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) { + pci_dbg(ithc->pci, "SPI command %u, size %u, offset %u\n", command, size, offset); + if (size > sizeof ithc->regs->spi_cmd.data) return -EINVAL; + CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + writeb(command, &ithc->regs->spi_cmd.code); + writew(size, &ithc->regs->spi_cmd.size); + writel(offset, &ithc->regs->spi_cmd.offset); + u32 *p = data, n = (size + 3) / 4; + for (u32 i = 0; i < n; i++) writel(p[i], &ithc->regs->spi_cmd.data[i]); + bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); + CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); + if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) return -EIO; + if (readw(&ithc->regs->spi_cmd.size) != size) return -EMSGSIZE; + for (u32 i = 0; i < n; i++) p[i] = readl(&ithc->regs->spi_cmd.data[i]); + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + return 0; +} + diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h new file mode 100644 index 0000000000000..90e8b702f1d15 --- /dev/null +++ b/drivers/hid/ithc/ithc-regs.h @@ -0,0 +1,186 @@ +#define CONTROL_QUIESCE BIT(1) +#define CONTROL_IS_QUIESCED BIT(2) +#define CONTROL_NRESET BIT(3) +#define CONTROL_READY BIT(29) + +#define SPI_CONFIG_MODE(x) (((x) & 3) << 2) +#define SPI_CONFIG_SPEED(x) (((x) & 7) << 4) +#define SPI_CONFIG_UNKNOWN_18(x) (((x) & 3) << 18) +#define SPI_CONFIG_SPEED2(x) (((x) & 0xf) << 20) // high bit = high speed mode? + +#define ERROR_CONTROL_UNKNOWN_0 BIT(0) +#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs +#define ERROR_CONTROL_UNKNOWN_2 BIT(2) +#define ERROR_CONTROL_UNKNOWN_3 BIT(3) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) +#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? +#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs + +#define ERROR_STATUS_DMA BIT(28) +#define ERROR_STATUS_SPI BIT(30) + +#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) +#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) +#define ERROR_FLAG_DMA_UNKNOWN_12 BIT(12) // set when we receive a truncated DMA message +#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) +#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) +#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) +#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) +#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) +#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) +#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) + +#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete +#define SPI_CMD_CONTROL_IRQ BIT(1) + +#define SPI_CMD_CODE_READ 4 +#define SPI_CMD_CODE_WRITE 6 + +#define SPI_CMD_STATUS_DONE BIT(0) +#define SPI_CMD_STATUS_ERROR BIT(1) +#define SPI_CMD_STATUS_BUSY BIT(3) + +#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete +#define DMA_TX_CONTROL_IRQ BIT(3) + +#define DMA_TX_STATUS_DONE BIT(0) +#define DMA_TX_STATUS_ERROR BIT(1) +#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) +#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? + +#define DMA_RX_CONTROL_ENABLE BIT(0) +#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? +#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? +#define DMA_RX_CONTROL_IRQ_UNKNOWN_4 BIT(4) // rx0 only? +#define DMA_RX_CONTROL_IRQ_DATA BIT(5) + +#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? +#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices + +#define DMA_RX_WRAP_FLAG BIT(7) + +#define DMA_RX_STATUS_ERROR BIT(3) +#define DMA_RX_STATUS_UNKNOWN_4 BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) +#define DMA_RX_STATUS_HAVE_DATA BIT(5) +#define DMA_RX_STATUS_ENABLED BIT(8) + +#define COUNTER_RESET BIT(31) + +struct ithc_registers { + /* 0000 */ u32 _unknown_0000[1024]; + /* 1000 */ u32 _unknown_1000; + /* 1004 */ u32 _unknown_1004; + /* 1008 */ u32 control_bits; + /* 100c */ u32 _unknown_100c; + /* 1010 */ u32 spi_config; + /* 1014 */ u32 _unknown_1014[3]; + /* 1020 */ u32 error_control; + /* 1024 */ u32 error_status; // write to clear + /* 1028 */ u32 error_flags; // write to clear + /* 102c */ u32 _unknown_102c[5]; + struct { + /* 1040 */ u8 control; + /* 1041 */ u8 code; + /* 1042 */ u16 size; + /* 1044 */ u32 status; // write to clear + /* 1048 */ u32 offset; + /* 104c */ u32 data[16]; + /* 108c */ u32 _unknown_108c; + } spi_cmd; + struct { + /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() + /* 1098 */ u8 control; + /* 1099 */ u8 _unknown_1099; + /* 109a */ u8 _unknown_109a; + /* 109b */ u8 num_prds; + /* 109c */ u32 status; // write to clear + } dma_tx; + /* 10a0 */ u32 _unknown_10a0[7]; + /* 10bc */ u32 state; // is 0xe0000402 (dev config val 0) after CONTROL_NRESET, 0xe0000461 after first touch, 0xe0000401 after DMA_RX_CODE_RESET + /* 10c0 */ u32 _unknown_10c0[8]; + /* 10e0 */ u32 _unknown_10e0_counters[3]; + /* 10ec */ u32 _unknown_10ec[5]; + struct { + /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() + /* 1108/1208 */ u8 num_bufs; + /* 1109/1209 */ u8 num_prds; + /* 110a/120a */ u16 _unknown_110a; + /* 110c/120c */ u8 control; + /* 110d/120d */ u8 head; + /* 110e/120e */ u8 tail; + /* 110f/120f */ u8 control2; + /* 1110/1210 */ u32 status; // write to clear + /* 1114/1214 */ u32 _unknown_1114; + /* 1118/1218 */ u64 _unknown_1118_guc_addr; + /* 1120/1220 */ u32 _unknown_1120_guc; + /* 1124/1224 */ u32 _unknown_1124_guc; + /* 1128/1228 */ u32 unknown_init_bits; // bit 2 = guc related, bit 3 = rx1 related, bit 4 = guc related + /* 112c/122c */ u32 _unknown_112c; + /* 1130/1230 */ u64 _unknown_1130_guc_addr; + /* 1138/1238 */ u32 _unknown_1138_guc; + /* 113c/123c */ u32 _unknown_113c; + /* 1140/1240 */ u32 _unknown_1140_guc; + /* 1144/1244 */ u32 _unknown_1144[23]; + /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; + /* 11b8/12b8 */ u32 _unknown_11b8[18]; + } dma_rx[2]; +}; +_Static_assert(sizeof(struct ithc_registers) == 0x1300); + +#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) +#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) + +#define DEVCFG_TOUCH_MASK 0x3f +#define DEVCFG_TOUCH_ENABLE BIT(0) +#define DEVCFG_TOUCH_UNKNOWN_1 BIT(1) +#define DEVCFG_TOUCH_UNKNOWN_2 BIT(2) +#define DEVCFG_TOUCH_UNKNOWN_3 BIT(3) +#define DEVCFG_TOUCH_UNKNOWN_4 BIT(4) +#define DEVCFG_TOUCH_UNKNOWN_5 BIT(5) +#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) + +#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" + +#define DEVCFG_SPI_MAX_FREQ(x) (((x) >> 1) & 0xf) // high bit = use high speed mode? +#define DEVCFG_SPI_MODE(x) (((x) >> 6) & 3) +#define DEVCFG_SPI_UNKNOWN_8(x) (((x) >> 8) & 0x3f) +#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) +#define DEVCFG_SPI_HEARTBEAT_INTERVAL (((x) >> 21) & 7) +#define DEVCFG_SPI_UNKNOWN_25 BIT(25) +#define DEVCFG_SPI_UNKNOWN_26 BIT(26) +#define DEVCFG_SPI_UNKNOWN_27 BIT(27) +#define DEVCFG_SPI_DELAY (((x) >> 28) & 7) +#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) + +struct ithc_device_config { + u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) + u32 _unknown_04; // 04 = 0x00000000 + u32 dma_buf_sizes; // 08 = 0x000a00ff + u32 touch_cfg; // 0c = 0x0000001c + u32 _unknown_10; // 10 = 0x0000001c + u32 device_id; // 14 = 0x43495424 = "$TIC" + u32 spi_config; // 18 = 0xfda00a2e + u16 vendor_id; // 1c = 0x045e = Microsoft Corp. + u16 product_id; // 1e = 0x0c1a + u32 revision; // 20 = 0x00000001 + u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 + u32 _unknown_28; // 28 = 0x00000000 + u32 fw_mode; // 2c = 0x00000000 + u32 _unknown_30; // 30 = 0x00000000 + u32 _unknown_34; // 34 = 0x0404035e (u8,u8,u8,u8 = version?) + u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) + u32 _unknown_3c; // 3c = 0x00000002 +}; + +void bitsl(u32 *reg, u32 mask, u32 val); +void bitsb(u8 *reg, u8 mask, u8 val); +#define bitsl_set(reg, x) bitsl(reg, x, x) +#define bitsb_set(reg, x) bitsb(reg, x, x) +int waitl(struct ithc *ithc, u32 *reg, u32 mask, u32 val); +int waitb(struct ithc *ithc, u8 *reg, u8 mask, u8 val); +int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode); +int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); + diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h new file mode 100644 index 0000000000000..da38f4be5ef18 --- /dev/null +++ b/drivers/hid/ithc/ithc.h @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVNAME "ithc" +#define DEVFULLNAME "Intel Touch Host Controller" + +#undef pr_fmt +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) +#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while(0) + +// We allocate NUM_RX_ALLOC buffers, and take a sliding window of NUM_RX_DEV buffers from that to populate the device ringbuffer. +// The remaining buffers outside the window contain old data that we keep around for sending to userspace. +#define NUM_RX_ALLOC 32 +#define NUM_RX_DEV 16 +_Static_assert(NUM_RX_ALLOC % NUM_RX_DEV == 0, "NUM_RX_ALLOC must be a multiple of NUM_RX_DEV"); + +struct ithc; +struct ithc_api; + +#include "ithc-regs.h" +#include "ithc-dma.h" +#include "ithc-api.h" + +struct ithc { + char phys[32]; + struct pci_dev *pci; + int irq; + struct task_struct *poll_thread; + struct pm_qos_request activity_qos; + struct timer_list activity_timer; + + struct input_dev *input; + + struct hid_device *hid; + bool hid_parse_done; + wait_queue_head_t wait_hid_parse; + wait_queue_head_t wait_hid_get_feature; + struct mutex hid_get_feature_mutex; + void *hid_get_feature_buf; + size_t hid_get_feature_size; + + struct ithc_registers *regs; + struct ithc_registers *prev_regs; // for debugging + struct ithc_device_config config; + struct ithc_dma_rx dma_rx[2]; + struct ithc_dma_tx dma_tx; +}; + +int ithc_reset(struct ithc *ithc); +void ithc_set_active(struct ithc *ithc); +int ithc_debug_init(struct ithc *ithc); +void ithc_log_regs(struct ithc *ithc); + diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c index 2e9683e970f8e..60baf708213d1 100644 --- a/drivers/iommu/intel/irq_remapping.c +++ b/drivers/iommu/intel/irq_remapping.c @@ -396,6 +396,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) data.busmatch_count = 0; pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + /* + * The Intel Touch Host Controller is at 00:10.6, but for some reason + * the MSI interrupts have request id 01:05.0. + * Disable id verification to work around this. + * FIXME Find proper fix or turn this into a quirk. + */ + if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { + switch(dev->device) { + case 0x98d0: case 0x98d1: // LKF + case 0xa0d0: case 0xa0d1: // TGL LP + case 0x43d0: case 0x43d1: // TGL H + set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); + return 0; + } + } + /* * DMA alias provides us with a PCI device and alias. The only case * where the it will return an alias on a different bus than the diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 12230c71fcf34..94e9fb4cdd761 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -513,5 +513,4 @@ source "drivers/misc/cardreader/Kconfig" source "drivers/misc/habanalabs/Kconfig" source "drivers/misc/uacce/Kconfig" source "drivers/misc/pvpanic/Kconfig" -source "drivers/misc/ipts/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 9e1a97b8c57e7..2be8542616dda 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -60,5 +60,4 @@ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o obj-$(CONFIG_OPEN_DICE) += open-dice.o -obj-$(CONFIG_VCPU_STALL_DETECTOR) += vcpu_stall_detector.o -obj-$(CONFIG_MISC_IPTS) += ipts/ +obj-$(CONFIG_VCPU_STALL_DETECTOR) += vcpu_stall_detector.o \ No newline at end of file diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h deleted file mode 100644 index f4b06a2d3f723..0000000000000 --- a/drivers/misc/ipts/context.h +++ /dev/null @@ -1,47 +0,0 @@ -/* 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 -#include -#include -#include - -#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 deleted file mode 100644 index a1d1f97a13d7d..0000000000000 --- a/drivers/misc/ipts/control.c +++ /dev/null @@ -1,113 +0,0 @@ -// 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 - -#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) - return 0; - - /* - * During shutdown the device might get pulled away from below our feet. - * Dont log an error in this case, because it will confuse people. - */ - if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) - dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); - - return ret; -} - -int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -{ - struct ipts_feedback_cmd cmd; - - memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); - cmd.buffer = buffer; - - return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, - sizeof(struct ipts_feedback_cmd)); -} - -int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -{ - struct ipts_feedback_buffer *feedback; - - memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); - feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; - - feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; - feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; - feedback->buffer = IPTS_HOST2ME_BUFFER; - feedback->size = 2; - feedback->payload[0] = report; - feedback->payload[1] = value; - - return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -} - -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) -{ - int ret; - - 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); - - ret = ipts_control_send_feedback(ipts, 0); - if (ret == -ENODEV) - ipts->status = IPTS_HOST_STATUS_STOPPED; - - return ret; -} - -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 deleted file mode 100644 index 2c44e9e0e99fc..0000000000000 --- a/drivers/misc/ipts/control.h +++ /dev/null @@ -1,24 +0,0 @@ -/* 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 - -#include "context.h" - -int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, - size_t size); -int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -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/protocol.h b/drivers/misc/ipts/protocol.h deleted file mode 100644 index c3458904a94da..0000000000000 --- a/drivers/misc/ipts/protocol.h +++ /dev/null @@ -1,347 +0,0 @@ -/* 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 - -/* - * 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 - -/* - * Instructs the ME to reset the touch sensor. - * - * The command must contain struct ipts_reset_sensor_cmd as payload. - * The response will not contain any payload. - */ -#define IPTS_CMD_RESET_SENSOR 0x0000000B -#define IPTS_RSP_RESET_SENSOR 0x8000000B - -/** - * enum ipts_status - Possible status codes returned by IPTS commands. - * @IPTS_STATUS_SUCCESS: Operation completed successfully. - * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. - * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. - * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. - * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. - * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. - * The host must wait for a response before sending another - * command of the same type. - * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it - * has not been initialized yet, or the system is improperly - * configured. - * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. - * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. - * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. - * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. - * The host can ignore this error and attempt to continue. - * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. - * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. - * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. - * @IPTS_STATUS_TIMEOUT: The operation timed out. - * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. - * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. - * Further progress is not possible. - * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. - * The host can attempt to continue. - * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. - * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. - */ -enum ipts_status { - IPTS_STATUS_SUCCESS = 0, - IPTS_STATUS_INVALID_PARAMS = 1, - IPTS_STATUS_ACCESS_DENIED = 2, - IPTS_STATUS_CMD_SIZE_ERROR = 3, - IPTS_STATUS_NOT_READY = 4, - IPTS_STATUS_REQUEST_OUTSTANDING = 5, - IPTS_STATUS_NO_SENSOR_FOUND = 6, - IPTS_STATUS_OUT_OF_MEMORY = 7, - IPTS_STATUS_INTERNAL_ERROR = 8, - IPTS_STATUS_SENSOR_DISABLED = 9, - IPTS_STATUS_COMPAT_CHECK_FAIL = 10, - IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, - IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, - IPTS_STATUS_RESET_FAILED = 13, - IPTS_STATUS_TIMEOUT = 14, - IPTS_STATUS_TEST_MODE_FAIL = 15, - IPTS_STATUS_SENSOR_FAIL_FATAL = 16, - IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, - IPTS_STATUS_INVALID_DEVICE_CAPS = 18, - IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -}; - -/* - * The amount of buffers that is used for IPTS - */ -#define IPTS_BUFFERS 16 - -/* - * The special buffer ID that is used for direct host2me feedback. - */ -#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS - -/** - * enum ipts_mode - Operation mode for IPTS hardware - * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. - * The data is received as a HID report with ID 64. - * @IPTS_MODE_MULTITOUCH: 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. - */ -enum ipts_mode { - IPTS_MODE_SINGLETOUCH = 0, - IPTS_MODE_MULTITOUCH = 1, -}; - -/** - * struct ipts_set_mode_cmd - Payload for the SET_MODE command. - * @mode: The mode that IPTS should operate in. - */ -struct ipts_set_mode_cmd { - enum ipts_mode mode; - u8 reserved[12]; -} __packed; - -#define IPTS_WORKQUEUE_SIZE 8192 -#define IPTS_WORKQUEUE_ITEM_SIZE 16 - -/** - * 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: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) - * @workqueue_size: Magic 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 and passed, - * otherwise the hardware will refuse to start. - */ -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; - -/** - * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. - */ -enum ipts_feedback_cmd_type { - IPTS_FEEDBACK_CMD_TYPE_NONE = 0, - IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, - IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, - IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, - IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, - IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, - IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -}; - -/** - * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. - * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. - * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. - * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. - * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. - * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. - */ -enum ipts_feedback_data_type { - IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, - IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, - IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, - IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, - IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -}; - -/** - * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. - * @cmd_type: A command that should be executed on the sensor. - * @size: The size of the payload to be written. - * @buffer: The ID of the buffer that contains this feedback data. - * @protocol: The protocol version of the EDS. - * @data_type: The type of payload that the buffer contains. - * @spi_offset: The offset at which to write the payload data. - * @payload: Payload for the feedback command, or 0 if no payload is sent. - */ -struct ipts_feedback_buffer { - enum ipts_feedback_cmd_type cmd_type; - u32 size; - u32 buffer; - u32 protocol; - enum ipts_feedback_data_type data_type; - u32 spi_offset; - u8 reserved[40]; - u8 payload[]; -} __packed; - -/** - * enum ipts_reset_type - Possible ways of resetting the touch sensor - * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. - * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. - */ -enum ipts_reset_type { - IPTS_RESET_TYPE_HARD = 0, - IPTS_RESET_TYPE_SOFT = 1, -}; - -/** - * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. - * @type: What type of reset should be performed. - */ -struct ipts_reset_sensor_cmd { - enum ipts_reset_type type; - u8 reserved[4]; -} __packed; - -/** - * struct ipts_command - A message sent from the host to the ME. - * @code: The message code describing the command. (see 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. - * @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; - enum ipts_mode mode; - u8 max_contacts; - u8 reserved[19]; -} __packed; - -/** - * struct ipts_feedback_rsp - Payload for the FEEDBACK response. - * @buffer: The buffer that has received feedback. - */ -struct ipts_feedback_rsp { - u32 buffer; -} __packed; - -/** - * struct ipts_response - A message sent from the ME to the host. - * @code: The message code describing the response. (see IPTS_RSP_*) - * @status: The status code returned by the command. - * @payload: Payload returned by the command. - */ -struct ipts_response { - u32 code; - enum ipts_status status; - u8 payload[80]; -} __packed; - -#endif /* _IPTS_PROTOCOL_H_ */ diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c deleted file mode 100644 index 23dca13c21391..0000000000000 --- a/drivers/misc/ipts/receiver.c +++ /dev/null @@ -1,224 +0,0 @@ -// 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 -#include -#include - -#include "context.h" -#include "control.h" -#include "protocol.h" -#include "resources.h" - -/* - * Temporary parameter to guard gen7 multitouch mode. - * Remove once gen7 has stable iptsd support. - */ -static bool gen7mt; -module_param(gen7mt, bool, 0644); - -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) -{ - int ret; - - 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; - - ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); - if (ret) - return ret; - - if (!gen7mt) - return 0; - - return ipts_control_set_feature(ipts, 0x5, 0x1); -} - -static int ipts_receiver_handle_feedback(struct ipts_context *ipts, - struct ipts_response *rsp) -{ - struct ipts_feedback_rsp feedback; - - if (ipts->status != IPTS_HOST_STATUS_STOPPING) - return 0; - - memcpy(&feedback, rsp->payload, sizeof(feedback)); - - if (feedback.buffer < IPTS_BUFFERS - 1) - return ipts_control_send_feedback(ipts, feedback.buffer + 1); - - return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -} - -static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -{ - ipts->status = IPTS_HOST_STATUS_STOPPED; - - if (ipts->restart) - return ipts_control_start(ipts); - - return 0; -} - -static bool ipts_receiver_sensor_was_reset(u32 status) -{ - return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || - status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -} - -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 (ipts_receiver_sensor_was_reset(rsp->status)) { - 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_FEEDBACK: - ret = ipts_receiver_handle_feedback(ipts, rsp); - 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 deleted file mode 100644 index 7f075afa7ef86..0000000000000 --- a/drivers/misc/ipts/receiver.h +++ /dev/null @@ -1,16 +0,0 @@ -/* 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 - -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 deleted file mode 100644 index 8e3a2409e4383..0000000000000 --- a/drivers/misc/ipts/resources.c +++ /dev/null @@ -1,128 +0,0 @@ -// 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 - -#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 deleted file mode 100644 index fdac0eee91562..0000000000000 --- a/drivers/misc/ipts/resources.h +++ /dev/null @@ -1,17 +0,0 @@ -/* 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 deleted file mode 100644 index 598f0710ad64a..0000000000000 --- a/drivers/misc/ipts/uapi.c +++ /dev/null @@ -1,208 +0,0 @@ -// 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 -#include -#include -#include -#include -#include - -#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 = 0; - - if (ipts) - 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 || 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 || 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; - u32 buffer; - - if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) - return -ENODEV; - - buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); - - ret = ipts_control_send_feedback(ipts, buffer); - if (ret) - return -EFAULT; - - return 0; -} - -static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -{ - int ret; - struct ipts_reset_sensor_cmd cmd; - - if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) - return -ENODEV; - - memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); - cmd.type = IPTS_RESET_TYPE_SOFT; - - ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, - sizeof(struct ipts_reset_sensor_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; - - 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); - case IPTS_IOCTL_SEND_RESET: - return ipts_uapi_ioctl_send_reset(ipts); - 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 deleted file mode 100644 index 53fb86a88f978..0000000000000 --- a/drivers/misc/ipts/uapi.h +++ /dev/null @@ -1,47 +0,0 @@ -/* 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 - -#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) -#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) - -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_ */