From 1a6a1057a25d6c46fb7da9e59d1ee4a930a56121 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Mon, 27 Jan 2020 21:16:20 +0100 Subject: [PATCH 7/7] ipts --- drivers/input/touchscreen/Kconfig | 2 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/ipts/Kconfig | 16 ++ drivers/input/touchscreen/ipts/Makefile | 17 ++ drivers/input/touchscreen/ipts/context.h | 60 ++++ drivers/input/touchscreen/ipts/control.c | 94 +++++++ drivers/input/touchscreen/ipts/control.h | 18 ++ drivers/input/touchscreen/ipts/data.c | 107 +++++++ drivers/input/touchscreen/ipts/data.h | 12 + drivers/input/touchscreen/ipts/hid.c | 38 +++ drivers/input/touchscreen/ipts/hid.h | 13 + drivers/input/touchscreen/ipts/init.c | 93 ++++++ drivers/input/touchscreen/ipts/math.c | 103 +++++++ drivers/input/touchscreen/ipts/math.h | 21 ++ drivers/input/touchscreen/ipts/params.c | 27 ++ drivers/input/touchscreen/ipts/params.h | 15 + drivers/input/touchscreen/ipts/payload.c | 52 ++++ drivers/input/touchscreen/ipts/payload.h | 14 + .../touchscreen/ipts/protocol/commands.h | 61 ++++ .../input/touchscreen/ipts/protocol/data.h | 30 ++ .../input/touchscreen/ipts/protocol/events.h | 29 ++ .../touchscreen/ipts/protocol/feedback.h | 30 ++ .../input/touchscreen/ipts/protocol/payload.h | 47 ++++ .../touchscreen/ipts/protocol/responses.h | 62 ++++ .../touchscreen/ipts/protocol/singletouch.h | 17 ++ .../input/touchscreen/ipts/protocol/stylus.h | 52 ++++ drivers/input/touchscreen/ipts/receiver.c | 265 ++++++++++++++++++ drivers/input/touchscreen/ipts/receiver.h | 8 + drivers/input/touchscreen/ipts/resources.c | 131 +++++++++ drivers/input/touchscreen/ipts/resources.h | 11 + drivers/input/touchscreen/ipts/singletouch.c | 64 +++++ drivers/input/touchscreen/ipts/singletouch.h | 14 + drivers/input/touchscreen/ipts/stylus.c | 179 ++++++++++++ drivers/input/touchscreen/ipts/stylus.h | 14 + drivers/misc/mei/hw-me-regs.h | 2 + drivers/misc/mei/pci-me.c | 2 + include/uapi/linux/input.h | 1 + 37 files changed, 1722 insertions(+) create mode 100644 drivers/input/touchscreen/ipts/Kconfig create mode 100644 drivers/input/touchscreen/ipts/Makefile create mode 100644 drivers/input/touchscreen/ipts/context.h create mode 100644 drivers/input/touchscreen/ipts/control.c create mode 100644 drivers/input/touchscreen/ipts/control.h create mode 100644 drivers/input/touchscreen/ipts/data.c create mode 100644 drivers/input/touchscreen/ipts/data.h create mode 100644 drivers/input/touchscreen/ipts/hid.c create mode 100644 drivers/input/touchscreen/ipts/hid.h create mode 100644 drivers/input/touchscreen/ipts/init.c create mode 100644 drivers/input/touchscreen/ipts/math.c create mode 100644 drivers/input/touchscreen/ipts/math.h create mode 100644 drivers/input/touchscreen/ipts/params.c create mode 100644 drivers/input/touchscreen/ipts/params.h create mode 100644 drivers/input/touchscreen/ipts/payload.c create mode 100644 drivers/input/touchscreen/ipts/payload.h create mode 100644 drivers/input/touchscreen/ipts/protocol/commands.h create mode 100644 drivers/input/touchscreen/ipts/protocol/data.h create mode 100644 drivers/input/touchscreen/ipts/protocol/events.h create mode 100644 drivers/input/touchscreen/ipts/protocol/feedback.h create mode 100644 drivers/input/touchscreen/ipts/protocol/payload.h create mode 100644 drivers/input/touchscreen/ipts/protocol/responses.h create mode 100644 drivers/input/touchscreen/ipts/protocol/singletouch.h create mode 100644 drivers/input/touchscreen/ipts/protocol/stylus.h create mode 100644 drivers/input/touchscreen/ipts/receiver.c create mode 100644 drivers/input/touchscreen/ipts/receiver.h create mode 100644 drivers/input/touchscreen/ipts/resources.c create mode 100644 drivers/input/touchscreen/ipts/resources.h create mode 100644 drivers/input/touchscreen/ipts/singletouch.c create mode 100644 drivers/input/touchscreen/ipts/singletouch.h create mode 100644 drivers/input/touchscreen/ipts/stylus.c create mode 100644 drivers/input/touchscreen/ipts/stylus.h diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index c071f7c407b61..028ff55d779d0 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1310,4 +1310,6 @@ config TOUCHSCREEN_IQS5XX To compile this driver as a module, choose M here: the module will be called iqs5xx. +source "drivers/input/touchscreen/ipts/Kconfig" + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 94c6162409b37..864f0e092ab67 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_TOUCHSCREEN_EXC3000) += exc3000.o obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix.o obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o +obj-$(CONFIG_TOUCHSCREEN_IPTS) += ipts/ obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC) += imx6ul_tsc.o obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o diff --git a/drivers/input/touchscreen/ipts/Kconfig b/drivers/input/touchscreen/ipts/Kconfig new file mode 100644 index 0000000000000..d3c530dafa948 --- /dev/null +++ b/drivers/input/touchscreen/ipts/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config TOUCHSCREEN_IPTS + tristate "Intel Precise Touch & Stylus" + select INTEL_MEI + depends on X86 + depends on PCI + depends on HID + help + Say Y here if your system has a touchscreen using Intels + Precise Touch & Stylus (IPTS). + + If unsure say N. + + To compile this driver as a module, choose M here: the + module will be called ipts. diff --git a/drivers/input/touchscreen/ipts/Makefile b/drivers/input/touchscreen/ipts/Makefile new file mode 100644 index 0000000000000..0f7c904e73171 --- /dev/null +++ b/drivers/input/touchscreen/ipts/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Makefile for the IPTS touchscreen driver +# + +obj-$(CONFIG_TOUCHSCREEN_IPTS) += ipts.o +ipts-objs := control.o +ipts-objs += data.o +ipts-objs += hid.o +ipts-objs += init.o +ipts-objs += math.o +ipts-objs += params.o +ipts-objs += payload.o +ipts-objs += receiver.o +ipts-objs += resources.o +ipts-objs += singletouch.o +ipts-objs += stylus.o diff --git a/drivers/input/touchscreen/ipts/context.h b/drivers/input/touchscreen/ipts/context.h new file mode 100644 index 0000000000000..ab26552579a5c --- /dev/null +++ b/drivers/input/touchscreen/ipts/context.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_CONTEXT_H_ +#define _IPTS_CONTEXT_H_ + +#include +#include +#include +#include + +#include "protocol/commands.h" +#include "protocol/responses.h" + +/* HACK: Workaround for DKMS build without BUS_MEI patch */ +#ifndef BUS_MEI +#define BUS_MEI 0x44 +#endif + +/* IPTS driver states */ +enum ipts_host_status { + IPTS_HOST_STATUS_NONE, + IPTS_HOST_STATUS_INIT, + IPTS_HOST_STATUS_RESOURCE_READY, + IPTS_HOST_STATUS_STARTED, + IPTS_HOST_STATUS_STOPPING, + IPTS_HOST_STATUS_RESTARTING +}; + +struct ipts_buffer_info { + u8 *address; + dma_addr_t dma_address; +}; + +struct ipts_context { + struct mei_cl_device *client_dev; + struct device *dev; + struct ipts_device_info device_info; + + enum ipts_host_status status; + enum ipts_sensor_mode mode; + + struct ipts_buffer_info data[16]; + struct ipts_buffer_info feedback[16]; + struct ipts_buffer_info doorbell; + + /* + * These buffers are not actually used by anything, but they need + * to be allocated and passed to the ME to get proper functionality. + */ + struct ipts_buffer_info workqueue; + struct ipts_buffer_info host2me; + + struct task_struct *receiver_loop; + struct task_struct *data_loop; + + struct input_dev *stylus; + struct input_dev *singletouch; +}; + +#endif /* _IPTS_CONTEXT_H_ */ diff --git a/drivers/input/touchscreen/ipts/control.c b/drivers/input/touchscreen/ipts/control.c new file mode 100644 index 0000000000000..9179eca665585 --- /dev/null +++ b/drivers/input/touchscreen/ipts/control.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "context.h" +#include "data.h" +#include "params.h" +#include "protocol/commands.h" +#include "protocol/events.h" +#include "protocol/feedback.h" +#include "resources.h" + +int ipts_control_send(struct ipts_context *ipts, + u32 cmd, void *data, u32 size) +{ + int ret; + struct ipts_command msg; + + memset(&msg, 0, sizeof(struct ipts_command)); + msg.code = cmd; + + // Copy message payload + if (data && size > 0) + memcpy(&msg.data, data, size); + + ret = mei_cldev_send(ipts->client_dev, (u8 *)&msg, + sizeof(msg.code) + size); + if (ret < 0) { + dev_err(ipts->dev, "%s: error 0x%X:%d\n", __func__, cmd, ret); + return ret; + } + + return 0; +} + +int ipts_control_send_feedback(struct ipts_context *ipts, + u32 buffer, u32 transaction) +{ + struct ipts_buffer_info feedback_buffer; + struct ipts_feedback *feedback; + struct ipts_feedback_cmd cmd; + + feedback_buffer = ipts->feedback[buffer]; + feedback = (struct ipts_feedback *)feedback_buffer.address; + + memset(feedback, 0, sizeof(struct ipts_feedback)); + memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); + + feedback->type = IPTS_FEEDBACK_TYPE_NONE; + feedback->transaction = transaction; + + cmd.buffer = buffer; + cmd.transaction = transaction; + + return ipts_control_send(ipts, IPTS_CMD(FEEDBACK), + &cmd, sizeof(struct ipts_feedback_cmd)); +} + +int ipts_control_start(struct ipts_context *ipts) +{ + ipts->status = IPTS_HOST_STATUS_INIT; + + if (ipts_params.singletouch) + ipts->mode = IPTS_SENSOR_MODE_SINGLETOUCH; + else + ipts->mode = IPTS_SENSOR_MODE_MULTITOUCH; + + return ipts_control_send(ipts, IPTS_CMD(NOTIFY_DEV_READY), NULL, 0); +} + +void ipts_control_stop(struct ipts_context *ipts) +{ + enum ipts_host_status old_status = ipts->status; + + ipts->status = IPTS_HOST_STATUS_STOPPING; + ipts_control_send(ipts, IPTS_CMD(QUIESCE_IO), NULL, 0); + ipts_control_send(ipts, IPTS_CMD(CLEAR_MEM_WINDOW), NULL, 0); + + if (old_status < IPTS_HOST_STATUS_RESOURCE_READY) + return; + + ipts_data_free(ipts); + ipts_resources_free(ipts); +} + +int ipts_control_restart(struct ipts_context *ipts) +{ + dev_info(ipts->dev, "Restarting IPTS\n"); + ipts_control_stop(ipts); + + ipts->status = IPTS_HOST_STATUS_RESTARTING; + return ipts_control_send(ipts, IPTS_CMD(QUIESCE_IO), NULL, 0); +} diff --git a/drivers/input/touchscreen/ipts/control.h b/drivers/input/touchscreen/ipts/control.h new file mode 100644 index 0000000000000..e57609c85d62a --- /dev/null +++ b/drivers/input/touchscreen/ipts/control.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_CONTROL_H_ +#define _IPTS_CONTROL_H_ + +#include + +#include "context.h" + +int ipts_control_start(struct ipts_context *ipts); +void ipts_control_stop(struct ipts_context *ipts); +int ipts_control_restart(struct ipts_context *ipts); +int ipts_control_send(struct ipts_context *ipts, + u32 cmd, void *data, u32 size); +int ipts_control_send_feedback(struct ipts_context *ipts, + u32 buffer, u32 transaction); + +#endif /* _IPTS_CONTROL_H_ */ diff --git a/drivers/input/touchscreen/ipts/data.c b/drivers/input/touchscreen/ipts/data.c new file mode 100644 index 0000000000000..568bf04f7ea6e --- /dev/null +++ b/drivers/input/touchscreen/ipts/data.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "hid.h" +#include "params.h" +#include "payload.h" +#include "protocol/data.h" + +static void ipts_data_handle_input(struct ipts_context *ipts, int buffer_id) +{ + struct ipts_buffer_info buffer; + struct ipts_data *data; + + buffer = ipts->data[buffer_id]; + data = (struct ipts_data *)buffer.address; + + if (ipts_params.debug) { + dev_info(ipts->dev, "Buffer %d\n", buffer_id); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 32, 1, + data->data, data->size, false); + } + + switch (data->type) { + case IPTS_DATA_TYPE_PAYLOAD: + ipts_payload_handle_input(ipts, data); + break; + case IPTS_DATA_TYPE_HID_REPORT: + ipts_hid_handle_input(ipts, data); + break; + default: + // ignore + break; + } + + ipts_control_send_feedback(ipts, buffer_id, data->transaction); +} + +int ipts_data_loop(void *data) +{ + time64_t timeout; + u32 doorbell; + u32 last_doorbell; + struct ipts_context *ipts; + + timeout = ktime_get_seconds() + 5; + ipts = (struct ipts_context *)data; + last_doorbell = 0; + doorbell = 0; + + dev_info(ipts->dev, "Starting data loop\n"); + + while (!kthread_should_stop()) { + if (ipts->status != IPTS_HOST_STATUS_STARTED) { + msleep(1000); + continue; + } + + // IPTS will increment the doorbell after if filled up one of + // the data buffers. If the doorbell didn't change, there is + // no work for us to do. Otherwise, the value of the doorbell + // will stand for the *next* buffer thats going to be filled. + doorbell = *(u32 *)ipts->doorbell.address; + if (doorbell == last_doorbell) + goto sleep; + + timeout = ktime_get_seconds() + 5; + + while (last_doorbell != doorbell) { + ipts_data_handle_input(ipts, last_doorbell % 16); + last_doorbell++; + } +sleep: + if (timeout > ktime_get_seconds()) + usleep_range(5000, 30000); + else + msleep(200); + } + + dev_info(ipts->dev, "Stopping data loop\n"); + return 0; +} + +int ipts_data_init(struct ipts_context *ipts) +{ + int ret; + + ret = ipts_payload_init(ipts); + if (ret) + return ret; + + ret = ipts_hid_init(ipts); + if (ret) + return ret; + + return 0; +} + +void ipts_data_free(struct ipts_context *ipts) +{ + ipts_payload_free(ipts); + ipts_hid_free(ipts); +} diff --git a/drivers/input/touchscreen/ipts/data.h b/drivers/input/touchscreen/ipts/data.h new file mode 100644 index 0000000000000..fa72c1be09451 --- /dev/null +++ b/drivers/input/touchscreen/ipts/data.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_DATA_H_ +#define _IPTS_DATA_H_ + +#include "context.h" + +int ipts_data_loop(void *data); +int ipts_data_init(struct ipts_context *ipts); +void ipts_data_free(struct ipts_context *ipts); + +#endif /* _IPTS_DATA_H_ */ diff --git a/drivers/input/touchscreen/ipts/hid.c b/drivers/input/touchscreen/ipts/hid.c new file mode 100644 index 0000000000000..2642990b8c420 --- /dev/null +++ b/drivers/input/touchscreen/ipts/hid.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "context.h" +#include "protocol/data.h" +#include "singletouch.h" + +/* + * IPTS on surface gen7 appears to make heavy use of HID reports, unlike + * previous generations. This file can be used to implement handling for + * them in the future, seperated from the actual singletouch implementation. + */ + +void ipts_hid_handle_input(struct ipts_context *ipts, struct ipts_data *data) +{ + // Make sure that we only handle singletouch inputs + // 40 is the report id of the singletouch device in the generic + // IPTS HID descriptor. + if (data->data[0] != 0x40) + return; + + ipts_singletouch_handle_input(ipts, data); +} + +int ipts_hid_init(struct ipts_context *ipts) +{ + int ret; + + ret = ipts_singletouch_init(ipts); + if (ret) + return ret; + + return 0; +} + +void ipts_hid_free(struct ipts_context *ipts) +{ + ipts_singletouch_free(ipts); +} diff --git a/drivers/input/touchscreen/ipts/hid.h b/drivers/input/touchscreen/ipts/hid.h new file mode 100644 index 0000000000000..e6cf38fce4541 --- /dev/null +++ b/drivers/input/touchscreen/ipts/hid.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_HID_H_ +#define _IPTS_HID_H_ + +#include "context.h" +#include "protocol/data.h" + +int ipts_hid_handle_input(struct ipts_context *ipts, struct ipts_data *data); +int ipts_hid_init(struct ipts_context *ipts); +void ipts_hid_free(struct ipts_context *ipts); + +#endif /* _IPTS_HID_H_ */ diff --git a/drivers/input/touchscreen/ipts/init.c b/drivers/input/touchscreen/ipts/init.c new file mode 100644 index 0000000000000..fb70d55542af7 --- /dev/null +++ b/drivers/input/touchscreen/ipts/init.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "data.h" +#include "receiver.h" + +#define IPTS_MEI_UUID UUID_LE(0x3e8d0870, 0x271a, 0x4208, \ + 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) + +static int ipts_init_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + int ret; + struct ipts_context *ipts = NULL; + + dev_info(&cldev->dev, "Probing IPTS\n"); + + // Setup the DMA bit mask + if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) { + dev_info(&cldev->dev, "IPTS using DMA_BIT_MASK(64)\n"); + } else if (!dma_coerce_mask_and_coherent(&cldev->dev, + DMA_BIT_MASK(32))) { + dev_info(&cldev->dev, "IPTS using DMA_BIT_MASK(32)"); + } else { + dev_err(&cldev->dev, "No suitable DMA for IPTS available\n"); + return -EFAULT; + } + + ret = mei_cldev_enable(cldev); + if (ret) { + dev_err(&cldev->dev, "Cannot enable IPTS\n"); + return ret; + } + + ipts = devm_kzalloc(&cldev->dev, + sizeof(struct ipts_context), GFP_KERNEL); + if (!ipts) { + mei_cldev_disable(cldev); + return -ENOMEM; + } + + ipts->client_dev = cldev; + ipts->dev = &cldev->dev; + + mei_cldev_set_drvdata(cldev, ipts); + + ipts->receiver_loop = kthread_run(ipts_receiver_loop, (void *)ipts, + "ipts_receiver_loop"); + ipts->data_loop = kthread_run(ipts_data_loop, (void *)ipts, + "ipts_data_loop"); + + ipts_control_start(ipts); + + return 0; +} + +static int ipts_init_remove(struct mei_cl_device *cldev) +{ + struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); + + dev_info(&cldev->dev, "Removing IPTS\n"); + + ipts_control_stop(ipts); + mei_cldev_disable(cldev); + kthread_stop(ipts->receiver_loop); + kthread_stop(ipts->data_loop); + + return 0; +} + +static struct mei_cl_device_id ipts_device_id[] = { + { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, + { }, +}; +MODULE_DEVICE_TABLE(mei, ipts_device_id); + +static struct mei_cl_driver ipts_driver = { + .id_table = ipts_device_id, + .name = "ipts", + .probe = ipts_init_probe, + .remove = ipts_init_remove, +}; +module_mei_cl_driver(ipts_driver); + +MODULE_DESCRIPTION("IPTS touchscreen driver"); +MODULE_AUTHOR("Dorian Stoll "); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/ipts/math.c b/drivers/input/touchscreen/ipts/math.c new file mode 100644 index 0000000000000..df956e5447e03 --- /dev/null +++ b/drivers/input/touchscreen/ipts/math.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "math.h" + +/* + * Since we need to work with [-pi, pi] in the atan functions, we are using + * 1 << 29 for the fixed point numbers. This allows us to store numbers from + * [-4, 4] using the full 32-bit signed integer range. + * + * Some constants such as PI have been already converted to the fixed-point + * format and are defined in math.h. + */ + +static inline s32 ipts_math_mul(s32 x, s32 y) +{ + return (x * (s64)y) >> 29; +} + +static inline s32 ipts_math_div(s32 x, s32 y) +{ + return ((s64)x << 29) / y; +} + +static s32 ipts_math_atan(s32 x) +{ + s32 tmp = ipts_math_mul( + ipts_math_mul(x, (abs(x) - (1 << 29))), + CONST_2447 + ipts_math_mul(CONST_0663, abs(x))); + + return ipts_math_mul(M_PI_4, x) - tmp; +} + +static s32 ipts_math_atan2(s32 y, s32 x) +{ + s32 z; + + if (x != 0) { + if (abs(x) > abs(y)) { + z = ipts_math_div(y, x); + if (x > 0) + return ipts_math_atan(z); + else if (y >= 0) + return ipts_math_atan(z) + M_PI; + else + return ipts_math_atan(z) - M_PI; + } else { + z = ipts_math_div(x, y); + if (y > 0) + return -ipts_math_atan(z) + M_PI_2; + else + return -ipts_math_atan(z) - M_PI_2; + } + } else { + if (y > 0) + return M_PI_2; + else if (y < 0) + return -M_PI_2; + } + + return 0; +} + +/* + * Convert altitude in range [0, 9000] and azimuth in range [0, 36000] + * to x-/y-tilt in range [-9000, 9000]. Azimuth is given + * counter-clockwise, starting with zero on the right. Altitude is + * given as angle between stylus and z-axis. + */ +void ipts_math_altitude_azimuth_to_tilt(s32 alt, s32 azm, s32 *tx, s32 *ty) +{ + s32 sin_alt, cos_alt; + s32 sin_azm, cos_azm; + + s32 x, y, z; + s64 atan_x, atan_y; + + sin_alt = fixp_sin32_rad(alt, 36000) / 4; + sin_azm = fixp_sin32_rad(azm, 36000) / 4; + + cos_alt = fixp_cos32_rad(alt, 36000) / 4; + cos_azm = fixp_cos32_rad(azm, 36000) / 4; + + x = ipts_math_mul(sin_alt, cos_azm); + y = ipts_math_mul(sin_alt, sin_azm); + z = cos_alt; + + atan_x = ipts_math_atan2(z, x); + atan_y = ipts_math_atan2(z, y); + + atan_x = atan_x * 4500; + atan_y = atan_y * 4500; + + atan_x = atan_x / M_PI_4; + atan_y = atan_y / M_PI_4; + + *tx = 9000 - atan_x; + *ty = atan_y - 9000; +} diff --git a/drivers/input/touchscreen/ipts/math.h b/drivers/input/touchscreen/ipts/math.h new file mode 100644 index 0000000000000..8e831074ab60b --- /dev/null +++ b/drivers/input/touchscreen/ipts/math.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_MATH_H_ +#define _IPTS_MATH_H_ + +#include + +/* (pi / 4) * (1 << 29) */ +#define M_PI_4 421657428 +#define M_PI_2 (M_PI_4 * 2) +#define M_PI (M_PI_2 * 2) + +/* 0.2447 * (1 << 29) */ +#define CONST_2447 131372312 + +/* 0.0663 * (1 << 29) */ +#define CONST_0663 35594541 + +void ipts_math_altitude_azimuth_to_tilt(s32 alt, s32 azm, s32 *tx, s32 *ty); + +#endif /* _IPTS_MATH_H_ */ diff --git a/drivers/input/touchscreen/ipts/params.c b/drivers/input/touchscreen/ipts/params.c new file mode 100644 index 0000000000000..6aa3f5cf1d762 --- /dev/null +++ b/drivers/input/touchscreen/ipts/params.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "params.h" + +#define IPTS_PARM(NAME, TYPE, PERM) \ + module_param_named(NAME, ipts_params.NAME, TYPE, PERM) + +#define IPTS_DESC(NAME, DESC) \ + MODULE_PARM_DESC(NAME, DESC) + +struct ipts_modparams ipts_params = { + .debug = false, + .singletouch = false, +}; + +IPTS_PARM(debug, bool, 0400); +IPTS_DESC(debug, + "Enable additional debugging in the IPTS driver (default: false)" +); + +IPTS_PARM(singletouch, bool, 0400); +IPTS_DESC(singletouch, + "Enables IPTS single touch mode (disables stylus) (default: false)" +); diff --git a/drivers/input/touchscreen/ipts/params.h b/drivers/input/touchscreen/ipts/params.h new file mode 100644 index 0000000000000..1f992a3bc21b9 --- /dev/null +++ b/drivers/input/touchscreen/ipts/params.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PARAMS_H_ +#define _IPTS_PARAMS_H_ + +#include + +struct ipts_modparams { + bool debug; + bool singletouch; +}; + +extern struct ipts_modparams ipts_params; + +#endif /* _IPTS_PARAMS_H_ */ diff --git a/drivers/input/touchscreen/ipts/payload.c b/drivers/input/touchscreen/ipts/payload.c new file mode 100644 index 0000000000000..3572ddc0f2fb0 --- /dev/null +++ b/drivers/input/touchscreen/ipts/payload.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "context.h" +#include "protocol/data.h" +#include "protocol/payload.h" +#include "stylus.h" + +void ipts_payload_handle_input(struct ipts_context *ipts, + struct ipts_data *data) +{ + u32 i, offset; + struct ipts_payload *payload; + struct ipts_payload_frame *frame; + + payload = (struct ipts_payload *)data->data; + offset = 0; + + for (i = 0; i < payload->num_frames; i++) { + frame = (struct ipts_payload_frame *)&payload->data[offset]; + offset += sizeof(struct ipts_payload_frame) + frame->size; + + switch (frame->type) { + case IPTS_PAYLOAD_FRAME_TYPE_STYLUS: + ipts_stylus_handle_input(ipts, frame); + break; + case IPTS_PAYLOAD_FRAME_TYPE_TOUCH: + // ignored (for the moment) + break; + default: + // ignored + break; + } + } +} + +int ipts_payload_init(struct ipts_context *ipts) +{ + int ret; + + ret = ipts_stylus_init(ipts); + if (ret) + return ret; + + return 0; +} + +void ipts_payload_free(struct ipts_context *ipts) +{ + ipts_stylus_free(ipts); +} diff --git a/drivers/input/touchscreen/ipts/payload.h b/drivers/input/touchscreen/ipts/payload.h new file mode 100644 index 0000000000000..6603714bb6fd0 --- /dev/null +++ b/drivers/input/touchscreen/ipts/payload.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PAYLOAD_H_ +#define _IPTS_PAYLOAD_H_ + +#include "context.h" +#include "protocol/data.h" + +void ipts_payload_handle_input(struct ipts_context *ipts, + struct ipts_data *data); +int ipts_payload_init(struct ipts_context *ipts); +void ipts_payload_free(struct ipts_context *ipts); + +#endif /* _IPTS_PAYLOAD_H_ */ diff --git a/drivers/input/touchscreen/ipts/protocol/commands.h b/drivers/input/touchscreen/ipts/protocol/commands.h new file mode 100644 index 0000000000000..2533dfb13584a --- /dev/null +++ b/drivers/input/touchscreen/ipts/protocol/commands.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PROTOCOL_COMMANDS_H_ +#define _IPTS_PROTOCOL_COMMANDS_H_ + +#include +#include + +enum ipts_sensor_mode { + IPTS_SENSOR_MODE_SINGLETOUCH = 0, + IPTS_SENSOR_MODE_MULTITOUCH, + IPTS_SENSOR_MODE_MAX +}; + +struct ipts_set_mode_cmd { + u32 sensor_mode; + u8 reserved[12]; +} __packed; + +struct ipts_set_mem_window_cmd { + u32 data_buffer_addr_lower[16]; + u32 data_buffer_addr_upper[16]; + u32 workqueue_addr_lower; + u32 workqueue_addr_upper; + u32 doorbell_addr_lower; + u32 doorbell_addr_upper; + u32 feedback_buffer_addr_lower[16]; + u32 feedback_buffer_addr_upper[16]; + 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 { + u32 buffer; + u32 transaction; + u8 reserved[8]; +} __packed; + +/* + * Commands are sent from the host to the ME + */ +struct ipts_command { + u32 code; + union { + struct ipts_set_mode_cmd set_mode; + struct ipts_set_mem_window_cmd set_mem_window; + struct ipts_feedback_cmd feedback; + } data; +} __packed; + +static_assert(sizeof(struct ipts_set_mode_cmd) == 16); +static_assert(sizeof(struct ipts_set_mem_window_cmd) == 320); +static_assert(sizeof(struct ipts_feedback_cmd) == 16); +static_assert(sizeof(struct ipts_command) == 324); + +#endif /* _IPTS_PROTOCOL_COMMANDS_H_ */ diff --git a/drivers/input/touchscreen/ipts/protocol/data.h b/drivers/input/touchscreen/ipts/protocol/data.h new file mode 100644 index 0000000000000..148e0545b2e4e --- /dev/null +++ b/drivers/input/touchscreen/ipts/protocol/data.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PROTOCOL_DATA_H_ +#define _IPTS_PROTOCOL_DATA_H_ + +#include +#include + +enum ipts_data_type { + IPTS_DATA_TYPE_PAYLOAD = 0, + IPTS_DATA_TYPE_ERROR, + IPTS_DATA_TYPE_VENDOR_DATA, + IPTS_DATA_TYPE_HID_REPORT, + IPTS_DATA_TYPE_GET_FEATURES, + IPTS_DATA_TYPE_MAX +}; + +struct ipts_data { + u32 type; + u32 size; + u32 buffer; + u8 reserved1[20]; + u8 transaction; + u8 reserved2[31]; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_data) == 64); + +#endif /* _IPTS_PROTOCOL_DATA_H_ */ diff --git a/drivers/input/touchscreen/ipts/protocol/events.h b/drivers/input/touchscreen/ipts/protocol/events.h new file mode 100644 index 0000000000000..f8b771f90bd2b --- /dev/null +++ b/drivers/input/touchscreen/ipts/protocol/events.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PROTOCOL_EVENTS_H_ +#define _IPTS_PROTOCOL_EVENTS_H_ + +/* + * Helpers to avoid writing boilerplate code. + * The response to a command code is always 0x8000000x, where x + * is the command code itself. Instead of writing two definitions, + * we use macros to calculate the value on the fly instead. + */ +#define IPTS_CMD(COMMAND) IPTS_EVT_##COMMAND +#define IPTS_RSP(COMMAND) (IPTS_CMD(COMMAND) + 0x80000000) + +/* + * Events that can be sent to / received from the ME + */ +enum ipts_evt_code { + IPTS_EVT_GET_DEVICE_INFO = 1, + IPTS_EVT_SET_MODE, + IPTS_EVT_SET_MEM_WINDOW, + IPTS_EVT_QUIESCE_IO, + IPTS_EVT_READY_FOR_DATA, + IPTS_EVT_FEEDBACK, + IPTS_EVT_CLEAR_MEM_WINDOW, + IPTS_EVT_NOTIFY_DEV_READY, +}; + +#endif /* _IPTS_PROTOCOL_EVENTS_H_ */ diff --git a/drivers/input/touchscreen/ipts/protocol/feedback.h b/drivers/input/touchscreen/ipts/protocol/feedback.h new file mode 100644 index 0000000000000..8b3d8b689ee83 --- /dev/null +++ b/drivers/input/touchscreen/ipts/protocol/feedback.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PROTOCOL_FEEDBACK_H_ +#define _IPTS_PROTOCOL_FEEDBACK_H_ + +#include +#include + +enum ipts_feedback_type { + IPTS_FEEDBACK_TYPE_NONE = 0, + IPTS_FEEDBACK_TYPE_SOFT_RESET, + IPTS_FEEDBACK_TYPE_GOTO_ARMED, + IPTS_FEEDBACK_TYPE_GOTO_SENSING, + IPTS_FEEDBACK_TYPE_GOTO_SLEEP, + IPTS_FEEDBACK_TYPE_GOTO_DOZE, + IPTS_FEEDBACK_TYPE_HARD_RESET, + IPTS_FEEDBACK_TYPE_MAX +}; + +struct ipts_feedback { + u32 type; + u32 size; + u32 transaction; + u8 reserved[52]; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_feedback) == 64); + +#endif /* _IPTS_PROTOCOL_FEEDBACK_H_ */ diff --git a/drivers/input/touchscreen/ipts/protocol/payload.h b/drivers/input/touchscreen/ipts/protocol/payload.h new file mode 100644 index 0000000000000..f46da4ea81f25 --- /dev/null +++ b/drivers/input/touchscreen/ipts/protocol/payload.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PROTOCOL_PAYLOAD_H_ +#define _IPTS_PROTOCOL_PAYLOAD_H_ + +#include +#include + +enum ipts_payload_frame_type { + IPTS_PAYLOAD_FRAME_TYPE_STYLUS = 6, + IPTS_PAYLOAD_FRAME_TYPE_TOUCH = 8, +}; + +enum ipts_report_type { + IPTS_REPORT_TYPE_TOUCH_HEATMAP_DIM = 0x0403, + IPTS_REPORT_TYPE_TOUCH_HEATMAP = 0x0425, + IPTS_REPORT_TYPE_STYLUS_NO_TILT = 0x0410, + IPTS_REPORT_TYPE_STYLUS_TILT = 0x0461, + IPTS_REPORT_TYPE_STYLUS_TILT_SERIAL = 0x0460, +}; + +struct ipts_payload { + u32 counter; + u32 num_frames; + u8 reserved[4]; + u8 data[]; +} __packed; + +struct ipts_payload_frame { + u16 index; + u16 type; + u32 size; + u8 reserved[8]; + u8 data[]; +} __packed; + +struct ipts_report { + u16 type; + u16 size; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_payload) == 12); +static_assert(sizeof(struct ipts_payload_frame) == 16); +static_assert(sizeof(struct ipts_report) == 4); + +#endif /* _IPTS_PROTOCOL_PAYLOAD_H_ */ diff --git a/drivers/input/touchscreen/ipts/protocol/responses.h b/drivers/input/touchscreen/ipts/protocol/responses.h new file mode 100644 index 0000000000000..27153d82a5d67 --- /dev/null +++ b/drivers/input/touchscreen/ipts/protocol/responses.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PROTOCOL_RESPONSES_H_ +#define _IPTS_PROTOCOL_RESPONSES_H_ + +#include +#include + +enum ipts_me_status { + IPTS_ME_STATUS_SUCCESS = 0, + IPTS_ME_STATUS_INVALID_PARAMS, + IPTS_ME_STATUS_ACCESS_DENIED, + IPTS_ME_STATUS_CMD_SIZE_ERROR, + IPTS_ME_STATUS_NOT_READY, + IPTS_ME_STATUS_REQUEST_OUTSTANDING, + IPTS_ME_STATUS_NO_SENSOR_FOUND, + IPTS_ME_STATUS_OUT_OF_MEMORY, + IPTS_ME_STATUS_INTERNAL_ERROR, + IPTS_ME_STATUS_SENSOR_DISABLED, + IPTS_ME_STATUS_COMPAT_CHECK_FAIL, + IPTS_ME_STATUS_SENSOR_EXPECTED_RESET, + IPTS_ME_STATUS_SENSOR_UNEXPECTED_RESET, + IPTS_ME_STATUS_RESET_FAILED, + IPTS_ME_STATUS_TIMEOUT, + IPTS_ME_STATUS_TEST_MODE_FAIL, + IPTS_ME_STATUS_SENSOR_FAIL_FATAL, + IPTS_ME_STATUS_SENSOR_FAIL_NONFATAL, + IPTS_ME_STATUS_INVALID_DEVICE_CAPS, + IPTS_ME_STATUS_QUIESCE_IO_IN_PROGRESS, + IPTS_ME_STATUS_MAX +}; + +struct ipts_device_info { + u16 vendor_id; + u16 device_id; + u32 hw_rev; + u32 fw_rev; + + /* Required size of one touch data buffer */ + u32 data_size; + + /* Required size of one feedback buffer */ + u32 feedback_size; + u8 reserved[24]; +} __packed; + +/* + * Responses are sent from the ME to the host, reacting to a command. + */ +struct ipts_response { + u32 code; + u32 status; + union { + struct ipts_device_info device_info; + u8 reserved[80]; + } data; +} __packed; + +static_assert(sizeof(struct ipts_device_info) == 44); +static_assert(sizeof(struct ipts_response) == 88); + +#endif /* _IPTS_PROTOCOL_RESPONSES_H_ */ diff --git a/drivers/input/touchscreen/ipts/protocol/singletouch.h b/drivers/input/touchscreen/ipts/protocol/singletouch.h new file mode 100644 index 0000000000000..bf9912ee2af4c --- /dev/null +++ b/drivers/input/touchscreen/ipts/protocol/singletouch.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PROTOCOL_SINGLETOUCH_H_ +#define _IPTS_PROTOCOL_SINGLETOUCH_H_ + +#include +#include + +struct ipts_singletouch_report { + u8 touch; + u16 x; + u16 y; +} __packed; + +static_assert(sizeof(struct ipts_singletouch_report) == 5); + +#endif /* _IPTS_PROTOCOL_SINGLETOUCH_H_ */ diff --git a/drivers/input/touchscreen/ipts/protocol/stylus.h b/drivers/input/touchscreen/ipts/protocol/stylus.h new file mode 100644 index 0000000000000..950850b365dfb --- /dev/null +++ b/drivers/input/touchscreen/ipts/protocol/stylus.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_PROTOCOL_STYLUS_H_ +#define _IPTS_PROTOCOL_STYLUS_H_ + +#include +#include + +struct ipts_stylus_report { + u8 reports; + u8 reserved[3]; + u8 data[]; +} __packed; + +struct ipts_stylus_report_serial { + u8 reports; + u8 reserved[3]; + u32 serial; + u8 data[]; +} __packed; + +struct ipts_stylus_report_data { + u16 timestamp; + u16 mode; + u16 x; + u16 y; + u16 pressure; + u16 altitude; + u16 azimuth; + u16 reserved; +} __packed; + +struct ipts_stylus_report_data_no_tilt { + u8 reserved[4]; + u8 mode; + u16 x; + u16 y; + u16 pressure; + u8 reserved2; +} __packed; + +#define IPTS_STYLUS_REPORT_MODE_PROX BIT(0) +#define IPTS_STYLUS_REPORT_MODE_TOUCH BIT(1) +#define IPTS_STYLUS_REPORT_MODE_BUTTON BIT(2) +#define IPTS_STYLUS_REPORT_MODE_ERASER BIT(3) + +static_assert(sizeof(struct ipts_stylus_report) == 4); +static_assert(sizeof(struct ipts_stylus_report_serial) == 8); +static_assert(sizeof(struct ipts_stylus_report_data) == 16); +static_assert(sizeof(struct ipts_stylus_report_data_no_tilt) == 12); + +#endif /* _IPTS_PAYLOAD_STYLUS_H_ */ diff --git a/drivers/input/touchscreen/ipts/receiver.c b/drivers/input/touchscreen/ipts/receiver.c new file mode 100644 index 0000000000000..ab283994c3e5f --- /dev/null +++ b/drivers/input/touchscreen/ipts/receiver.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "context.h" +#include "control.h" +#include "data.h" +#include "protocol/commands.h" +#include "protocol/events.h" +#include "protocol/responses.h" +#include "resources.h" + +static void ipts_receiver_handle_notify_dev_ready(struct ipts_context *ipts, + struct ipts_response *msg, int *cmd_status) +{ + if (msg->status != IPTS_ME_STATUS_SENSOR_FAIL_NONFATAL && + msg->status != IPTS_ME_STATUS_SUCCESS) { + dev_err(ipts->dev, "0x%08x failed - status = %d\n", + msg->code, msg->status); + return; + } + + *cmd_status = ipts_control_send(ipts, + IPTS_CMD(GET_DEVICE_INFO), NULL, 0); +} + +static void ipts_receiver_handle_get_device_info(struct ipts_context *ipts, + struct ipts_response *msg, int *cmd_status) +{ + if (msg->status != IPTS_ME_STATUS_COMPAT_CHECK_FAIL && + msg->status != IPTS_ME_STATUS_SUCCESS) { + dev_err(ipts->dev, "0x%08x failed - status = %d\n", + msg->code, msg->status); + return; + } + + memcpy(&ipts->device_info, &msg->data.device_info, + sizeof(struct ipts_device_info)); + + dev_info(ipts->dev, "Device %04hX:%04hX found\n", + ipts->device_info.vendor_id, + ipts->device_info.device_id); + + if (ipts_data_init(ipts)) + return; + + *cmd_status = ipts_control_send(ipts, + IPTS_CMD(CLEAR_MEM_WINDOW), NULL, 0); +} + +static void ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts, + struct ipts_response *msg, int *cmd_status, int *ret) +{ + struct ipts_set_mode_cmd sensor_mode_cmd; + + if (msg->status != IPTS_ME_STATUS_TIMEOUT && + msg->status != IPTS_ME_STATUS_SUCCESS) { + dev_err(ipts->dev, "0x%08x failed - status = %d\n", + msg->code, msg->status); + return; + } + + if (ipts->status == IPTS_HOST_STATUS_STOPPING) + return; + + if (ipts_resources_init(ipts)) + return; + + ipts->status = IPTS_HOST_STATUS_RESOURCE_READY; + + memset(&sensor_mode_cmd, 0, sizeof(struct ipts_set_mode_cmd)); + sensor_mode_cmd.sensor_mode = ipts->mode; + + *cmd_status = ipts_control_send(ipts, IPTS_CMD(SET_MODE), + &sensor_mode_cmd, sizeof(struct ipts_set_mode_cmd)); +} + +static void ipts_receiver_handle_set_mode(struct ipts_context *ipts, + struct ipts_response *msg, int *cmd_status) +{ + int i; + struct ipts_set_mem_window_cmd cmd; + + if (msg->status != IPTS_ME_STATUS_SUCCESS) { + dev_err(ipts->dev, "0x%08x failed - status = %d\n", + msg->code, msg->status); + return; + } + + memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); + + for (i = 0; i < 16; i++) { + cmd.data_buffer_addr_lower[i] = + lower_32_bits(ipts->data[i].dma_address); + + cmd.data_buffer_addr_upper[i] = + upper_32_bits(ipts->data[i].dma_address); + + cmd.feedback_buffer_addr_lower[i] = + lower_32_bits(ipts->feedback[i].dma_address); + + cmd.feedback_buffer_addr_upper[i] = + upper_32_bits(ipts->feedback[i].dma_address); + } + + cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); + cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); + + cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); + cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); + + cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); + cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); + cmd.host2me_size = ipts->device_info.data_size; + + cmd.workqueue_size = 8192; + cmd.workqueue_item_size = 16; + + *cmd_status = ipts_control_send(ipts, IPTS_CMD(SET_MEM_WINDOW), + &cmd, sizeof(struct ipts_set_mem_window_cmd)); +} + +static void ipts_receiver_handle_set_mem_window(struct ipts_context *ipts, + struct ipts_response *msg, int *cmd_status) +{ + if (msg->status != IPTS_ME_STATUS_SUCCESS) { + dev_err(ipts->dev, "0x%08x failed - status = %d\n", + msg->code, msg->status); + return; + } + + *cmd_status = ipts_control_send(ipts, + IPTS_CMD(READY_FOR_DATA), NULL, 0); + if (*cmd_status) + return; + + ipts->status = IPTS_HOST_STATUS_STARTED; + dev_info(ipts->dev, "IPTS enabled\n"); +} + +static void ipts_receiver_handle_ready_for_data(struct ipts_context *ipts, + struct ipts_response *msg) +{ + if (msg->status != IPTS_ME_STATUS_SENSOR_DISABLED && + msg->status != IPTS_ME_STATUS_SUCCESS) { + dev_err(ipts->dev, "0x%08x failed - status = %d\n", + msg->code, msg->status); + return; + } + + if (ipts->mode != IPTS_SENSOR_MODE_SINGLETOUCH || + ipts->status != IPTS_HOST_STATUS_STARTED) + return; + + // Increment the doorbell manually to indicate that a new buffer + // filled with touch data is available + *((u32 *)ipts->doorbell.address) += 1; +} + +static void ipts_recever_handle_feedback(struct ipts_context *ipts, + struct ipts_response *msg, int *cmd_status) +{ + if (msg->status != IPTS_ME_STATUS_COMPAT_CHECK_FAIL && + msg->status != IPTS_ME_STATUS_SUCCESS && + msg->status != IPTS_ME_STATUS_INVALID_PARAMS) { + dev_err(ipts->dev, "0x%08x failed - status = %d\n", + msg->code, msg->status); + return; + } + + if (ipts->mode != IPTS_SENSOR_MODE_SINGLETOUCH) + return; + + *cmd_status = ipts_control_send(ipts, + IPTS_CMD(READY_FOR_DATA), NULL, 0); +} + +static void ipts_receiver_handle_quiesce_io(struct ipts_context *ipts, + struct ipts_response *msg) +{ + if (msg->status != IPTS_ME_STATUS_SUCCESS) { + dev_err(ipts->dev, "0x%08x failed - status = %d\n", + msg->code, msg->status); + return; + } + + if (ipts->status == IPTS_HOST_STATUS_RESTARTING) + ipts_control_start(ipts); +} + + +static int ipts_receiver_handle_response(struct ipts_context *ipts, + struct ipts_response *msg, u32 msg_len) +{ + int cmd_status = 0; + int ret = 0; + + switch (msg->code) { + case IPTS_RSP(NOTIFY_DEV_READY): + ipts_receiver_handle_notify_dev_ready(ipts, msg, &cmd_status); + break; + case IPTS_RSP(GET_DEVICE_INFO): + ipts_receiver_handle_get_device_info(ipts, msg, &cmd_status); + break; + case IPTS_RSP(CLEAR_MEM_WINDOW): + ipts_receiver_handle_clear_mem_window(ipts, msg, + &cmd_status, &ret); + break; + case IPTS_RSP(SET_MODE): + ipts_receiver_handle_set_mode(ipts, msg, &cmd_status); + break; + case IPTS_RSP(SET_MEM_WINDOW): + ipts_receiver_handle_set_mem_window(ipts, msg, &cmd_status); + break; + case IPTS_RSP(READY_FOR_DATA): + ipts_receiver_handle_ready_for_data(ipts, msg); + break; + case IPTS_RSP(FEEDBACK): + ipts_recever_handle_feedback(ipts, msg, &cmd_status); + break; + case IPTS_RSP(QUIESCE_IO): + ipts_receiver_handle_quiesce_io(ipts, msg); + break; + } + + if (ipts->status == IPTS_HOST_STATUS_STOPPING) + return 0; + + if (msg->status == IPTS_ME_STATUS_SENSOR_UNEXPECTED_RESET || + msg->status == IPTS_ME_STATUS_SENSOR_EXPECTED_RESET) { + dev_info(ipts->dev, "Sensor has been reset: %d\n", msg->status); + ipts_control_restart(ipts); + } + + if (cmd_status) + ipts_control_restart(ipts); + + return ret; +} + +int ipts_receiver_loop(void *data) +{ + u32 msg_len; + struct ipts_context *ipts; + struct ipts_response msg; + + ipts = (struct ipts_context *)data; + dev_info(ipts->dev, "Starting receive loop\n"); + + while (!kthread_should_stop()) { + msg_len = mei_cldev_recv(ipts->client_dev, + (u8 *)&msg, sizeof(msg)); + + if (msg_len <= 0) { + dev_err(ipts->dev, "Error in reading ME message\n"); + continue; + } + + if (ipts_receiver_handle_response(ipts, &msg, msg_len)) + dev_err(ipts->dev, "Error in handling ME message\n"); + } + + dev_info(ipts->dev, "Stopping receive loop\n"); + return 0; +} diff --git a/drivers/input/touchscreen/ipts/receiver.h b/drivers/input/touchscreen/ipts/receiver.h new file mode 100644 index 0000000000000..4d413a0abd4c5 --- /dev/null +++ b/drivers/input/touchscreen/ipts/receiver.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_RECEIVER_H_ +#define _IPTS_RECEIVER_H_ + +int ipts_receiver_loop(void *data); + +#endif /* _IPTS_RECEIVER_H_ */ diff --git a/drivers/input/touchscreen/ipts/resources.c b/drivers/input/touchscreen/ipts/resources.c new file mode 100644 index 0000000000000..704db9fdd3fd4 --- /dev/null +++ b/drivers/input/touchscreen/ipts/resources.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "context.h" + +void ipts_resources_free(struct ipts_context *ipts) +{ + int i; + u32 touch_buffer_size; + u32 feedback_buffer_size; + struct ipts_buffer_info *buffers; + + touch_buffer_size = ipts->device_info.data_size; + feedback_buffer_size = ipts->device_info.feedback_size; + + buffers = ipts->data; + for (i = 0; i < 16; i++) { + if (!buffers[i].address) + continue; + + dmam_free_coherent(ipts->dev, touch_buffer_size, + buffers[i].address, buffers[i].dma_address); + + buffers[i].address = 0; + buffers[i].dma_address = 0; + } + + buffers = ipts->feedback; + for (i = 0; i < 16; i++) { + if (!buffers[i].address) + continue; + + dmam_free_coherent(ipts->dev, feedback_buffer_size, + buffers[i].address, buffers[i].dma_address); + + buffers[i].address = 0; + buffers[i].dma_address = 0; + } + + if (ipts->doorbell.address) { + dmam_free_coherent(ipts->dev, sizeof(u32), + ipts->doorbell.address, + ipts->doorbell.dma_address); + + ipts->doorbell.address = 0; + ipts->doorbell.dma_address = 0; + } + + if (ipts->workqueue.address) { + dmam_free_coherent(ipts->dev, sizeof(u32), + ipts->workqueue.address, + ipts->workqueue.dma_address); + + ipts->workqueue.address = 0; + ipts->workqueue.dma_address = 0; + } + + if (ipts->host2me.address) { + dmam_free_coherent(ipts->dev, touch_buffer_size, + ipts->host2me.address, + ipts->host2me.dma_address); + + ipts->host2me.address = 0; + ipts->host2me.dma_address = 0; + } +} + +int ipts_resources_init(struct ipts_context *ipts) +{ + int i; + u32 touch_buffer_size; + u32 feedback_buffer_size; + struct ipts_buffer_info *buffers; + + touch_buffer_size = ipts->device_info.data_size; + feedback_buffer_size = ipts->device_info.feedback_size; + + buffers = ipts->data; + for (i = 0; i < 16; i++) { + buffers[i].address = dmam_alloc_coherent(ipts->dev, + touch_buffer_size, + &buffers[i].dma_address, + GFP_ATOMIC | __GFP_ZERO); + + if (!buffers[i].address) + goto release_resources; + } + + buffers = ipts->feedback; + for (i = 0; i < 16; i++) { + buffers[i].address = dmam_alloc_coherent(ipts->dev, + feedback_buffer_size, + &buffers[i].dma_address, + GFP_ATOMIC | __GFP_ZERO); + + if (!buffers[i].address) + goto release_resources; + } + + ipts->doorbell.address = dmam_alloc_coherent(ipts->dev, + sizeof(u32), + &ipts->doorbell.dma_address, + GFP_ATOMIC | __GFP_ZERO); + + if (!ipts->doorbell.address) + goto release_resources; + + ipts->workqueue.address = dmam_alloc_coherent(ipts->dev, + sizeof(u32), + &ipts->workqueue.dma_address, + GFP_ATOMIC | __GFP_ZERO); + + if (!ipts->workqueue.address) + goto release_resources; + + ipts->host2me.address = dmam_alloc_coherent(ipts->dev, + touch_buffer_size, + &ipts->host2me.dma_address, + GFP_ATOMIC | __GFP_ZERO); + + if (!ipts->workqueue.address) + goto release_resources; + + return 0; + +release_resources: + + ipts_resources_free(ipts); + return -ENOMEM; +} diff --git a/drivers/input/touchscreen/ipts/resources.h b/drivers/input/touchscreen/ipts/resources.h new file mode 100644 index 0000000000000..cf9807b0dbe62 --- /dev/null +++ b/drivers/input/touchscreen/ipts/resources.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_RESOURCES_H_ +#define _IPTS_RESOURCES_H_ + +#include "context.h" + +int ipts_resources_init(struct ipts_context *ipts); +void ipts_resources_free(struct ipts_context *ipts); + +#endif /* _IPTS_RESOURCES_H_ */ diff --git a/drivers/input/touchscreen/ipts/singletouch.c b/drivers/input/touchscreen/ipts/singletouch.c new file mode 100644 index 0000000000000..ed70444f649c4 --- /dev/null +++ b/drivers/input/touchscreen/ipts/singletouch.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "context.h" +#include "protocol/data.h" +#include "protocol/singletouch.h" + +void ipts_singletouch_handle_input(struct ipts_context *ipts, + struct ipts_data *data) +{ + struct ipts_singletouch_report *report = + (struct ipts_singletouch_report *)&data->data[1]; + + input_report_key(ipts->singletouch, BTN_TOUCH, report->touch); + input_report_abs(ipts->singletouch, ABS_X, report->x); + input_report_abs(ipts->singletouch, ABS_Y, report->y); + + input_sync(ipts->singletouch); +} + +int ipts_singletouch_init(struct ipts_context *ipts) +{ + int ret; + + ipts->singletouch = input_allocate_device(); + if (!ipts->singletouch) + return -ENOMEM; + + __set_bit(INPUT_PROP_DIRECT, ipts->singletouch->propbit); + + input_set_capability(ipts->singletouch, EV_KEY, BTN_TOUCH); + input_set_abs_params(ipts->singletouch, ABS_X, 0, 32767, 0, 0); + input_abs_set_res(ipts->singletouch, ABS_X, 112); + input_set_abs_params(ipts->singletouch, ABS_Y, 0, 32767, 0, 0); + input_abs_set_res(ipts->singletouch, ABS_Y, 199); + + ipts->singletouch->id.bustype = BUS_MEI; + ipts->singletouch->id.vendor = ipts->device_info.vendor_id; + ipts->singletouch->id.product = ipts->device_info.device_id; + ipts->singletouch->id.version = ipts->device_info.fw_rev; + + ipts->singletouch->phys = "heci3"; + ipts->singletouch->name = "IPTS Singletouch"; + + ret = input_register_device(ipts->singletouch); + if (ret) { + dev_err(ipts->dev, "Cannot register input device: %s (%d)\n", + ipts->singletouch->name, ret); + input_free_device(ipts->singletouch); + return ret; + } + + return 0; +} + +void ipts_singletouch_free(struct ipts_context *ipts) +{ + if (!ipts->singletouch) + return; + + input_unregister_device(ipts->singletouch); +} diff --git a/drivers/input/touchscreen/ipts/singletouch.h b/drivers/input/touchscreen/ipts/singletouch.h new file mode 100644 index 0000000000000..53207497a4628 --- /dev/null +++ b/drivers/input/touchscreen/ipts/singletouch.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_SINGLETOUCH_H_ +#define _IPTS_SINGLETOUCH_H_ + +#include "context.h" +#include "protocol/data.h" + +void ipts_singletouch_handle_input(struct ipts_context *ipts, + struct ipts_data *data); +int ipts_singletouch_init(struct ipts_context *ipts); +void ipts_singletouch_free(struct ipts_context *ipts); + +#endif /* _IPTS_SINGLETOUCH_H_ */ diff --git a/drivers/input/touchscreen/ipts/stylus.c b/drivers/input/touchscreen/ipts/stylus.c new file mode 100644 index 0000000000000..987fa756fec33 --- /dev/null +++ b/drivers/input/touchscreen/ipts/stylus.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "context.h" +#include "math.h" +#include "protocol/payload.h" +#include "protocol/stylus.h" + +static void ipts_stylus_handle_stylus_data(struct ipts_context *ipts, + struct ipts_stylus_report_data *data) +{ + u8 prox = data->mode & IPTS_STYLUS_REPORT_MODE_PROX; + u8 touch = data->mode & IPTS_STYLUS_REPORT_MODE_TOUCH; + u8 button = data->mode & IPTS_STYLUS_REPORT_MODE_BUTTON; + u8 rubber = data->mode & IPTS_STYLUS_REPORT_MODE_ERASER; + + s32 tx = 0; + s32 ty = 0; + + // avoid unnecessary computations + // altitude is zero if stylus does not touch the screen + if (data->altitude) { + ipts_math_altitude_azimuth_to_tilt(data->altitude, + data->azimuth, &tx, &ty); + } + + input_report_key(ipts->stylus, BTN_TOUCH, touch); + input_report_key(ipts->stylus, BTN_TOOL_PEN, prox && !rubber); + input_report_key(ipts->stylus, BTN_TOOL_RUBBER, prox && rubber); + input_report_key(ipts->stylus, BTN_STYLUS, button); + + input_report_abs(ipts->stylus, ABS_X, data->x); + input_report_abs(ipts->stylus, ABS_Y, data->y); + input_report_abs(ipts->stylus, ABS_PRESSURE, data->pressure); + input_report_abs(ipts->stylus, ABS_MISC, data->timestamp); + + input_report_abs(ipts->stylus, ABS_TILT_X, tx); + input_report_abs(ipts->stylus, ABS_TILT_Y, ty); + + input_sync(ipts->stylus); +} + +static void ipts_stylus_handle_report_tilt_serial(struct ipts_context *ipts, + struct ipts_report *report) +{ + int i; + struct ipts_stylus_report_serial *stylus_report; + struct ipts_stylus_report_data *data; + + stylus_report = (struct ipts_stylus_report_serial *)report->data; + data = (struct ipts_stylus_report_data *)stylus_report->data; + + // TODO: Track serial number and support multiple styli + + for (i = 0; i < stylus_report->reports; i++) + ipts_stylus_handle_stylus_data(ipts, &data[i]); +} + +static void ipts_stylus_handle_report_tilt(struct ipts_context *ipts, + struct ipts_report *report) +{ + int i; + struct ipts_stylus_report *stylus_report; + struct ipts_stylus_report_data *data; + + stylus_report = (struct ipts_stylus_report *)report->data; + data = (struct ipts_stylus_report_data *)stylus_report->data; + + for (i = 0; i < stylus_report->reports; i++) + ipts_stylus_handle_stylus_data(ipts, &data[i]); +} + +static void ipts_stylus_handle_report_no_tilt(struct ipts_context *ipts, + struct ipts_report *report) +{ + int i; + struct ipts_stylus_report_serial *stylus_report; + struct ipts_stylus_report_data_no_tilt *data; + struct ipts_stylus_report_data new_data; + + stylus_report = (struct ipts_stylus_report_serial *)report->data; + data = (struct ipts_stylus_report_data_no_tilt *)stylus_report->data; + + for (i = 0; i < stylus_report->reports; i++) { + new_data.mode = data[i].mode; + new_data.x = data[i].x; + new_data.y = data[i].y; + new_data.pressure = data[i].pressure * 4; + new_data.altitude = 0; + new_data.azimuth = 0; + new_data.timestamp = 0; + + ipts_stylus_handle_stylus_data(ipts, &new_data); + } +} + +void ipts_stylus_handle_input(struct ipts_context *ipts, + struct ipts_payload_frame *frame) +{ + int size; + struct ipts_report *report; + + size = 0; + + while (size < frame->size) { + report = (struct ipts_report *)&frame->data[size]; + size += sizeof(struct ipts_report) + report->size; + + switch (report->type) { + case IPTS_REPORT_TYPE_STYLUS_NO_TILT: + ipts_stylus_handle_report_no_tilt(ipts, report); + break; + case IPTS_REPORT_TYPE_STYLUS_TILT: + ipts_stylus_handle_report_tilt(ipts, report); + break; + case IPTS_REPORT_TYPE_STYLUS_TILT_SERIAL: + ipts_stylus_handle_report_tilt_serial(ipts, report); + break; + default: + // ignored + break; + } + } +} + +int ipts_stylus_init(struct ipts_context *ipts) +{ + int ret; + + ipts->stylus = input_allocate_device(); + if (!ipts->stylus) + return -ENOMEM; + + __set_bit(INPUT_PROP_DIRECT, ipts->stylus->propbit); + __set_bit(INPUT_PROP_POINTER, ipts->stylus->propbit); + + input_set_abs_params(ipts->stylus, ABS_X, 0, 9600, 0, 0); + input_abs_set_res(ipts->stylus, ABS_X, 34); + input_set_abs_params(ipts->stylus, ABS_Y, 0, 7200, 0, 0); + input_abs_set_res(ipts->stylus, ABS_Y, 38); + input_set_abs_params(ipts->stylus, ABS_PRESSURE, 0, 4096, 0, 0); + input_set_abs_params(ipts->stylus, ABS_TILT_X, -9000, 9000, 0, 0); + input_abs_set_res(ipts->stylus, ABS_TILT_X, 5730); + input_set_abs_params(ipts->stylus, ABS_TILT_Y, -9000, 9000, 0, 0); + input_abs_set_res(ipts->stylus, ABS_TILT_Y, 5730); + input_set_abs_params(ipts->stylus, ABS_MISC, 0, 65535, 0, 0); + input_set_capability(ipts->stylus, EV_KEY, BTN_TOUCH); + input_set_capability(ipts->stylus, EV_KEY, BTN_STYLUS); + input_set_capability(ipts->stylus, EV_KEY, BTN_TOOL_PEN); + input_set_capability(ipts->stylus, EV_KEY, BTN_TOOL_RUBBER); + + ipts->stylus->id.bustype = BUS_MEI; + ipts->stylus->id.vendor = ipts->device_info.vendor_id; + ipts->stylus->id.product = ipts->device_info.device_id; + ipts->stylus->id.version = ipts->device_info.fw_rev; + + ipts->stylus->phys = "heci3"; + ipts->stylus->name = "IPTS Stylus"; + + ret = input_register_device(ipts->stylus); + if (ret) { + dev_err(ipts->dev, "Cannot register input device: %s (%d)\n", + ipts->stylus->name, ret); + input_free_device(ipts->stylus); + return ret; + } + + return 0; +} + +void ipts_stylus_free(struct ipts_context *ipts) +{ + if (!ipts->stylus) + return; + + input_unregister_device(ipts->stylus); +} diff --git a/drivers/input/touchscreen/ipts/stylus.h b/drivers/input/touchscreen/ipts/stylus.h new file mode 100644 index 0000000000000..5b93add1eac2d --- /dev/null +++ b/drivers/input/touchscreen/ipts/stylus.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _IPTS_STYLUS_H_ +#define _IPTS_STYLUS_H_ + +#include "context.h" +#include "protocol/payload.h" + +void ipts_stylus_handle_input(struct ipts_context *ipts, + struct ipts_payload_frame *frame); +int ipts_stylus_init(struct ipts_context *ipts); +void ipts_stylus_free(struct ipts_context *ipts); + +#endif /* _IPTS_STYLUS_H_ */ diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index 5213eacc8b861..ad78fa7d72fd4 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -59,6 +59,7 @@ #define MEI_DEV_ID_SPT 0x9D3A /* Sunrise Point */ #define MEI_DEV_ID_SPT_2 0x9D3B /* Sunrise Point 2 */ +#define MEI_DEV_ID_SPT_4 0x9D3E /* Sunrise Point 4 (iTouch) */ #define MEI_DEV_ID_SPT_H 0xA13A /* Sunrise Point H */ #define MEI_DEV_ID_SPT_H_2 0xA13B /* Sunrise Point H 2 */ @@ -90,6 +91,7 @@ #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ +#define MEI_DEV_ID_ICP_LP_4 0x34E4 /* Ice Lake Point LP 4 (iTouch) */ #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 0dd2922aa06d8..8f00f6b1bafed 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -77,6 +77,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_SPT, MEI_ME_PCH8_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_2, MEI_ME_PCH8_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_4, MEI_ME_PCH8_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H, MEI_ME_PCH8_SPS_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H_2, MEI_ME_PCH8_SPS_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_LBG, MEI_ME_PCH12_SPS_CFG)}, @@ -103,6 +104,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_4, MEI_ME_PCH12_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index 9a61c28ed3ae4..47fc20975245d 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -271,6 +271,7 @@ struct input_mask { #define BUS_RMI 0x1D #define BUS_CEC 0x1E #define BUS_INTEL_ISHTP 0x1F +#define BUS_MEI 0x44 /* * MT_TOOL types -- 2.26.2