From 4617721c502b2ddaa4e324e86da4997edf738fa5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 09:55:01 -0400 Subject: ftrace: Synchronize the initialization of ftrace_ops There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- drivers/hid/intel-thc-hid/Kconfig | 43 + drivers/hid/intel-thc-hid/Makefile | 23 + .../intel-thc-hid/intel-quicki2c/pci-quicki2c.c | 1087 ++++++++++++ .../intel-thc-hid/intel-quicki2c/quicki2c-dev.h | 230 +++ .../intel-thc-hid/intel-quicki2c/quicki2c-hid.c | 174 ++ .../intel-thc-hid/intel-quicki2c/quicki2c-hid.h | 14 + .../intel-quicki2c/quicki2c-protocol.c | 265 +++ .../intel-quicki2c/quicki2c-protocol.h | 21 + .../intel-thc-hid/intel-quickspi/pci-quickspi.c | 1090 ++++++++++++ .../intel-thc-hid/intel-quickspi/quickspi-dev.h | 185 ++ .../intel-thc-hid/intel-quickspi/quickspi-hid.c | 165 ++ .../intel-thc-hid/intel-quickspi/quickspi-hid.h | 14 + .../intel-quickspi/quickspi-protocol.c | 426 +++++ .../intel-quickspi/quickspi-protocol.h | 25 + .../hid/intel-thc-hid/intel-thc/intel-thc-dev.c | 1787 ++++++++++++++++++++ .../hid/intel-thc-hid/intel-thc/intel-thc-dev.h | 152 ++ .../hid/intel-thc-hid/intel-thc/intel-thc-dma.c | 1067 ++++++++++++ .../hid/intel-thc-hid/intel-thc/intel-thc-dma.h | 157 ++ drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h | 893 ++++++++++ .../hid/intel-thc-hid/intel-thc/intel-thc-wot.c | 94 + .../hid/intel-thc-hid/intel-thc/intel-thc-wot.h | 26 + 21 files changed, 7938 insertions(+) create mode 100644 drivers/hid/intel-thc-hid/Kconfig create mode 100644 drivers/hid/intel-thc-hid/Makefile create mode 100644 drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c create mode 100644 drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h create mode 100644 drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c create mode 100644 drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.h create mode 100644 drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c create mode 100644 drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h create mode 100644 drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c create mode 100644 drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h create mode 100644 drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c create mode 100644 drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.h create mode 100644 drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c create mode 100644 drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h create mode 100644 drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c create mode 100644 drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h create mode 100644 drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c create mode 100644 drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h create mode 100644 drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h create mode 100644 drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.c create mode 100644 drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.h (limited to 'drivers/hid/intel-thc-hid') diff --git a/drivers/hid/intel-thc-hid/Kconfig b/drivers/hid/intel-thc-hid/Kconfig new file mode 100644 index 000000000..9d74e53b8 --- /dev/null +++ b/drivers/hid/intel-thc-hid/Kconfig @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2024, Intel Corporation. + +menu "Intel THC HID Support" + depends on X86_64 && PCI + +config INTEL_THC_HID + tristate "Intel Touch Host Controller" + depends on ACPI + select SGL_ALLOC + help + THC (Touch Host Controller) is the name of the IP block in PCH that + interfaces with Touch Devices (ex: touchscreen, touchpad etc.). It + is comprised of 3 key functional blocks: A natively half-duplex + Quad I/O capable SPI master; a low latency I2C interface to support + HIDI2C compliant devices; a hardware sequencer with Read/Write DMA + capability to system memory. + + Say Y/M here if you want to support Intel THC. If unsure, say N. + +config INTEL_QUICKSPI + tristate "Intel QuickSPI driver based on Intel Touch Host Controller" + depends on INTEL_THC_HID + help + Intel QuickSPI, based on Touch Host Controller (THC), implements + HIDSPI (HID over SPI) protocol. It configures THC to work at SPI + mode, and controls THC hardware sequencer to accelerate HIDSPI + transaction flow. + + Say Y/M here if you want to support Intel QuickSPI. If unsure, say N. + +config INTEL_QUICKI2C + tristate "Intel QuickI2C driver based on Intel Touch Host Controller" + depends on INTEL_THC_HID + help + Intel QuickI2C, uses Touch Host Controller (THC) hardware, implements + HIDI2C (HID over I2C) protocol. It configures THC to work in I2C + mode, and controls THC hardware sequencer to accelerate HIDI2C + transaction flow. + + Say Y/M here if you want to support Intel QuickI2C. If unsure, say N. + +endmenu diff --git a/drivers/hid/intel-thc-hid/Makefile b/drivers/hid/intel-thc-hid/Makefile new file mode 100644 index 000000000..f1182253b --- /dev/null +++ b/drivers/hid/intel-thc-hid/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile - Intel Touch Host Controller (THC) drivers +# Copyright (c) 2024, Intel Corporation. +# +# + +obj-$(CONFIG_INTEL_THC_HID) += intel-thc.o +intel-thc-objs += intel-thc/intel-thc-dev.o +intel-thc-objs += intel-thc/intel-thc-dma.o +intel-thc-objs += intel-thc/intel-thc-wot.o + +obj-$(CONFIG_INTEL_QUICKSPI) += intel-quickspi.o +intel-quickspi-objs += intel-quickspi/pci-quickspi.o +intel-quickspi-objs += intel-quickspi/quickspi-hid.o +intel-quickspi-objs += intel-quickspi/quickspi-protocol.o + +obj-$(CONFIG_INTEL_QUICKI2C) += intel-quicki2c.o +intel-quicki2c-objs += intel-quicki2c/pci-quicki2c.o +intel-quicki2c-objs += intel-quicki2c/quicki2c-hid.o +intel-quicki2c-objs += intel-quicki2c/quicki2c-protocol.o + +ccflags-y += -I $(src)/intel-thc diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c new file mode 100644 index 000000000..0d2ad7bc3 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -0,0 +1,1087 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "intel-thc-dev.h" +#include "intel-thc-hw.h" +#include "intel-thc-wot.h" + +#include "quicki2c-dev.h" +#include "quicki2c-hid.h" +#include "quicki2c-protocol.h" + +static struct quicki2c_ddata ptl_ddata = { + .max_detect_size = MAX_RX_DETECT_SIZE_PTL, + .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY, +}; + +static struct quicki2c_ddata nvl_ddata = { + .max_detect_size = MAX_RX_DETECT_SIZE_NVL, + .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY, +}; + +/* THC QuickI2C ACPI method to get device properties */ +/* HIDI2C device method */ +static guid_t i2c_hid_guid = + GUID_INIT(0x3cdff6f7, 0x4267, 0x4555, 0xad, 0x05, 0xb3, 0x0a, 0x3d, 0x89, 0x38, 0xde); + +/* platform method */ +static guid_t thc_platform_guid = + GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); + +/* QuickI2C Wake-on-Touch GPIO resource */ +static const struct acpi_gpio_params wake_gpio = { 0, 0, true }; + +static const struct acpi_gpio_mapping quicki2c_gpios[] = { + { "wake-on-touch", &wake_gpio, 1 }, + { } +}; + +/** + * quicki2c_acpi_get_dsm_property - Query device ACPI DSM parameter + * @adev: Point to ACPI device + * @guid: ACPI method's guid + * @rev: ACPI method's revision + * @func: ACPI method's function number + * @type: ACPI parameter's data type + * @prop_buf: Point to return buffer + * + * This is a helper function for device to query its ACPI DSM parameters. + * + * Return: 0 if success or ENODEV on failure. + */ +static int quicki2c_acpi_get_dsm_property(struct acpi_device *adev, const guid_t *guid, + u64 rev, u64 func, acpi_object_type type, void *prop_buf) +{ + acpi_handle handle = acpi_device_handle(adev); + union acpi_object *obj; + + obj = acpi_evaluate_dsm_typed(handle, guid, rev, func, NULL, type); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call failed, rev: %d, func: %d, type: %d\n", + (int)rev, (int)func, (int)type); + return -ENODEV; + } + + if (type == ACPI_TYPE_INTEGER) + *(u32 *)prop_buf = (u32)obj->integer.value; + else if (type == ACPI_TYPE_BUFFER) + memcpy(prop_buf, obj->buffer.pointer, obj->buffer.length); + + ACPI_FREE(obj); + + return 0; +} + +/** + * quicki2c_acpi_get_dsd_property - Query device ACPI DSD parameter + * @adev: Point to ACPI device + * @dsd_method_name: ACPI method's property name + * @type: ACPI parameter's data type + * @prop_buf: Point to return buffer + * + * This is a helper function for device to query its ACPI DSD parameters. + * + * Return: 0 if success or ENODEV on failed. + */ +static int quicki2c_acpi_get_dsd_property(struct acpi_device *adev, acpi_string dsd_method_name, + acpi_object_type type, void *prop_buf) +{ + acpi_handle handle = acpi_device_handle(adev); + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *ret_obj; + acpi_status status; + + status = acpi_evaluate_object(handle, dsd_method_name, NULL, &buffer); + if (ACPI_FAILURE(status)) { + acpi_handle_err(handle, + "Can't evaluate %s method: %d\n", dsd_method_name, status); + return -ENODEV; + } + + ret_obj = buffer.pointer; + + memcpy(prop_buf, ret_obj->buffer.pointer, ret_obj->buffer.length); + + return 0; +} + +/** + * quicki2c_get_acpi_resources - Query all QuickI2C devices' ACPI parameters + * @qcdev: Point to quicki2c_device structure + * + * This function gets all QuickI2C devices' ACPI resource. + * + * Return: 0 if success or error code on failure. + */ +static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev) +{ + struct acpi_device *adev = ACPI_COMPANION(qcdev->dev); + struct quicki2c_subip_acpi_parameter i2c_param; + struct quicki2c_subip_acpi_config i2c_config; + u32 hid_desc_addr; + int ret = -EINVAL; + + if (!adev) { + dev_err(qcdev->dev, "Invalid acpi device pointer\n"); + return ret; + } + + qcdev->acpi_dev = adev; + + ret = quicki2c_acpi_get_dsm_property(adev, &i2c_hid_guid, + QUICKI2C_ACPI_REVISION_NUM, + QUICKI2C_ACPI_FUNC_NUM_HID_DESC_ADDR, + ACPI_TYPE_INTEGER, + &hid_desc_addr); + if (ret) + return ret; + + qcdev->hid_desc_addr = (u16)hid_desc_addr; + + ret = quicki2c_acpi_get_dsm_property(adev, &thc_platform_guid, + QUICKI2C_ACPI_REVISION_NUM, + QUICKI2C_ACPI_FUNC_NUM_ACTIVE_LTR_VAL, + ACPI_TYPE_INTEGER, + &qcdev->active_ltr_val); + if (ret) + return ret; + + ret = quicki2c_acpi_get_dsm_property(adev, &thc_platform_guid, + QUICKI2C_ACPI_REVISION_NUM, + QUICKI2C_ACPI_FUNC_NUM_LP_LTR_VAL, + ACPI_TYPE_INTEGER, + &qcdev->low_power_ltr_val); + if (ret) + return ret; + + ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ICRS, + ACPI_TYPE_BUFFER, &i2c_param); + if (ret) + return ret; + + if (i2c_param.addressing_mode != HIDI2C_ADDRESSING_MODE_7BIT) + return -EOPNOTSUPP; + + qcdev->i2c_config.addr_mode = HIDI2C_ADDRESSING_MODE_7BIT; + + qcdev->i2c_config.target_addr = i2c_param.device_address; + + ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ISUB, + ACPI_TYPE_BUFFER, &i2c_config); + if (ret) + return ret; + + if (i2c_param.connection_speed > 0 && + i2c_param.connection_speed <= QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED) { + qcdev->i2c_config.speed = THC_I2C_STANDARD; + qcdev->i2c_config.scl_hcnt = (u32)i2c_config.SMHX; + qcdev->i2c_config.scl_lcnt = (u32)i2c_config.SMLX; + qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.SMTD; + qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.SMRD; + } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED && + i2c_param.connection_speed <= QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED) { + qcdev->i2c_config.speed = THC_I2C_FAST_AND_PLUS; + qcdev->i2c_config.scl_hcnt = (u32)i2c_config.FMHX; + qcdev->i2c_config.scl_lcnt = (u32)i2c_config.FMLX; + qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.FMTD; + qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.FMRD; + } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED && + i2c_param.connection_speed <= QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED) { + qcdev->i2c_config.speed = THC_I2C_FAST_AND_PLUS; + qcdev->i2c_config.scl_hcnt = (u32)i2c_config.FPHX; + qcdev->i2c_config.scl_lcnt = (u32)i2c_config.FPLX; + qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.FPTD; + qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.FPRD; + } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED && + i2c_param.connection_speed <= QUICKI2C_SUBIP_HIGH_SPEED_MODE_MAX_SPEED) { + qcdev->i2c_config.speed = THC_I2C_HIGH_SPEED; + qcdev->i2c_config.scl_hcnt = (u32)i2c_config.HMHX; + qcdev->i2c_config.scl_lcnt = (u32)i2c_config.HMLX; + qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.HMTD; + qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.HMRD; + } else { + return -EOPNOTSUPP; + } + + if (qcdev->ddata) { + qcdev->i2c_max_frame_size_enable = i2c_config.FSEN; + qcdev->i2c_int_delay_enable = i2c_config.INDE; + + if (i2c_config.FSVL <= qcdev->ddata->max_detect_size) + qcdev->i2c_max_frame_size = i2c_config.FSVL; + else + qcdev->i2c_max_frame_size = qcdev->ddata->max_detect_size; + + if (i2c_config.INDV <= qcdev->ddata->max_interrupt_delay) + qcdev->i2c_int_delay = i2c_config.INDV; + else + qcdev->i2c_int_delay = qcdev->ddata->max_interrupt_delay; + } + + return 0; +} + +/** + * quicki2c_irq_quick_handler - The ISR of the QuickI2C driver + * @irq: The irq number + * @dev_id: Pointer to the quicki2c_device structure + * + * Return: IRQ_WAKE_THREAD if further process needed. + */ +static irqreturn_t quicki2c_irq_quick_handler(int irq, void *dev_id) +{ + struct quicki2c_device *qcdev = dev_id; + + if (qcdev->state == QUICKI2C_DISABLED) + return IRQ_HANDLED; + + /* Disable THC interrupt before current interrupt be handled */ + thc_interrupt_enable(qcdev->thc_hw, false); + + return IRQ_WAKE_THREAD; +} + +/** + * try_recover - Recover callback to recover THC + * @work: pointer to work_struct + * + * This function is an error handler, called when fatal error happens. + * It try to reset Touch Device and re-configure THC to recover + * transferring between Device and THC. + */ +static void try_recover(struct work_struct *work) +{ + struct quicki2c_device *qcdev = container_of(work, struct quicki2c_device, recover_work); + + if (READ_ONCE(qcdev->recovery_disabled)) + return; + + if (pm_runtime_resume_and_get(qcdev->dev)) + return; + + thc_interrupt_enable(qcdev->thc_hw, false); + + if (thc_rxdma_reset(qcdev->thc_hw)) { + qcdev->state = QUICKI2C_DISABLED; + dev_err(qcdev->dev, "RxDMA reset failed during recover, disable QuickI2C\n"); + } else { + thc_interrupt_enable(qcdev->thc_hw, true); + } + + pm_runtime_put_autosuspend(qcdev->dev); +} + +static int handle_input_report(struct quicki2c_device *qcdev) +{ + struct hidi2c_report_packet *pkt = (struct hidi2c_report_packet *)qcdev->input_buf; + int rx_dma_finished = 0; + size_t report_len; + int ret; + + while (!rx_dma_finished) { + ret = thc_rxdma_read(qcdev->thc_hw, THC_RXDMA2, + (u8 *)pkt, &report_len, + &rx_dma_finished); + if (ret) + return ret; + + if (!pkt->len) { + if (qcdev->state == QUICKI2C_RESETING) { + qcdev->reset_ack = true; + wake_up(&qcdev->reset_ack_wq); + + qcdev->state = QUICKI2C_RESETED; + } else { + dev_warn(qcdev->dev, "unexpected DIR happen\n"); + } + + continue; + } + + /* Discard samples before driver probe complete */ + if (qcdev->state != QUICKI2C_ENABLED) + continue; + + quicki2c_hid_send_report(qcdev, pkt->data, + HIDI2C_DATA_LEN(le16_to_cpu(pkt->len))); + } + + return 0; +} + +/** + * quicki2c_irq_thread_handler - IRQ thread handler of QuickI2C driver + * @irq: The IRQ number + * @dev_id: Pointer to the quicki2c_device structure + * + * Return: IRQ_HANDLED to finish this handler. + */ +static irqreturn_t quicki2c_irq_thread_handler(int irq, void *dev_id) +{ + struct quicki2c_device *qcdev = dev_id; + int err_recover = 0; + int int_mask; + int ret; + + if (qcdev->state == QUICKI2C_DISABLED) + return IRQ_HANDLED; + + ret = pm_runtime_resume_and_get(qcdev->dev); + if (ret) + return IRQ_HANDLED; + + int_mask = thc_interrupt_handler(qcdev->thc_hw); + + if (int_mask & BIT(THC_FATAL_ERR_INT) || int_mask & BIT(THC_TXN_ERR_INT) || + int_mask & BIT(THC_UNKNOWN_INT)) { + err_recover = 1; + goto exit; + } + + if (int_mask & BIT(THC_RXDMA2_INT)) { + err_recover = handle_input_report(qcdev); + if (err_recover) + goto exit; + } + +exit: + if (err_recover) + schedule_work(&qcdev->recover_work); + else + thc_interrupt_enable(qcdev->thc_hw, true); + + pm_runtime_put_autosuspend(qcdev->dev); + + return IRQ_HANDLED; +} + +/** + * quicki2c_dev_init - Initialize QuickI2C device + * @pdev: Pointer to the THC PCI device + * @mem_addr: The Pointer of MMIO memory address + * @ddata: Point to quicki2c_ddata structure + * + * Alloc quicki2c_device structure and initialized THC device, + * then configure THC to HIDI2C mode. + * + * If success, enable THC hardware interrupt. + * + * Return: Pointer to the quicki2c_device structure if success + * or NULL on failure. + */ +static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __iomem *mem_addr, + const struct quicki2c_ddata *ddata) +{ + struct device *dev = &pdev->dev; + struct quicki2c_device *qcdev; + int ret; + + qcdev = devm_kzalloc(dev, sizeof(struct quicki2c_device), GFP_KERNEL); + if (!qcdev) + return ERR_PTR(-ENOMEM); + + qcdev->pdev = pdev; + qcdev->dev = dev; + qcdev->mem_addr = mem_addr; + qcdev->state = QUICKI2C_DISABLED; + qcdev->ddata = ddata; + + init_waitqueue_head(&qcdev->reset_ack_wq); + WRITE_ONCE(qcdev->recovery_disabled, false); + INIT_WORK(&qcdev->recover_work, try_recover); + + /* THC hardware init */ + qcdev->thc_hw = thc_dev_init(qcdev->dev, qcdev->mem_addr); + if (IS_ERR(qcdev->thc_hw)) { + ret = PTR_ERR(qcdev->thc_hw); + dev_err_once(dev, "Failed to initialize THC device context, ret = %d.\n", ret); + return ERR_PTR(ret); + } + + ret = quicki2c_get_acpi_resources(qcdev); + if (ret) { + dev_err_once(dev, "Get ACPI resources failed, ret = %d\n", ret); + return ERR_PTR(ret); + } + + ret = thc_interrupt_quiesce(qcdev->thc_hw, true); + if (ret) + return ERR_PTR(ret); + + ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C); + if (ret) { + dev_err_once(dev, "Failed to select THC port, ret = %d.\n", ret); + return ERR_PTR(ret); + } + + ret = thc_i2c_subip_init(qcdev->thc_hw, &qcdev->i2c_config); + if (ret) + return ERR_PTR(ret); + + thc_int_trigger_type_select(qcdev->thc_hw, false); + + thc_interrupt_config(qcdev->thc_hw); + + thc_interrupt_enable(qcdev->thc_hw, true); + + thc_wot_config(qcdev->thc_hw, &quicki2c_gpios[0]); + + qcdev->state = QUICKI2C_INITED; + + return qcdev; +} + +/** + * quicki2c_dev_deinit - De-initialize QuickI2C device + * @qcdev: Pointer to the quicki2c_device structure + * + * Disable THC interrupt and deinitilize THC. + */ +static void quicki2c_dev_deinit(struct quicki2c_device *qcdev) +{ + WRITE_ONCE(qcdev->recovery_disabled, true); + cancel_work_sync(&qcdev->recover_work); + + thc_interrupt_quiesce(qcdev->thc_hw, true); + thc_interrupt_enable(qcdev->thc_hw, false); + thc_ltr_unconfig(qcdev->thc_hw); + thc_wot_unconfig(qcdev->thc_hw); + + qcdev->state = QUICKI2C_DISABLED; +} + +/** + * quicki2c_dma_adv_enable - Configure and enable DMA advanced features + * @qcdev: Pointer to the quicki2c_device structure + * + * If platform supports THC DMA advanced features, such as max input size + * control or interrupt delay, configures and enables them. + */ +static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev) +{ + /* + * If platform supports max input size control feature and touch device + * max input length <= THC detect capability, enable the feature with device + * max input length. + */ + if (qcdev->i2c_max_frame_size_enable) { + if (qcdev->i2c_max_frame_size >= + le16_to_cpu(qcdev->dev_desc.max_input_len)) { + thc_i2c_set_rx_max_size(qcdev->thc_hw, + le16_to_cpu(qcdev->dev_desc.max_input_len)); + } else { + dev_warn(qcdev->dev, + "Max frame size is smaller than hid max input length!"); + thc_i2c_set_rx_max_size(qcdev->thc_hw, + qcdev->i2c_max_frame_size); + } + thc_i2c_rx_max_size_enable(qcdev->thc_hw, true); + } + + /* If platform supports interrupt delay feature, enable it with given delay */ + if (qcdev->i2c_int_delay_enable) { + thc_i2c_set_rx_int_delay(qcdev->thc_hw, + qcdev->i2c_int_delay * 10); + thc_i2c_rx_int_delay_enable(qcdev->thc_hw, true); + } +} + +/** + * quicki2c_dma_adv_disable - Disable DMA advanced features + * @qcdev: Pointer to the quicki2c device structure + * + * Disable all DMA advanced features if platform supports. + */ +static void quicki2c_dma_adv_disable(struct quicki2c_device *qcdev) +{ + if (qcdev->i2c_max_frame_size_enable) + thc_i2c_rx_max_size_enable(qcdev->thc_hw, false); + + if (qcdev->i2c_int_delay_enable) + thc_i2c_rx_int_delay_enable(qcdev->thc_hw, false); +} + +/** + * quicki2c_dma_init - Configure THC DMA for QuickI2C device + * @qcdev: Pointer to the quicki2c_device structure + * + * This function uses TIC's parameters(such as max input length, max output + * length) to allocate THC DMA buffers and configure THC DMA engines. + * + * Return: 0 if success or error code on failure. + */ +static int quicki2c_dma_init(struct quicki2c_device *qcdev) +{ + size_t swdma_max_len; + int ret; + + swdma_max_len = max(le16_to_cpu(qcdev->dev_desc.max_input_len), + le16_to_cpu(qcdev->dev_desc.report_desc_len)); + + ret = thc_dma_set_max_packet_sizes(qcdev->thc_hw, 0, + le16_to_cpu(qcdev->dev_desc.max_input_len), + le16_to_cpu(qcdev->dev_desc.max_output_len), + swdma_max_len); + if (ret) + return ret; + + ret = thc_dma_allocate(qcdev->thc_hw); + if (ret) { + dev_err(qcdev->dev, "Allocate THC DMA buffer failed, ret = %d\n", ret); + return ret; + } + + /* Enable RxDMA */ + ret = thc_dma_configure(qcdev->thc_hw); + if (ret) { + dev_err(qcdev->dev, "Configure THC DMA failed, ret = %d\n", ret); + thc_dma_unconfigure(qcdev->thc_hw); + thc_dma_release(qcdev->thc_hw); + return ret; + } + + if (qcdev->ddata) + quicki2c_dma_adv_enable(qcdev); + + return 0; +} + +/** + * quicki2c_dma_deinit - Release THC DMA for QuickI2C device + * @qcdev: Pointer to the quicki2c_device structure + * + * Stop THC DMA engines and release all DMA buffers. + * + */ +static void quicki2c_dma_deinit(struct quicki2c_device *qcdev) +{ + thc_dma_unconfigure(qcdev->thc_hw); + thc_dma_release(qcdev->thc_hw); + + if (qcdev->ddata) + quicki2c_dma_adv_disable(qcdev); +} + +/** + * quicki2c_alloc_report_buf - Alloc report buffers + * @qcdev: Pointer to the quicki2c_device structure + * + * Allocate report descriptor buffer, it will be used for restore TIC HID + * report descriptor. + * + * Allocate input report buffer, it will be used for receive HID input report + * data from TIC. + * + * Allocate output report buffer, it will be used for store HID output report, + * such as set feature. + * + * Return: 0 if success or error code on failure. + */ +static int quicki2c_alloc_report_buf(struct quicki2c_device *qcdev) +{ + size_t max_report_len; + + qcdev->report_descriptor = devm_kzalloc(qcdev->dev, + le16_to_cpu(qcdev->dev_desc.report_desc_len), + GFP_KERNEL); + if (!qcdev->report_descriptor) + return -ENOMEM; + + /* + * Some HIDI2C devices don't declare input/output max length correctly, + * give default 4K buffer to avoid DMA buffer overrun. + */ + max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_input_len), SZ_4K); + + qcdev->input_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL); + if (!qcdev->input_buf) + return -ENOMEM; + + if (!le16_to_cpu(qcdev->dev_desc.max_output_len)) + qcdev->dev_desc.max_output_len = cpu_to_le16(SZ_4K); + + max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_output_len), + max_report_len); + + qcdev->report_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL); + if (!qcdev->report_buf) + return -ENOMEM; + + qcdev->report_len = max_report_len; + + return 0; +} + +/* + * quicki2c_probe: QuickI2C driver probe function + * @pdev: Point to PCI device + * @id: Point to pci_device_id structure + * + * This function initializes THC and HIDI2C device, the flow is: + * - Do THC pci device initialization + * - Query HIDI2C ACPI parameters + * - Configure THC to HIDI2C mode + * - Go through HIDI2C enumeration flow + * |- Read device descriptor + * |- Reset HIDI2C device + * - Enable THC interrupt and DMA + * - Read report descriptor + * - Register HID device + * - Enable runtime power management + * + * Return 0 if success or error code on failure. + */ +static int quicki2c_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + const struct quicki2c_ddata *ddata = (const struct quicki2c_ddata *)id->driver_data; + struct quicki2c_device *qcdev; + void __iomem *mem_addr; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) { + dev_err_once(&pdev->dev, "Failed to enable PCI device, ret = %d.\n", ret); + return ret; + } + + pci_set_master(pdev); + + mem_addr = pcim_iomap_region(pdev, 0, KBUILD_MODNAME); + ret = PTR_ERR_OR_ZERO(mem_addr); + if (ret) { + dev_err_once(&pdev->dev, "Failed to get PCI regions, ret = %d.\n", ret); + goto disable_pci_device; + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err_once(&pdev->dev, "No usable DMA configuration %d\n", ret); + goto disable_pci_device; + } + } + + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); + if (ret < 0) { + dev_err_once(&pdev->dev, + "Failed to allocate IRQ vectors. ret = %d\n", ret); + goto disable_pci_device; + } + + pdev->irq = pci_irq_vector(pdev, 0); + + qcdev = quicki2c_dev_init(pdev, mem_addr, ddata); + if (IS_ERR(qcdev)) { + dev_err_once(&pdev->dev, "QuickI2C device init failed\n"); + ret = PTR_ERR(qcdev); + goto disable_pci_device; + } + + pci_set_drvdata(pdev, qcdev); + + ret = devm_request_threaded_irq(&pdev->dev, pdev->irq, + quicki2c_irq_quick_handler, + quicki2c_irq_thread_handler, + IRQF_ONESHOT, KBUILD_MODNAME, + qcdev); + if (ret) + goto dev_deinit; + + ret = quicki2c_get_device_descriptor(qcdev); + if (ret) { + dev_err(&pdev->dev, "Get device descriptor failed, ret = %d\n", ret); + goto dev_deinit; + } + + ret = quicki2c_alloc_report_buf(qcdev); + if (ret) { + dev_err(&pdev->dev, "Alloc report buffers failed, ret= %d\n", ret); + goto dev_deinit; + } + + ret = quicki2c_dma_init(qcdev); + if (ret) { + dev_err(&pdev->dev, "Setup THC DMA failed, ret= %d\n", ret); + goto dev_deinit; + } + + ret = thc_interrupt_quiesce(qcdev->thc_hw, false); + if (ret) + goto dev_deinit; + + ret = quicki2c_set_power(qcdev, HIDI2C_ON); + if (ret) { + dev_err(&pdev->dev, "Set Power On command failed, ret= %d\n", ret); + goto dev_deinit; + } + + ret = quicki2c_reset(qcdev); + if (ret) { + dev_err(&pdev->dev, "Reset HIDI2C device failed, ret= %d\n", ret); + goto dev_deinit; + } + + ret = quicki2c_get_report_descriptor(qcdev); + if (ret) { + dev_err(&pdev->dev, "Get report descriptor failed, ret = %d\n", ret); + goto dma_deinit; + } + + ret = quicki2c_hid_probe(qcdev); + if (ret) { + dev_err(&pdev->dev, "Failed to register HID device, ret = %d\n", ret); + goto dma_deinit; + } + + qcdev->state = QUICKI2C_ENABLED; + + /* Enable runtime power management */ + pm_runtime_use_autosuspend(qcdev->dev); + pm_runtime_set_autosuspend_delay(qcdev->dev, DEFAULT_AUTO_SUSPEND_DELAY_MS); + pm_runtime_put_noidle(qcdev->dev); + pm_runtime_put_autosuspend(qcdev->dev); + + dev_dbg(&pdev->dev, "QuickI2C probe success\n"); + + return 0; + +dma_deinit: + quicki2c_dma_deinit(qcdev); +dev_deinit: + quicki2c_dev_deinit(qcdev); +disable_pci_device: + pci_clear_master(pdev); + + return ret; +} + +/** + * quicki2c_remove - Device Removal Routine + * @pdev: Point to PCI device structure + * + * This is called by the PCI subsystem to alert the driver that it should + * release a PCI device. + */ +static void quicki2c_remove(struct pci_dev *pdev) +{ + struct quicki2c_device *qcdev; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return; + + quicki2c_hid_remove(qcdev); + + quicki2c_dev_deinit(qcdev); + + quicki2c_dma_deinit(qcdev); + + pm_runtime_dont_use_autosuspend(qcdev->dev); + pm_runtime_get_noresume(qcdev->dev); + + pci_clear_master(pdev); +} + +/** + * quicki2c_shutdown - Device Shutdown Routine + * @pdev: Point to PCI device structure + * + * This is called from the reboot notifier, it's a simplified version of remove + * so we go down faster. + */ +static void quicki2c_shutdown(struct pci_dev *pdev) +{ + struct quicki2c_device *qcdev; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return; + + quicki2c_dev_deinit(qcdev); + + /* Must stop DMA before reboot to avoid DMA entering into unknown state */ + quicki2c_dma_deinit(qcdev); +} + +static int quicki2c_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quicki2c_device *qcdev; + int ret; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return -ENODEV; + + if (!device_may_wakeup(qcdev->dev)) { + ret = quicki2c_set_power(qcdev, HIDI2C_SLEEP); + if (ret) + return ret; + } + + /* + * As I2C is THC subsystem, no register auto save/restore support, + * need driver to do that explicitly for every D3 case. + */ + ret = thc_i2c_subip_regs_save(qcdev->thc_hw); + if (ret) + return ret; + + WRITE_ONCE(qcdev->recovery_disabled, true); + cancel_work_sync(&qcdev->recover_work); + + ret = thc_interrupt_quiesce(qcdev->thc_hw, true); + if (ret) + return ret; + + thc_interrupt_enable(qcdev->thc_hw, false); + + thc_dma_unconfigure(qcdev->thc_hw); + + return 0; +} + +static int quicki2c_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quicki2c_device *qcdev; + int ret; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return -ENODEV; + + ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C); + if (ret) + return ret; + + ret = thc_i2c_subip_regs_restore(qcdev->thc_hw); + if (ret) + return ret; + + thc_interrupt_config(qcdev->thc_hw); + + thc_interrupt_enable(qcdev->thc_hw, true); + + ret = thc_dma_configure(qcdev->thc_hw); + if (ret) + return ret; + + ret = thc_interrupt_quiesce(qcdev->thc_hw, false); + if (ret) + return ret; + + WRITE_ONCE(qcdev->recovery_disabled, false); + + if (!device_may_wakeup(qcdev->dev)) + return quicki2c_set_power(qcdev, HIDI2C_ON); + + return 0; +} + +static int quicki2c_freeze(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quicki2c_device *qcdev; + int ret; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return -ENODEV; + + WRITE_ONCE(qcdev->recovery_disabled, true); + cancel_work_sync(&qcdev->recover_work); + + ret = thc_interrupt_quiesce(qcdev->thc_hw, true); + if (ret) + return ret; + + thc_interrupt_enable(qcdev->thc_hw, false); + + thc_dma_unconfigure(qcdev->thc_hw); + + return 0; +} + +static int quicki2c_thaw(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quicki2c_device *qcdev; + int ret; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return -ENODEV; + + ret = thc_dma_configure(qcdev->thc_hw); + if (ret) + return ret; + + thc_interrupt_enable(qcdev->thc_hw, true); + + ret = thc_interrupt_quiesce(qcdev->thc_hw, false); + if (ret) + return ret; + + WRITE_ONCE(qcdev->recovery_disabled, false); + + return 0; +} + +static int quicki2c_poweroff(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quicki2c_device *qcdev; + int ret; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return -ENODEV; + + /* Ignore the return value as platform will be poweroff soon */ + quicki2c_set_power(qcdev, HIDI2C_SLEEP); + + ret = thc_interrupt_quiesce(qcdev->thc_hw, true); + if (ret) + return ret; + + thc_interrupt_enable(qcdev->thc_hw, false); + + thc_ltr_unconfig(qcdev->thc_hw); + + quicki2c_dev_deinit(qcdev); + + quicki2c_dma_deinit(qcdev); + + return 0; +} + +static int quicki2c_restore(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quicki2c_device *qcdev; + int ret; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return -ENODEV; + + /* Reconfig THC HW when back from hibernate */ + ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C); + if (ret) + return ret; + + ret = thc_i2c_subip_init(qcdev->thc_hw, &qcdev->i2c_config); + if (ret) + return ret; + + thc_interrupt_config(qcdev->thc_hw); + + thc_interrupt_enable(qcdev->thc_hw, true); + + ret = thc_interrupt_quiesce(qcdev->thc_hw, false); + if (ret) + return ret; + + ret = thc_dma_configure(qcdev->thc_hw); + if (ret) + return ret; + + thc_ltr_config(qcdev->thc_hw, + qcdev->active_ltr_val, + qcdev->low_power_ltr_val); + + thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_ACTIVE); + + return quicki2c_set_power(qcdev, HIDI2C_ON); +} + +static int quicki2c_runtime_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quicki2c_device *qcdev; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return -ENODEV; + + thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_LP); + + pci_save_state(pdev); + + return 0; +} + +static int quicki2c_runtime_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quicki2c_device *qcdev; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return -ENODEV; + + thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_ACTIVE); + + return 0; +} + +static const struct dev_pm_ops quicki2c_pm_ops = { + .suspend = quicki2c_suspend, + .resume = quicki2c_resume, + .freeze = quicki2c_freeze, + .thaw = quicki2c_thaw, + .poweroff = quicki2c_poweroff, + .restore = quicki2c_restore, + .runtime_suspend = quicki2c_runtime_suspend, + .runtime_resume = quicki2c_runtime_resume, + .runtime_idle = NULL, +}; + +static const struct pci_device_id quicki2c_pci_tbl[] = { + { PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_I2C_PORT1, NULL) }, + { PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_I2C_PORT2, NULL) }, + { PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT1, &ptl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT2, &ptl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT1, &ptl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT2, &ptl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_I2C_PORT1, &ptl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_I2C_PORT2, &ptl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_I2C_PORT1, &nvl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_I2C_PORT2, &nvl_ddata) }, + { } +}; +MODULE_DEVICE_TABLE(pci, quicki2c_pci_tbl); + +static struct pci_driver quicki2c_driver = { + .name = KBUILD_MODNAME, + .id_table = quicki2c_pci_tbl, + .probe = quicki2c_probe, + .remove = quicki2c_remove, + .shutdown = quicki2c_shutdown, + .driver.pm = &quicki2c_pm_ops, + .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, +}; + +module_pci_driver(quicki2c_driver); + +MODULE_AUTHOR("Xinpeng Sun "); +MODULE_AUTHOR("Even Xu "); + +MODULE_DESCRIPTION("Intel(R) QuickI2C Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("INTEL_THC"); diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h new file mode 100644 index 000000000..6d25a8461 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _QUICKI2C_DEV_H_ +#define _QUICKI2C_DEV_H_ + +#include +#include + +#include "intel-thc-dev.h" + +#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT1 0xA848 +#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT2 0xA84A +#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_I2C_PORT1 0xE348 +#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_I2C_PORT2 0xE34A +#define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_I2C_PORT1 0xE448 +#define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_I2C_PORT2 0xE44A +#define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_I2C_PORT1 0x4D48 +#define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_I2C_PORT2 0x4D4A +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_I2C_PORT1 0xD348 +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_I2C_PORT2 0xD34A + +/* Packet size value, the unit is 16 bytes */ +#define MAX_PACKET_SIZE_VALUE_LNL 256 + +/* HIDI2C special ACPI parameters DSD name */ +#define QUICKI2C_ACPI_METHOD_NAME_ICRS "ICRS" +#define QUICKI2C_ACPI_METHOD_NAME_ISUB "ISUB" + +/* HIDI2C special ACPI parameters DSM methods */ +#define QUICKI2C_ACPI_REVISION_NUM 1 +#define QUICKI2C_ACPI_FUNC_NUM_HID_DESC_ADDR 1 +#define QUICKI2C_ACPI_FUNC_NUM_ACTIVE_LTR_VAL 1 +#define QUICKI2C_ACPI_FUNC_NUM_LP_LTR_VAL 2 + +#define QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED 100000 +#define QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED 400000 +#define QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED 1000000 +#define QUICKI2C_SUBIP_HIGH_SPEED_MODE_MAX_SPEED 3400000 + +#define QUICKI2C_DEFAULT_ACTIVE_LTR_VALUE 5 +#define QUICKI2C_DEFAULT_LP_LTR_VALUE 500 +#define QUICKI2C_RPM_TIMEOUT_MS 500 + +/* PTL Max packet size detection capability is 255 Bytes */ +#define MAX_RX_DETECT_SIZE_PTL 255 +/* NVL Max packet size detection capability is 64K Bytes */ +#define MAX_RX_DETECT_SIZE_NVL 65535 +/* Max interrupt delay capability is 2.56ms */ +#define MAX_RX_INTERRUPT_DELAY 256 + +/* Default interrupt delay is 1ms, suitable for most devices */ +#define DEFAULT_INTERRUPT_DELAY_US (1 * USEC_PER_MSEC) + +/* + * THC uses runtime auto suspend to dynamically switch between THC active LTR + * and low power LTR to save CPU power. + * Default value is 5000ms, that means if no touch event in this time, THC will + * change to low power LTR mode. + */ +#define DEFAULT_AUTO_SUSPEND_DELAY_MS 5000 + +enum quicki2c_dev_state { + QUICKI2C_NONE, + QUICKI2C_RESETING, + QUICKI2C_RESETED, + QUICKI2C_INITED, + QUICKI2C_ENABLED, + QUICKI2C_DISABLED, +}; + +enum { + HIDI2C_ADDRESSING_MODE_7BIT, + HIDI2C_ADDRESSING_MODE_10BIT, +}; + +/** + * struct quicki2c_subip_acpi_parameter - QuickI2C ACPI DSD parameters + * @device_address: I2C device slave address + * @connection_speed: I2C device expected connection speed + * @addressing_mode: I2C device slave address mode, 7bit or 10bit + * + * Those properties get from QUICKI2C_ACPI_METHOD_NAME_ICRS method, used for + * Bus parameter. + */ +struct quicki2c_subip_acpi_parameter { + u16 device_address; + u64 connection_speed; + u8 addressing_mode; + u8 reserved; +} __packed; + +/** + * struct quicki2c_subip_acpi_config - QuickI2C ACPI DSD parameters + * @SMHX: Standard Mode (100 kbit/s) Serial Clock Line HIGH Period + * @SMLX: Standard Mode (100 kbit/s) Serial Clock Line LOW Period + * @SMTD: Standard Mode (100 kbit/s) Serial Data Line Transmit Hold Period + * @SMRD: Standard Mode (100 kbit/s) Serial Data Receive Hold Period + * @FMHX: Fast Mode (400 kbit/s) Serial Clock Line HIGH Period + * @FMLX: Fast Mode (400 kbit/s) Serial Clock Line LOW Period + * @FMTD: Fast Mode (400 kbit/s) Serial Data Line Transmit Hold Period + * @FMRD: Fast Mode (400 kbit/s) Serial Data Line Receive Hold Period + * @FMSL: Maximum length (in ic_clk_cycles) of suppressed spikes + * in Standard Mode, Fast Mode and Fast Mode Plus + * @FPHX: Fast Mode Plus (1Mbit/sec) Serial Clock Line HIGH Period + * @FPLX: Fast Mode Plus (1Mbit/sec) Serial Clock Line LOW Period + * @FPTD: Fast Mode Plus (1Mbit/sec) Serial Data Line Transmit HOLD Period + * @FPRD: Fast Mode Plus (1Mbit/sec) Serial Data Line Receive HOLD Period + * @HMHX: High Speed Mode Plus (3.4Mbits/sec) Serial Clock Line HIGH Period + * @HMLX: High Speed Mode Plus (3.4Mbits/sec) Serial Clock Line LOW Period + * @HMTD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Transmit HOLD Period + * @HMRD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Receive HOLD Period + * @HMSL: Maximum length (in ic_clk_cycles) of suppressed spikes in High Speed Mode + * @FSEN: Maximum Frame Size Feature Enable Control + * @FSVL: Maximum Frame Size Value (unit in Bytes) + * @INDE: Interrupt Delay Feature Enable Control + * @INDV: Interrupt Delay Value (unit in 10 us) + * + * Those properties get from QUICKI2C_ACPI_METHOD_NAME_ISUB method, used for + * I2C timing configure. + */ +struct quicki2c_subip_acpi_config { + u64 SMHX; + u64 SMLX; + u64 SMTD; + u64 SMRD; + + u64 FMHX; + u64 FMLX; + u64 FMTD; + u64 FMRD; + u64 FMSL; + + u64 FPHX; + u64 FPLX; + u64 FPTD; + u64 FPRD; + + u64 HMHX; + u64 HMLX; + u64 HMTD; + u64 HMRD; + u64 HMSL; + + u64 FSEN; + u64 FSVL; + u64 INDE; + u64 INDV; + u8 reserved; +}; + +/** + * struct quicki2c_ddata - Driver specific data for quicki2c device + * @max_detect_size: Identify max packet size detect for rx + * @interrupt_delay: Identify max interrupt detect delay for rx + */ +struct quicki2c_ddata { + u32 max_detect_size; + u32 max_interrupt_delay; +}; + +struct device; +struct pci_dev; +struct hid_device; +struct acpi_device; + +/** + * struct quicki2c_device - THC QuickI2C device struct + * @dev: Point to kernel device + * @pdev: Point to PCI device + * @thc_hw: Point to THC device + * @hid_dev: Point to HID device + * @acpi_dev: Point to ACPI device + * @ddata: Point to QuickI2C platform specific driver data + * @state: THC I2C device state + * @mem_addr: MMIO memory address + * @dev_desc: Device descriptor for HIDI2C protocol + * @i2c_config: I2C bus configuration + * @hid_desc_addr: Register address for retrieve HID device descriptor + * @active_ltr_val: THC active LTR value + * @low_power_ltr_val: THC low power LTR value + * @report_descriptor: Store a copy of device report descriptor + * @input_buf: Store a copy of latest input report data + * @report_buf: Store a copy of latest input/output report packet from set/get feature + * @report_len: The length of input/output report packet + * @reset_ack_wq: Workqueue for waiting reset response from device + * @reset_ack: Indicate reset response received or not + * @i2c_max_frame_size_enable: Indicate max frame size feature enabled or not + * @i2c_max_frame_size: Max RX frame size (unit in Bytes) + * @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not + * @i2c_int_delay: Interrupt detection delay value (unit in 10 us) + * @recover_work: Work structure for recovery + * @recovery_disabled: Whether recovery work is blocked during teardown + */ +struct quicki2c_device { + struct device *dev; + struct pci_dev *pdev; + struct thc_device *thc_hw; + struct hid_device *hid_dev; + struct acpi_device *acpi_dev; + const struct quicki2c_ddata *ddata; + enum quicki2c_dev_state state; + + void __iomem *mem_addr; + + struct hidi2c_dev_descriptor dev_desc; + struct thc_i2c_config i2c_config; + u16 hid_desc_addr; + + u32 active_ltr_val; + u32 low_power_ltr_val; + + u8 *report_descriptor; + u8 *input_buf; + u8 *report_buf; + size_t report_len; + + wait_queue_head_t reset_ack_wq; + bool reset_ack; + + u32 i2c_max_frame_size_enable; + u32 i2c_max_frame_size; + u32 i2c_int_delay_enable; + u32 i2c_int_delay; + + struct work_struct recover_work; + bool recovery_disabled; +}; + +#endif /* _QUICKI2C_DEV_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c new file mode 100644 index 000000000..8075992e8 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include +#include +#include + +#include "quicki2c-dev.h" +#include "quicki2c-hid.h" +#include "quicki2c-protocol.h" + +/** + * quicki2c_hid_parse() - HID core parse() callback + * + * @hid: HID device instance + * + * This function gets called during call to hid_add_device + * + * Return: 0 on success and non zero on error. + */ +static int quicki2c_hid_parse(struct hid_device *hid) +{ + struct quicki2c_device *qcdev = hid->driver_data; + + if (qcdev->report_descriptor) + return hid_parse_report(hid, qcdev->report_descriptor, + le16_to_cpu(qcdev->dev_desc.report_desc_len)); + + dev_err_once(qcdev->dev, "invalid report descriptor\n"); + return -EINVAL; +} + +static int quicki2c_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void quicki2c_hid_stop(struct hid_device *hid) +{ +} + +static int quicki2c_hid_open(struct hid_device *hid) +{ + return 0; +} + +static void quicki2c_hid_close(struct hid_device *hid) +{ +} + +static int quicki2c_hid_raw_request(struct hid_device *hid, + unsigned char reportnum, + __u8 *buf, size_t len, + unsigned char rtype, int reqtype) +{ + struct quicki2c_device *qcdev = hid->driver_data; + int ret = 0; + + ret = pm_runtime_resume_and_get(qcdev->dev); + if (ret) + return ret; + + switch (reqtype) { + case HID_REQ_GET_REPORT: + ret = quicki2c_get_report(qcdev, rtype, reportnum, buf, len); + break; + case HID_REQ_SET_REPORT: + ret = quicki2c_set_report(qcdev, rtype, reportnum, buf, len); + break; + default: + dev_err(qcdev->dev, "Not supported request type %d\n", reqtype); + break; + } + + pm_runtime_put_autosuspend(qcdev->dev); + + return ret; +} + +static int quicki2c_hid_power(struct hid_device *hid, int lvl) +{ + return 0; +} + +static int quicki2c_hid_output_report(struct hid_device *hid, u8 *buf, size_t count) +{ + struct quicki2c_device *qcdev = hid->driver_data; + + return quicki2c_output_report(qcdev, buf, count); +} + +static struct hid_ll_driver quicki2c_hid_ll_driver = { + .parse = quicki2c_hid_parse, + .start = quicki2c_hid_start, + .stop = quicki2c_hid_stop, + .open = quicki2c_hid_open, + .close = quicki2c_hid_close, + .power = quicki2c_hid_power, + .raw_request = quicki2c_hid_raw_request, + .output_report = quicki2c_hid_output_report, +}; + +/** + * quicki2c_hid_probe() - Register HID low level driver + * + * @qcdev: point to quicki2c device + * + * This function is used to allocate and add HID device. + * + * Return: 0 on success, non zero on error. + */ +int quicki2c_hid_probe(struct quicki2c_device *qcdev) +{ + struct hid_device *hid; + int ret; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + hid->ll_driver = &quicki2c_hid_ll_driver; + hid->bus = BUS_PCI; + hid->dev.parent = qcdev->dev; + hid->driver_data = qcdev; + hid->version = le16_to_cpu(qcdev->dev_desc.version_id); + hid->vendor = le16_to_cpu(qcdev->dev_desc.vendor_id); + hid->product = le16_to_cpu(qcdev->dev_desc.product_id); + snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "quicki2c-hid", + hid->vendor, hid->product); + strscpy(hid->phys, dev_name(qcdev->dev), sizeof(hid->phys)); + + ret = hid_add_device(hid); + if (ret) { + hid_destroy_device(hid); + return ret; + } + + qcdev->hid_dev = hid; + + return 0; +} + +/** + * quicki2c_hid_remove() - Destroy HID device + * + * @qcdev: point to quicki2c device + * + * Return: 0 on success, non zero on error. + */ +void quicki2c_hid_remove(struct quicki2c_device *qcdev) +{ + hid_destroy_device(qcdev->hid_dev); +} + +/** + * quicki2c_hid_send_report() - Send HID input report data to HID core + * + * @qcdev: point to quicki2c device + * @data: point to input report data buffer + * @data_len: the length of input report data + * + * Return: 0 on success, non zero on error. + */ +int quicki2c_hid_send_report(struct quicki2c_device *qcdev, + void *data, size_t data_len) +{ + int ret; + + ret = hid_input_report(qcdev->hid_dev, HID_INPUT_REPORT, data, data_len, 1); + if (ret) + dev_err(qcdev->dev, "Failed to send HID input report, ret = %d.\n", ret); + + return ret; +} diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.h new file mode 100644 index 000000000..e80df5f33 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _QUICKI2C_HID_H_ +#define _QUICKI2C_HID_H_ + +struct quicki2c_device; + +int quicki2c_hid_send_report(struct quicki2c_device *qcdev, + void *data, size_t data_size); +int quicki2c_hid_probe(struct quicki2c_device *qcdev); +void quicki2c_hid_remove(struct quicki2c_device *qcdev); + +#endif /* _QUICKI2C_HID_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c new file mode 100644 index 000000000..412713012 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2024 Intel Corporation */ + +#include +#include +#include +#include + +#include "intel-thc-dev.h" +#include "intel-thc-dma.h" + +#include "quicki2c-dev.h" +#include "quicki2c-hid.h" +#include "quicki2c-protocol.h" + +static ssize_t quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, size_t cmd_len, + bool append_data_reg, u8 *data, size_t data_len, + u8 *write_buf, size_t write_buf_len) +{ + size_t buf_len, offset = 0; + + buf_len = HIDI2C_REG_LEN + cmd_len; + + if (append_data_reg) + buf_len += HIDI2C_REG_LEN; + + if (data && data_len) + buf_len += data_len + HIDI2C_LENGTH_LEN; + + if (buf_len > write_buf_len) + return -EINVAL; + + if (cmd_len) { + memcpy(write_buf, &qcdev->dev_desc.cmd_reg, HIDI2C_REG_LEN); + offset += HIDI2C_REG_LEN; + memcpy(write_buf + offset, &cmd, cmd_len); + offset += cmd_len; + + if (append_data_reg) { + memcpy(write_buf + offset, &qcdev->dev_desc.data_reg, HIDI2C_REG_LEN); + offset += HIDI2C_REG_LEN; + } + } else { + memcpy(write_buf, &qcdev->dev_desc.output_reg, HIDI2C_REG_LEN); + offset += HIDI2C_REG_LEN; + } + + if (data && data_len) { + put_unaligned_le16(data_len + HIDI2C_LENGTH_LEN, write_buf + offset); + offset += HIDI2C_LENGTH_LEN; + memcpy(write_buf + offset, data, data_len); + } + + return buf_len; +} + +static size_t quicki2c_encode_cmd(struct quicki2c_device *qcdev, u32 *cmd_buf, + u8 opcode, u8 report_type, u8 report_id) +{ + size_t cmd_len; + + *cmd_buf = FIELD_PREP(HIDI2C_CMD_OPCODE, opcode) | + FIELD_PREP(HIDI2C_CMD_REPORT_TYPE, report_type); + + if (report_id < HIDI2C_CMD_MAX_RI) { + *cmd_buf |= FIELD_PREP(HIDI2C_CMD_REPORT_ID, report_id); + cmd_len = HIDI2C_CMD_LEN; + } else { + *cmd_buf |= FIELD_PREP(HIDI2C_CMD_REPORT_ID, HIDI2C_CMD_MAX_RI) | + FIELD_PREP(HIDI2C_CMD_3RD_BYTE, report_id); + cmd_len = HIDI2C_CMD_LEN_OPT; + } + + return cmd_len; +} + +static int write_cmd_to_txdma(struct quicki2c_device *qcdev, int opcode, + int report_type, int report_id, u8 *buf, size_t buf_len) +{ + size_t cmd_len; + ssize_t len; + u32 cmd; + + cmd_len = quicki2c_encode_cmd(qcdev, &cmd, opcode, report_type, report_id); + + len = quicki2c_init_write_buf(qcdev, cmd, cmd_len, buf ? true : false, buf, + buf_len, qcdev->report_buf, qcdev->report_len); + if (len < 0) + return len; + + return thc_dma_write(qcdev->thc_hw, qcdev->report_buf, len); +} + +int quicki2c_set_power(struct quicki2c_device *qcdev, enum hidi2c_power_state power_state) +{ + return write_cmd_to_txdma(qcdev, HIDI2C_SET_POWER, HIDI2C_RESERVED, power_state, NULL, 0); +} + +int quicki2c_get_device_descriptor(struct quicki2c_device *qcdev) +{ + u32 read_len = 0; + int ret; + + ret = thc_tic_pio_write_and_read(qcdev->thc_hw, qcdev->hid_desc_addr, + HIDI2C_REG_LEN, NULL, HIDI2C_DEV_DESC_LEN, + &read_len, (u32 *)&qcdev->dev_desc); + if (ret || HIDI2C_DEV_DESC_LEN != read_len) { + dev_err_once(qcdev->dev, "Get device descriptor failed, ret %d, read len %u\n", + ret, read_len); + return -EIO; + } + + if (le16_to_cpu(qcdev->dev_desc.bcd_ver) != HIDI2C_HID_DESC_BCDVERSION) + return -EOPNOTSUPP; + + return 0; +} + +int quicki2c_get_report_descriptor(struct quicki2c_device *qcdev) +{ + u16 desc_reg = le16_to_cpu(qcdev->dev_desc.report_desc_reg); + size_t read_len = le16_to_cpu(qcdev->dev_desc.report_desc_len); + u32 prd_len = read_len; + + return thc_swdma_read(qcdev->thc_hw, (u8 *)&desc_reg, HIDI2C_REG_LEN, + &prd_len, qcdev->report_descriptor, &read_len); +} + +int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type, + unsigned int reportnum, void *buf, size_t buf_len) +{ + struct hidi2c_report_packet *rpt; + size_t cmd_len, read_len = 0; + int rep_type, ret; + ssize_t len; + u32 cmd; + + if (report_type == HID_INPUT_REPORT) { + rep_type = HIDI2C_INPUT; + } else if (report_type == HID_FEATURE_REPORT) { + rep_type = HIDI2C_FEATURE; + } else { + dev_err(qcdev->dev, "Unsupported report type for GET REPORT: %d\n", report_type); + return -EINVAL; + } + + cmd_len = quicki2c_encode_cmd(qcdev, &cmd, HIDI2C_GET_REPORT, rep_type, reportnum); + + len = quicki2c_init_write_buf(qcdev, cmd, cmd_len, true, NULL, 0, + qcdev->report_buf, qcdev->report_len); + if (len < 0) + return len; + + rpt = (struct hidi2c_report_packet *)qcdev->input_buf; + + ret = thc_swdma_read(qcdev->thc_hw, qcdev->report_buf, len, NULL, rpt, &read_len); + if (ret) { + dev_err_once(qcdev->dev, "Get report failed, ret %d, read len (%zu vs %zu)\n", + ret, read_len, buf_len); + return ret; + } + + if (HIDI2C_DATA_LEN(le16_to_cpu(rpt->len)) != buf_len || rpt->data[0] != reportnum) { + dev_err_once(qcdev->dev, "Invalid packet, len (%d vs %zu) report id (%d vs %d)\n", + le16_to_cpu(rpt->len), buf_len, rpt->data[0], reportnum); + return -EINVAL; + } + + memcpy(buf, rpt->data, buf_len); + + return buf_len; +} + +int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type, + unsigned int reportnum, void *buf, size_t buf_len) +{ + int rep_type; + int ret; + + if (report_type == HID_OUTPUT_REPORT) { + rep_type = HIDI2C_OUTPUT; + } else if (report_type == HID_FEATURE_REPORT) { + rep_type = HIDI2C_FEATURE; + } else { + dev_err(qcdev->dev, "Unsupported report type for SET REPORT: %d\n", report_type); + return -EINVAL; + } + + ret = write_cmd_to_txdma(qcdev, HIDI2C_SET_REPORT, rep_type, reportnum, buf, buf_len); + if (ret) { + dev_err_once(qcdev->dev, "Set Report failed, ret %d\n", ret); + return ret; + } + + return buf_len; +} + +int quicki2c_output_report(struct quicki2c_device *qcdev, void *buf, size_t buf_len) +{ + ssize_t len; + int ret; + + len = quicki2c_init_write_buf(qcdev, 0, 0, false, buf, buf_len, + qcdev->report_buf, qcdev->report_len); + if (len < 0) + return -EINVAL; + + ret = thc_dma_write(qcdev->thc_hw, qcdev->report_buf, len); + if (ret) { + dev_err(qcdev->dev, "Output Report failed, ret %d\n", ret); + return ret; + } + + return buf_len; +} + +#define HIDI2C_RESET_TIMEOUT 5 + +int quicki2c_reset(struct quicki2c_device *qcdev) +{ + u16 input_reg = le16_to_cpu(qcdev->dev_desc.input_reg); + size_t read_len = HIDI2C_LENGTH_LEN; + u32 prd_len = read_len; + int ret; + + qcdev->reset_ack = false; + qcdev->state = QUICKI2C_RESETING; + + ret = write_cmd_to_txdma(qcdev, HIDI2C_RESET, HIDI2C_RESERVED, 0, NULL, 0); + if (ret) { + dev_err_once(qcdev->dev, "Send reset command failed, ret %d\n", ret); + return ret; + } + + ret = wait_event_interruptible_timeout(qcdev->reset_ack_wq, qcdev->reset_ack, + HIDI2C_RESET_TIMEOUT * HZ); + if (qcdev->reset_ack) + return 0; + + /* + * Manually read reset response if it wasn't received, in case reset interrupt + * was missed by touch device or THC hardware. + */ + ret = thc_tic_pio_read(qcdev->thc_hw, input_reg, read_len, &prd_len, + (u32 *)qcdev->input_buf); + if (ret) { + dev_err_once(qcdev->dev, "Read Reset Response failed, ret %d\n", ret); + return ret; + } + + /* + * Check response packet length, it's first 16 bits of packet. + * If response packet length is zero, it's reset response, otherwise not. + */ + if (get_unaligned_le16(qcdev->input_buf)) { + dev_err_once(qcdev->dev, + "Wait reset response timed out ret:%d timeout:%ds\n", + ret, HIDI2C_RESET_TIMEOUT); + return -ETIMEDOUT; + } + + qcdev->reset_ack = true; + + return 0; +} diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h new file mode 100644 index 000000000..6642cefb8 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _QUICKI2C_PROTOCOL_H_ +#define _QUICKI2C_PROTOCOL_H_ + +#include + +struct quicki2c_device; + +int quicki2c_set_power(struct quicki2c_device *qcdev, enum hidi2c_power_state power_state); +int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type, + unsigned int reportnum, void *buf, size_t buf_len); +int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type, + unsigned int reportnum, void *buf, size_t buf_len); +int quicki2c_output_report(struct quicki2c_device *qcdev, void *buf, size_t buf_len); +int quicki2c_get_device_descriptor(struct quicki2c_device *qcdev); +int quicki2c_get_report_descriptor(struct quicki2c_device *qcdev); +int quicki2c_reset(struct quicki2c_device *qcdev); + +#endif /* _QUICKI2C_PROTOCOL_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c new file mode 100644 index 000000000..89226f5ce --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -0,0 +1,1090 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "intel-thc-dev.h" +#include "intel-thc-hw.h" +#include "intel-thc-wot.h" + +#include "quickspi-dev.h" +#include "quickspi-hid.h" +#include "quickspi-protocol.h" + +struct quickspi_driver_data mtl = { + .max_packet_size_value = MAX_PACKET_SIZE_VALUE_MTL, +}; + +struct quickspi_driver_data lnl = { + .max_packet_size_value = MAX_PACKET_SIZE_VALUE_LNL, +}; + +struct quickspi_driver_data ptl = { + .max_packet_size_value = MAX_PACKET_SIZE_VALUE_LNL, +}; + +struct quickspi_driver_data arl = { + .max_packet_size_value = MAX_PACKET_SIZE_VALUE_MTL, +}; + +struct quickspi_driver_data nvl = { + .max_packet_size_value = MAX_PACKET_SIZE_VALUE_LNL, +}; + +/* THC QuickSPI ACPI method to get device properties */ +/* HIDSPI Method: {6e2ac436-0fcf-41af-a265-b32a220dcfab} */ +static guid_t hidspi_guid = + GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, + 0x22, 0x0d, 0xcf, 0xab); + +/* QuickSpi Method: {300D35b7-ac20-413e-8e9c-92e4dafd0afe} */ +static guid_t thc_quickspi_guid = + GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, + 0xda, 0xfd, 0x0a, 0xfe); + +/* Platform Method: {84005682-5b71-41a4-0x8d668130f787a138} */ +static guid_t thc_platform_guid = + GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, + 0xf7, 0x87, 0xa1, 0x38); + + +/* QuickSPI Wake-on-Touch GPIO resource */ +static const struct acpi_gpio_params wake_gpio = { 0, 0, true }; + +static const struct acpi_gpio_mapping quickspi_gpios[] = { + { "wake-on-touch", &wake_gpio, 1 }, + { } +}; + +/** + * thc_acpi_get_property - Query device ACPI parameter + * + * @adev: point to ACPI device + * @guid: ACPI method's guid + * @rev: ACPI method's revision + * @func: ACPI method's function number + * @type: ACPI parameter's data type + * @prop_buf: point to return buffer + * + * This is a helper function for device to query its ACPI parameters. + * + * Return: 0 if successful or ENODEV on failed. + */ +static int thc_acpi_get_property(struct acpi_device *adev, const guid_t *guid, + u64 rev, u64 func, acpi_object_type type, void *prop_buf) +{ + acpi_handle handle = acpi_device_handle(adev); + union acpi_object *obj; + + obj = acpi_evaluate_dsm_typed(handle, guid, rev, func, NULL, type); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call failed, rev: %llu, func: %llu, type: %u\n", + rev, func, type); + return -ENODEV; + } + + if (type == ACPI_TYPE_INTEGER) + *(u32 *)prop_buf = (u32)obj->integer.value; + else if (type == ACPI_TYPE_BUFFER) + memcpy(prop_buf, obj->buffer.pointer, obj->buffer.length); + + ACPI_FREE(obj); + + return 0; +} + +/** + * quickspi_get_acpi_resources - Query all quickspi devices' ACPI parameters + * + * @qsdev: point to quickspi device + * + * This function gets all quickspi devices' ACPI resource. + * + * Return: 0 if successful or error code on failed. + */ +static int quickspi_get_acpi_resources(struct quickspi_device *qsdev) +{ + struct acpi_device *adev = ACPI_COMPANION(qsdev->dev); + int ret = -EINVAL; + + if (!adev) { + dev_err(qsdev->dev, "no valid ACPI companion\n"); + return ret; + } + + qsdev->acpi_dev = adev; + + ret = thc_acpi_get_property(adev, &hidspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_INPUT_REP_HDR_ADDR, + ACPI_TYPE_INTEGER, + &qsdev->input_report_hdr_addr); + if (ret) + return ret; + + ret = thc_acpi_get_property(adev, &hidspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_INPUT_REP_BDY_ADDR, + ACPI_TYPE_INTEGER, + &qsdev->input_report_bdy_addr); + if (ret) + return ret; + + ret = thc_acpi_get_property(adev, &hidspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_OUTPUT_REP_ADDR, + ACPI_TYPE_INTEGER, + &qsdev->output_report_addr); + if (ret) + return ret; + + ret = thc_acpi_get_property(adev, &hidspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_READ_OPCODE, + ACPI_TYPE_BUFFER, + &qsdev->spi_read_opcode); + if (ret) + return ret; + + ret = thc_acpi_get_property(adev, &hidspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_WRITE_OPCODE, + ACPI_TYPE_BUFFER, + &qsdev->spi_write_opcode); + if (ret) + return ret; + + ret = thc_acpi_get_property(adev, &hidspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_IO_MODE, + ACPI_TYPE_INTEGER, + &qsdev->spi_read_io_mode); + if (ret) + return ret; + + if (qsdev->spi_read_io_mode & SPI_WRITE_IO_MODE) + qsdev->spi_write_io_mode = FIELD_GET(SPI_IO_MODE_OPCODE, qsdev->spi_read_io_mode); + else + qsdev->spi_write_io_mode = THC_SINGLE_IO; + + qsdev->spi_read_io_mode = FIELD_GET(SPI_IO_MODE_OPCODE, qsdev->spi_read_io_mode); + + ret = thc_acpi_get_property(adev, &thc_quickspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_CONNECTION_SPEED, + ACPI_TYPE_INTEGER, + &qsdev->spi_freq_val); + if (ret) + return ret; + + ret = thc_acpi_get_property(adev, &thc_quickspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_LIMIT_PACKET_SIZE, + ACPI_TYPE_INTEGER, + &qsdev->limit_packet_size); + if (ret) + return ret; + + if (qsdev->limit_packet_size || !qsdev->driver_data) + qsdev->spi_packet_size = DEFAULT_MIN_PACKET_SIZE_VALUE; + else + qsdev->spi_packet_size = qsdev->driver_data->max_packet_size_value; + + ret = thc_acpi_get_property(adev, &thc_quickspi_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_PERFORMANCE_LIMIT, + ACPI_TYPE_INTEGER, + &qsdev->performance_limit); + if (ret) + return ret; + + qsdev->performance_limit = FIELD_GET(PERFORMANCE_LIMITATION, qsdev->performance_limit); + + ret = thc_acpi_get_property(adev, &thc_platform_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_ACTIVE_LTR, + ACPI_TYPE_INTEGER, + &qsdev->active_ltr_val); + if (ret) + return ret; + + ret = thc_acpi_get_property(adev, &thc_platform_guid, + ACPI_QUICKSPI_REVISION_NUM, + ACPI_QUICKSPI_FUNC_NUM_LP_LTR, + ACPI_TYPE_INTEGER, + &qsdev->low_power_ltr_val); + if (ret) + return ret; + + return 0; +} + +/** + * quickspi_irq_quick_handler - The ISR of the quickspi driver + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * Return: IRQ_WAKE_THREAD if further process needed. + */ +static irqreturn_t quickspi_irq_quick_handler(int irq, void *dev_id) +{ + struct quickspi_device *qsdev = dev_id; + + if (qsdev->state == QUICKSPI_DISABLED) + return IRQ_HANDLED; + + /* Disable THC interrupt before current interrupt be handled */ + thc_interrupt_enable(qsdev->thc_hw, false); + + return IRQ_WAKE_THREAD; +} + +/** + * try_recover - Recover callback to recover THC + * @work: pointer to work_struct + * + * This function is an error handler, called when fatal error happens. + * It try to reset Touch Device and re-configure THC to recover + * transferring between Device and THC. + */ +static void try_recover(struct work_struct *work) +{ + struct quickspi_device *qsdev = container_of(work, struct quickspi_device, recover_work); + + if (READ_ONCE(qsdev->recovery_disabled)) + return; + + if (pm_runtime_resume_and_get(qsdev->dev)) + return; + + thc_interrupt_enable(qsdev->thc_hw, false); + + if (thc_rxdma_reset(qsdev->thc_hw)) { + qsdev->state = QUICKSPI_DISABLED; + dev_err(qsdev->dev, "RxDMA reset failed during recover, disable QuickSPI\n"); + } else { + thc_interrupt_enable(qsdev->thc_hw, true); + } + + pm_runtime_put_autosuspend(qsdev->dev); +} + +/** + * quickspi_irq_thread_handler - IRQ thread handler of quickspi driver + * + * @irq: The IRQ number + * @dev_id: pointer to the quickspi device structure + * + * Return: IRQ_HANDLED to finish this handler. + */ +static irqreturn_t quickspi_irq_thread_handler(int irq, void *dev_id) +{ + struct quickspi_device *qsdev = dev_id; + size_t input_len; + int read_finished = 0; + int err_recover = 0; + int int_mask; + int ret; + + if (qsdev->state == QUICKSPI_DISABLED) + return IRQ_HANDLED; + + ret = pm_runtime_resume_and_get(qsdev->dev); + if (ret) + return IRQ_HANDLED; + + int_mask = thc_interrupt_handler(qsdev->thc_hw); + + if (int_mask & BIT(THC_FATAL_ERR_INT) || int_mask & BIT(THC_TXN_ERR_INT)) { + err_recover = 1; + goto end; + } + + if (int_mask & BIT(THC_NONDMA_INT)) { + if (qsdev->state == QUICKSPI_RESETING) { + qsdev->reset_ack = true; + wake_up_interruptible(&qsdev->reset_ack_wq); + } else { + qsdev->nondma_int_received = true; + wake_up_interruptible(&qsdev->nondma_int_received_wq); + } + } + + if (int_mask & BIT(THC_RXDMA2_INT)) { + while (!read_finished) { + ret = thc_rxdma_read(qsdev->thc_hw, THC_RXDMA2, qsdev->input_buf, + &input_len, &read_finished); + if (ret) { + err_recover = 1; + goto end; + } + + quickspi_handle_input_data(qsdev, input_len); + } + } + +end: + if (err_recover) + schedule_work(&qsdev->recover_work); + else + thc_interrupt_enable(qsdev->thc_hw, true); + + pm_runtime_put_autosuspend(qsdev->dev); + + return IRQ_HANDLED; +} + +/** + * quickspi_dev_init - Initialize quickspi device + * + * @pdev: pointer to the thc pci device + * @mem_addr: The pointer of MMIO memory address + * @id: point to pci_device_id structure + * + * Alloc quickspi device structure and initialized THC device, + * then configure THC to HIDSPI mode. + * + * If success, enable THC hardware interrupt. + * + * Return: pointer to the quickspi device structure if success + * or NULL on failed. + */ +static struct quickspi_device *quickspi_dev_init(struct pci_dev *pdev, void __iomem *mem_addr, + const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct quickspi_device *qsdev; + int ret; + + qsdev = devm_kzalloc(dev, sizeof(struct quickspi_device), GFP_KERNEL); + if (!qsdev) + return ERR_PTR(-ENOMEM); + + qsdev->pdev = pdev; + qsdev->dev = dev; + qsdev->mem_addr = mem_addr; + qsdev->state = QUICKSPI_DISABLED; + qsdev->driver_data = (struct quickspi_driver_data *)id->driver_data; + + init_waitqueue_head(&qsdev->reset_ack_wq); + init_waitqueue_head(&qsdev->nondma_int_received_wq); + init_waitqueue_head(&qsdev->report_desc_got_wq); + init_waitqueue_head(&qsdev->get_report_cmpl_wq); + init_waitqueue_head(&qsdev->set_report_cmpl_wq); + WRITE_ONCE(qsdev->recovery_disabled, false); + INIT_WORK(&qsdev->recover_work, try_recover); + + /* thc hw init */ + qsdev->thc_hw = thc_dev_init(qsdev->dev, qsdev->mem_addr); + if (IS_ERR(qsdev->thc_hw)) { + ret = PTR_ERR(qsdev->thc_hw); + dev_err(dev, "Failed to initialize THC device context, ret = %d.\n", ret); + return ERR_PTR(ret); + } + + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); + if (ret) + return ERR_PTR(ret); + + ret = thc_port_select(qsdev->thc_hw, THC_PORT_TYPE_SPI); + if (ret) { + dev_err(dev, "Failed to select THC port, ret = %d.\n", ret); + return ERR_PTR(ret); + } + + ret = quickspi_get_acpi_resources(qsdev); + if (ret) { + dev_err(dev, "Get ACPI resources failed, ret = %d\n", ret); + return ERR_PTR(ret); + } + + /* THC config for input/output address */ + thc_spi_input_output_address_config(qsdev->thc_hw, + qsdev->input_report_hdr_addr, + qsdev->input_report_bdy_addr, + qsdev->output_report_addr); + + /* THC config for spi read operation */ + ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val, + qsdev->spi_read_io_mode, + qsdev->spi_read_opcode, + qsdev->spi_packet_size); + if (ret) { + dev_err(dev, "thc_spi_read_config failed, ret = %d\n", ret); + return ERR_PTR(ret); + } + + /* THC config for spi write operation */ + ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val, + qsdev->spi_write_io_mode, + qsdev->spi_write_opcode, + qsdev->spi_packet_size, + qsdev->performance_limit); + if (ret) { + dev_err(dev, "thc_spi_write_config failed, ret = %d\n", ret); + return ERR_PTR(ret); + } + + thc_ltr_config(qsdev->thc_hw, + qsdev->active_ltr_val, + qsdev->low_power_ltr_val); + + thc_interrupt_config(qsdev->thc_hw); + + thc_interrupt_enable(qsdev->thc_hw, true); + + thc_wot_config(qsdev->thc_hw, &quickspi_gpios[0]); + + qsdev->state = QUICKSPI_INITIATED; + + return qsdev; +} + +/** + * quickspi_dev_deinit - De-initialize quickspi device + * + * @qsdev: pointer to the quickspi device structure + * + * Disable THC interrupt and deinitilize THC. + */ +static void quickspi_dev_deinit(struct quickspi_device *qsdev) +{ + WRITE_ONCE(qsdev->recovery_disabled, true); + cancel_work_sync(&qsdev->recover_work); + + thc_interrupt_quiesce(qsdev->thc_hw, true); + thc_interrupt_enable(qsdev->thc_hw, false); + thc_ltr_unconfig(qsdev->thc_hw); + thc_wot_unconfig(qsdev->thc_hw); + + qsdev->state = QUICKSPI_DISABLED; +} + +/** + * quickspi_dma_init - Configure THC DMA for quickspi device + * @qsdev: pointer to the quickspi device structure + * + * This function uses TIC's parameters(such as max input length, max output + * length) to allocate THC DMA buffers and configure THC DMA engines. + * + * Return: 0 if successful or error code on failed. + */ +static int quickspi_dma_init(struct quickspi_device *qsdev) +{ + int ret; + + ret = thc_dma_set_max_packet_sizes(qsdev->thc_hw, 0, + le16_to_cpu(qsdev->dev_desc.max_input_len), + le16_to_cpu(qsdev->dev_desc.max_output_len), + 0); + if (ret) + return ret; + + ret = thc_dma_allocate(qsdev->thc_hw); + if (ret) { + dev_err(qsdev->dev, "Allocate THC DMA buffer failed, ret = %d\n", ret); + return ret; + } + + /* Enable RxDMA */ + ret = thc_dma_configure(qsdev->thc_hw); + if (ret) { + dev_err(qsdev->dev, "Configure THC DMA failed, ret = %d\n", ret); + thc_dma_unconfigure(qsdev->thc_hw); + thc_dma_release(qsdev->thc_hw); + return ret; + } + + return ret; +} + +/** + * quickspi_dma_deinit - Release THC DMA for quickspi device + * @qsdev: pointer to the quickspi device structure + * + * Stop THC DMA engines and release all DMA buffers. + * + */ +static void quickspi_dma_deinit(struct quickspi_device *qsdev) +{ + thc_dma_unconfigure(qsdev->thc_hw); + thc_dma_release(qsdev->thc_hw); +} + +/** + * quickspi_alloc_report_buf - Alloc report buffers + * @qsdev: pointer to the quickspi device structure + * + * Allocate report descriptor buffer, it will be used for restore TIC HID + * report descriptor. + * + * Allocate input report buffer, it will be used for receive HID input report + * data from TIC. + * + * Allocate output report buffer, it will be used for store HID output report, + * such as set feature. + * + * Return: 0 if successful or error code on failed. + */ +static int quickspi_alloc_report_buf(struct quickspi_device *qsdev) +{ + size_t max_report_len; + size_t max_input_len; + + qsdev->report_descriptor = devm_kzalloc(qsdev->dev, + le16_to_cpu(qsdev->dev_desc.rep_desc_len), + GFP_KERNEL); + if (!qsdev->report_descriptor) + return -ENOMEM; + + max_input_len = max(le16_to_cpu(qsdev->dev_desc.rep_desc_len), + le16_to_cpu(qsdev->dev_desc.max_input_len)); + + qsdev->input_buf = devm_kzalloc(qsdev->dev, max_input_len, GFP_KERNEL); + if (!qsdev->input_buf) + return -ENOMEM; + + max_report_len = max(le16_to_cpu(qsdev->dev_desc.max_output_len), + le16_to_cpu(qsdev->dev_desc.max_input_len)); + + /* + * write_cmd_to_txdma() writes the output report header ahead of the + * content in this buffer, so it has to hold both. + */ + qsdev->report_buf_size = HIDSPI_OUTPUT_REPORT_SIZE(max_report_len); + + qsdev->report_buf = devm_kzalloc(qsdev->dev, qsdev->report_buf_size, + GFP_KERNEL); + if (!qsdev->report_buf) + return -ENOMEM; + + return 0; +} + +/* + * quickspi_probe: Quickspi driver probe function + * + * @pdev: point to pci device + * @id: point to pci_device_id structure + * + * This function initializes THC and HIDSPI device, the flow is: + * - do THC pci device initialization + * - query HIDSPI ACPI parameters + * - configure THC to HIDSPI mode + * - go through HIDSPI enumeration flow + * |- reset HIDSPI device + * |- read device descriptor + * - enable THC interrupt and DMA + * - read report descriptor + * - register HID device + * - enable runtime power management + * + * Return 0 if success or error code on failure. + */ +static int quickspi_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct quickspi_device *qsdev; + void __iomem *mem_addr; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to enable PCI device, ret = %d.\n", ret); + return ret; + } + + pci_set_master(pdev); + + mem_addr = pcim_iomap_region(pdev, 0, KBUILD_MODNAME); + ret = PTR_ERR_OR_ZERO(mem_addr); + if (ret) { + dev_err(&pdev->dev, "Failed to get PCI regions, ret = %d.\n", ret); + goto disable_pci_device; + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "No usable DMA configuration %d\n", ret); + goto disable_pci_device; + } + } + + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to allocate IRQ vectors. ret = %d\n", ret); + goto disable_pci_device; + } + + pdev->irq = pci_irq_vector(pdev, 0); + + qsdev = quickspi_dev_init(pdev, mem_addr, id); + if (IS_ERR(qsdev)) { + dev_err(&pdev->dev, "QuickSPI device init failed\n"); + ret = PTR_ERR(qsdev); + goto disable_pci_device; + } + + pci_set_drvdata(pdev, qsdev); + + ret = devm_request_threaded_irq(&pdev->dev, pdev->irq, + quickspi_irq_quick_handler, + quickspi_irq_thread_handler, + IRQF_ONESHOT, KBUILD_MODNAME, + qsdev); + if (ret) + goto dev_deinit; + + ret = reset_tic(qsdev); + if (ret) { + dev_err(&pdev->dev, "Reset Touch Device failed, ret = %d\n", ret); + goto dev_deinit; + } + + ret = quickspi_alloc_report_buf(qsdev); + if (ret) { + dev_err(&pdev->dev, "Alloc report buffers failed, ret= %d\n", ret); + goto dev_deinit; + } + + ret = quickspi_dma_init(qsdev); + if (ret) { + dev_err(&pdev->dev, "Setup THC DMA failed, ret= %d\n", ret); + goto dev_deinit; + } + + ret = quickspi_get_report_descriptor(qsdev); + if (ret) { + dev_err(&pdev->dev, "Get report descriptor failed, ret = %d\n", ret); + goto dma_deinit; + } + + ret = quickspi_hid_probe(qsdev); + if (ret) { + dev_err(&pdev->dev, "Failed to register HID device, ret = %d\n", ret); + goto dma_deinit; + } + + qsdev->state = QUICKSPI_ENABLED; + + /* Enable runtime power management */ + pm_runtime_use_autosuspend(qsdev->dev); + pm_runtime_set_autosuspend_delay(qsdev->dev, DEFAULT_AUTO_SUSPEND_DELAY_MS); + pm_runtime_put_noidle(qsdev->dev); + pm_runtime_put_autosuspend(qsdev->dev); + + dev_dbg(&pdev->dev, "QuickSPI probe success\n"); + + return 0; + +dma_deinit: + quickspi_dma_deinit(qsdev); +dev_deinit: + quickspi_dev_deinit(qsdev); +disable_pci_device: + pci_clear_master(pdev); + + return ret; +} + +/** + * quickspi_remove - Device Removal Routine + * + * @pdev: PCI device structure + * + * This is called by the PCI subsystem to alert the driver + * that it should release a PCI device. + */ +static void quickspi_remove(struct pci_dev *pdev) +{ + struct quickspi_device *qsdev; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return; + + quickspi_hid_remove(qsdev); + + quickspi_dev_deinit(qsdev); + + quickspi_dma_deinit(qsdev); + + pm_runtime_dont_use_autosuspend(qsdev->dev); + pm_runtime_get_noresume(qsdev->dev); + + pci_clear_master(pdev); +} + +/** + * quickspi_shutdown - Device Shutdown Routine + * + * @pdev: PCI device structure + * + * This is called from the reboot notifier + * it's a simplified version of remove so we go down + * faster. + */ +static void quickspi_shutdown(struct pci_dev *pdev) +{ + struct quickspi_device *qsdev; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return; + + quickspi_dev_deinit(qsdev); + + /* Must stop DMA before reboot to avoid DMA entering into unknown state */ + quickspi_dma_deinit(qsdev); +} + +static int quickspi_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quickspi_device *qsdev; + int ret; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return -ENODEV; + + if (!device_may_wakeup(qsdev->dev)) { + ret = quickspi_set_power(qsdev, HIDSPI_SLEEP); + if (ret) + return ret; + } + + WRITE_ONCE(qsdev->recovery_disabled, true); + cancel_work_sync(&qsdev->recover_work); + + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); + if (ret) + return ret; + + thc_interrupt_enable(qsdev->thc_hw, false); + + thc_dma_unconfigure(qsdev->thc_hw); + + return 0; +} + +static int quickspi_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quickspi_device *qsdev; + int ret; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return -ENODEV; + + ret = thc_port_select(qsdev->thc_hw, THC_PORT_TYPE_SPI); + if (ret) + return ret; + + WRITE_ONCE(qsdev->recovery_disabled, false); + + /* + * A wake-enabled device keeps its power and state across suspend, so + * only restore the THC context. Resetting it here would discard a + * pending wake touch event and break wake-on-touch. + */ + if (device_may_wakeup(qsdev->dev)) { + thc_interrupt_config(qsdev->thc_hw); + + thc_interrupt_enable(qsdev->thc_hw, true); + + ret = thc_dma_configure(qsdev->thc_hw); + if (ret) + return ret; + + return thc_interrupt_quiesce(qsdev->thc_hw, false); + } + + /* + * Otherwise the touch IC may have lost power across suspend. On + * platforms that suspend through s2idle (for example the Surface Pro + * 10, whose firmware exposes s2idle as the only mem_sleep state) the + * IC loses power the same way it does across hibernation. A plain + * HIDSPI_ON is then not acknowledged and the descriptor read fails, so + * re-enumerate the device through the full reset flow already used by + * quickspi_restore(). + */ + thc_spi_input_output_address_config(qsdev->thc_hw, + qsdev->input_report_hdr_addr, + qsdev->input_report_bdy_addr, + qsdev->output_report_addr); + + ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val, + qsdev->spi_read_io_mode, + qsdev->spi_read_opcode, + qsdev->spi_packet_size); + if (ret) + return ret; + + ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val, + qsdev->spi_write_io_mode, + qsdev->spi_write_opcode, + qsdev->spi_packet_size, + qsdev->performance_limit); + if (ret) + return ret; + + thc_interrupt_config(qsdev->thc_hw); + + thc_interrupt_enable(qsdev->thc_hw, true); + + /* The touch IC may have lost power, reset it to recover */ + ret = reset_tic(qsdev); + if (ret) + return ret; + + ret = thc_dma_configure(qsdev->thc_hw); + if (ret) + return ret; + + thc_ltr_config(qsdev->thc_hw, + qsdev->active_ltr_val, + qsdev->low_power_ltr_val); + + thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE); + + qsdev->state = QUICKSPI_ENABLED; + + return 0; +} + +static int quickspi_freeze(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quickspi_device *qsdev; + int ret; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return -ENODEV; + + WRITE_ONCE(qsdev->recovery_disabled, true); + cancel_work_sync(&qsdev->recover_work); + + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); + if (ret) + return ret; + + thc_interrupt_enable(qsdev->thc_hw, false); + + thc_dma_unconfigure(qsdev->thc_hw); + + return 0; +} + +static int quickspi_thaw(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quickspi_device *qsdev; + int ret; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return -ENODEV; + + ret = thc_dma_configure(qsdev->thc_hw); + if (ret) + return ret; + + thc_interrupt_enable(qsdev->thc_hw, true); + + ret = thc_interrupt_quiesce(qsdev->thc_hw, false); + if (ret) + return ret; + + WRITE_ONCE(qsdev->recovery_disabled, false); + + return 0; +} + +static int quickspi_poweroff(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quickspi_device *qsdev; + int ret; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return -ENODEV; + + /* Ignore the return value as platform will be poweroff soon */ + quickspi_set_power(qsdev, HIDSPI_OFF); + + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); + if (ret) + return ret; + + thc_interrupt_enable(qsdev->thc_hw, false); + + thc_ltr_unconfig(qsdev->thc_hw); + + quickspi_dev_deinit(qsdev); + + quickspi_dma_deinit(qsdev); + + return 0; +} + +static int quickspi_restore(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quickspi_device *qsdev; + int ret; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return -ENODEV; + + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); + if (ret) + return ret; + + /* Reconfig THC HW when back from hibernate */ + ret = thc_port_select(qsdev->thc_hw, THC_PORT_TYPE_SPI); + if (ret) + return ret; + + thc_spi_input_output_address_config(qsdev->thc_hw, + qsdev->input_report_hdr_addr, + qsdev->input_report_bdy_addr, + qsdev->output_report_addr); + + ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val, + qsdev->spi_read_io_mode, + qsdev->spi_read_opcode, + qsdev->spi_packet_size); + if (ret) + return ret; + + ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val, + qsdev->spi_write_io_mode, + qsdev->spi_write_opcode, + qsdev->spi_packet_size, + qsdev->performance_limit); + if (ret) + return ret; + + thc_interrupt_config(qsdev->thc_hw); + + thc_interrupt_enable(qsdev->thc_hw, true); + + /* TIC may lose power, needs go through reset flow */ + ret = reset_tic(qsdev); + if (ret) + return ret; + + ret = thc_dma_configure(qsdev->thc_hw); + if (ret) + return ret; + + thc_ltr_config(qsdev->thc_hw, + qsdev->active_ltr_val, + qsdev->low_power_ltr_val); + + thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE); + + qsdev->state = QUICKSPI_ENABLED; + + return 0; +} + +static int quickspi_runtime_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quickspi_device *qsdev; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return -ENODEV; + + thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_LP); + + pci_save_state(pdev); + + return 0; +} + +static int quickspi_runtime_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct quickspi_device *qsdev; + + qsdev = pci_get_drvdata(pdev); + if (!qsdev) + return -ENODEV; + + thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE); + + return 0; +} + +static const struct dev_pm_ops quickspi_pm_ops = { + .suspend = quickspi_suspend, + .resume = quickspi_resume, + .freeze = quickspi_freeze, + .thaw = quickspi_thaw, + .poweroff = quickspi_poweroff, + .restore = quickspi_restore, + .runtime_suspend = quickspi_runtime_suspend, + .runtime_resume = quickspi_runtime_resume, + .runtime_idle = NULL, +}; + +static const struct pci_device_id quickspi_pci_tbl[] = { + {PCI_DEVICE_DATA(INTEL, THC_MTL_DEVICE_ID_SPI_PORT1, &mtl), }, + {PCI_DEVICE_DATA(INTEL, THC_MTL_DEVICE_ID_SPI_PORT2, &mtl), }, + {PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_SPI_PORT1, &lnl), }, + {PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_SPI_PORT2, &lnl), }, + {PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_SPI_PORT1, &ptl), }, + {PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_SPI_PORT2, &ptl), }, + {PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_SPI_PORT1, &ptl), }, + {PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_SPI_PORT2, &ptl), }, + {PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_SPI_PORT1, &ptl), }, + {PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_SPI_PORT2, &ptl), }, + {PCI_DEVICE_DATA(INTEL, THC_ARL_DEVICE_ID_SPI_PORT1, &arl), }, + {PCI_DEVICE_DATA(INTEL, THC_ARL_DEVICE_ID_SPI_PORT2, &arl), }, + {PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_SPI_PORT1, &nvl), }, + {PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_SPI_PORT2, &nvl), }, + {} +}; +MODULE_DEVICE_TABLE(pci, quickspi_pci_tbl); + +static struct pci_driver quickspi_driver = { + .name = KBUILD_MODNAME, + .id_table = quickspi_pci_tbl, + .probe = quickspi_probe, + .remove = quickspi_remove, + .shutdown = quickspi_shutdown, + .driver.pm = &quickspi_pm_ops, + .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, +}; + +module_pci_driver(quickspi_driver); + +MODULE_AUTHOR("Xinpeng Sun "); +MODULE_AUTHOR("Even Xu "); + +MODULE_DESCRIPTION("Intel(R) QuickSPI Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("INTEL_THC"); diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h new file mode 100644 index 000000000..dc67a39ad --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _QUICKSPI_DEV_H_ +#define _QUICKSPI_DEV_H_ + +#include +#include +#include +#include +#include + +#include "quickspi-protocol.h" + +#define PCI_DEVICE_ID_INTEL_THC_MTL_DEVICE_ID_SPI_PORT1 0x7E49 +#define PCI_DEVICE_ID_INTEL_THC_MTL_DEVICE_ID_SPI_PORT2 0x7E4B +#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_SPI_PORT1 0xA849 +#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_SPI_PORT2 0xA84B +#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_SPI_PORT1 0xE349 +#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_SPI_PORT2 0xE34B +#define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_SPI_PORT1 0xE449 +#define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_SPI_PORT2 0xE44B +#define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_SPI_PORT1 0x4D49 +#define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_SPI_PORT2 0x4D4B +#define PCI_DEVICE_ID_INTEL_THC_ARL_DEVICE_ID_SPI_PORT1 0x7749 +#define PCI_DEVICE_ID_INTEL_THC_ARL_DEVICE_ID_SPI_PORT2 0x774B +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_SPI_PORT1 0xD349 +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_SPI_PORT2 0xD34B + +/* HIDSPI special ACPI parameters DSM methods */ +#define ACPI_QUICKSPI_REVISION_NUM 2 +#define ACPI_QUICKSPI_FUNC_NUM_INPUT_REP_HDR_ADDR 1 +#define ACPI_QUICKSPI_FUNC_NUM_INPUT_REP_BDY_ADDR 2 +#define ACPI_QUICKSPI_FUNC_NUM_OUTPUT_REP_ADDR 3 +#define ACPI_QUICKSPI_FUNC_NUM_READ_OPCODE 4 +#define ACPI_QUICKSPI_FUNC_NUM_WRITE_OPCODE 5 +#define ACPI_QUICKSPI_FUNC_NUM_IO_MODE 6 + +/* QickSPI device special ACPI parameters DSM methods */ +#define ACPI_QUICKSPI_FUNC_NUM_CONNECTION_SPEED 1 +#define ACPI_QUICKSPI_FUNC_NUM_LIMIT_PACKET_SIZE 2 +#define ACPI_QUICKSPI_FUNC_NUM_PERFORMANCE_LIMIT 3 + +/* Platform special ACPI parameters DSM methods */ +#define ACPI_QUICKSPI_FUNC_NUM_ACTIVE_LTR 1 +#define ACPI_QUICKSPI_FUNC_NUM_LP_LTR 2 + +#define SPI_WRITE_IO_MODE BIT(13) +#define SPI_IO_MODE_OPCODE GENMASK(15, 14) +#define PERFORMANCE_LIMITATION GENMASK(15, 0) + +/* Packet size value, the unit is 16 bytes */ +#define DEFAULT_MIN_PACKET_SIZE_VALUE 4 +#define MAX_PACKET_SIZE_VALUE_MTL 128 +#define MAX_PACKET_SIZE_VALUE_LNL 256 + +/* + * THC uses runtime auto suspend to dynamically switch between THC active LTR + * and low power LTR to save CPU power. + * Default value is 5000ms, that means if no touch event in this time, THC will + * change to low power LTR mode. + */ +#define DEFAULT_AUTO_SUSPEND_DELAY_MS 5000 + +enum quickspi_dev_state { + QUICKSPI_NONE, + QUICKSPI_INITIATED, + QUICKSPI_RESETING, + QUICKSPI_RESET, + QUICKSPI_ENABLED, + QUICKSPI_DISABLED, +}; + +/** + * struct quickspi_driver_data - Driver specific data for quickspi device + * @max_packet_size_value: identify max packet size, unit is 16 bytes + */ +struct quickspi_driver_data { + u32 max_packet_size_value; +}; + +struct device; +struct pci_dev; +struct thc_device; +struct hid_device; +struct acpi_device; + +/** + * struct quickspi_device - THC QuickSpi device struct + * @dev: point to kernel device + * @pdev: point to PCI device + * @thc_hw: point to THC device + * @hid_dev: point to hid device + * @acpi_dev: point to ACPI device + * @driver_data: point to quickspi specific driver data + * @state: THC SPI device state + * @mem_addr: MMIO memory address + * @dev_desc: device descriptor for HIDSPI protocol + * @input_report_hdr_addr: device input report header address + * @input_report_bdy_addr: device input report body address + * @output_report_bdy_addr: device output report address + * @spi_freq_val: device supported max SPI frequnecy, in Hz + * @spi_read_io_mode: device supported SPI read io mode + * @spi_write_io_mode: device supported SPI write io mode + * @spi_read_opcode: device read opcode + * @spi_write_opcode: device write opcode + * @limit_packet_size: 1 - limit read/write packet to 64Bytes + * 0 - device no packet size limiation for read/write + * @performance_limit: delay time, in ms. + * if device has performance limitation, must give a delay + * before write operation after a read operation. + * @active_ltr_val: THC active LTR value + * @low_power_ltr_val: THC low power LTR value + * @report_descriptor: store a copy of device report descriptor + * @input_buf: store a copy of latest input report data + * @report_buf: store a copy of latest input/output report packet from set/get feature + * @report_len: the length of input/output report packet + * @reset_ack_wq: workqueue for waiting reset response from device + * @reset_ack: indicate reset response received or not + * @nondma_int_received_wq: workqueue for waiting THC non-DMA interrupt + * @nondma_int_received: indicate THC non-DMA interrupt received or not + * @report_desc_got_wq: workqueue for waiting device report descriptor + * @report_desc_got: indicate device report descritor received or not + * @set_power_on_wq: workqueue for waiting set power on response from device + * @set_power_on: indicate set power on response received or not + * @get_feature_cmpl_wq: workqueue for waiting get feature response from device + * @get_feature_cmpl: indicate get feature received or not + * @set_feature_cmpl_wq: workqueue for waiting set feature to device + * @set_feature_cmpl: indicate set feature send complete or not + * @recover_work: Work structure for recovery + * @recovery_disabled: Whether recovery work is blocked during teardown + */ +struct quickspi_device { + struct device *dev; + struct pci_dev *pdev; + struct thc_device *thc_hw; + struct hid_device *hid_dev; + struct acpi_device *acpi_dev; + struct quickspi_driver_data *driver_data; + enum quickspi_dev_state state; + + void __iomem *mem_addr; + + struct hidspi_dev_descriptor dev_desc; + u32 input_report_hdr_addr; + u32 input_report_bdy_addr; + u32 output_report_addr; + u32 spi_freq_val; + u32 spi_read_io_mode; + u32 spi_write_io_mode; + u32 spi_read_opcode; + u32 spi_write_opcode; + u32 limit_packet_size; + u32 spi_packet_size; + u32 performance_limit; + + u32 active_ltr_val; + u32 low_power_ltr_val; + + u8 *report_descriptor; + u8 *input_buf; + u8 *report_buf; + u32 report_buf_size; + u32 report_len; + + wait_queue_head_t reset_ack_wq; + bool reset_ack; + + wait_queue_head_t nondma_int_received_wq; + bool nondma_int_received; + + wait_queue_head_t report_desc_got_wq; + bool report_desc_got; + + wait_queue_head_t get_report_cmpl_wq; + bool get_report_cmpl; + + wait_queue_head_t set_report_cmpl_wq; + bool set_report_cmpl; + + struct work_struct recover_work; + bool recovery_disabled; +}; + +#endif /* _QUICKSPI_DEV_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c new file mode 100644 index 000000000..a60a0a7f1 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include +#include +#include + +#include "quickspi-dev.h" +#include "quickspi-hid.h" + +/** + * quickspi_hid_parse() - HID core parse() callback + * + * @hid: HID device instance + * + * This function gets called during call to hid_add_device + * + * Return: 0 on success and non zero on error. + */ +static int quickspi_hid_parse(struct hid_device *hid) +{ + struct quickspi_device *qsdev = hid->driver_data; + + if (qsdev->report_descriptor) + return hid_parse_report(hid, qsdev->report_descriptor, + le16_to_cpu(qsdev->dev_desc.rep_desc_len)); + + dev_err(qsdev->dev, "invalid report descriptor\n"); + return -EINVAL; +} + +static int quickspi_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void quickspi_hid_stop(struct hid_device *hid) +{ +} + +static int quickspi_hid_open(struct hid_device *hid) +{ + return 0; +} + +static void quickspi_hid_close(struct hid_device *hid) +{ +} + +static int quickspi_hid_raw_request(struct hid_device *hid, + unsigned char reportnum, + __u8 *buf, size_t len, + unsigned char rtype, int reqtype) +{ + struct quickspi_device *qsdev = hid->driver_data; + int ret = 0; + + ret = pm_runtime_resume_and_get(qsdev->dev); + if (ret) + return ret; + + switch (reqtype) { + case HID_REQ_GET_REPORT: + ret = quickspi_get_report(qsdev, rtype, reportnum, buf, len); + break; + case HID_REQ_SET_REPORT: + ret = quickspi_set_report(qsdev, rtype, reportnum, buf, len); + break; + default: + dev_err_once(qsdev->dev, "Not supported request type %d\n", reqtype); + break; + } + + pm_runtime_put_autosuspend(qsdev->dev); + + return ret; +} + +static int quickspi_hid_power(struct hid_device *hid, int lvl) +{ + return 0; +} + +static struct hid_ll_driver quickspi_hid_ll_driver = { + .parse = quickspi_hid_parse, + .start = quickspi_hid_start, + .stop = quickspi_hid_stop, + .open = quickspi_hid_open, + .close = quickspi_hid_close, + .power = quickspi_hid_power, + .raw_request = quickspi_hid_raw_request, +}; + +/** + * quickspi_hid_probe() - Register HID low level driver + * + * @qsdev: point to quickspi device + * + * This function is used to allocate and add HID device. + * + * Return: 0 on success, non zero on error. + */ +int quickspi_hid_probe(struct quickspi_device *qsdev) +{ + struct hid_device *hid; + int ret; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + hid->ll_driver = &quickspi_hid_ll_driver; + hid->bus = BUS_PCI; + hid->dev.parent = qsdev->dev; + hid->driver_data = qsdev; + hid->version = le16_to_cpu(qsdev->dev_desc.version_id); + hid->vendor = le16_to_cpu(qsdev->dev_desc.vendor_id); + hid->product = le16_to_cpu(qsdev->dev_desc.product_id); + snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "quickspi-hid", + hid->vendor, hid->product); + strscpy(hid->phys, dev_name(qsdev->dev), sizeof(hid->phys)); + + ret = hid_add_device(hid); + if (ret) { + hid_destroy_device(hid); + return ret; + } + + qsdev->hid_dev = hid; + + return 0; +} + +/** + * quickspi_hid_remove() - Destroy HID device + * + * @qsdev: point to quickspi device + * + * Return: 0 on success, non zero on error. + */ +void quickspi_hid_remove(struct quickspi_device *qsdev) +{ + hid_destroy_device(qsdev->hid_dev); +} + +/** + * quickspi_hid_send_report() - Send HID input report data to HID core + * + * @qsdev: point to quickspi device + * @data: point to input report data buffer + * @data_len: the length of input report data + * + * Return: 0 on success, non zero on error. + */ +int quickspi_hid_send_report(struct quickspi_device *qsdev, + void *data, size_t data_len) +{ + int ret; + + ret = hid_input_report(qsdev->hid_dev, HID_INPUT_REPORT, data, data_len, 1); + if (ret) + dev_err(qsdev->dev, "Failed to send HID input report, ret = %d.\n", ret); + + return ret; +} diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.h new file mode 100644 index 000000000..f640fa876 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _QUICKSPI_HID_H_ +#define _QUICKSPI_HID_H_ + +struct quickspi_device; + +int quickspi_hid_send_report(struct quickspi_device *qsdev, + void *data, size_t data_size); +int quickspi_hid_probe(struct quickspi_device *qsdev); +void quickspi_hid_remove(struct quickspi_device *qsdev); + +#endif /* _QUICKSPI_HID_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c new file mode 100644 index 000000000..847c5ec55 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c @@ -0,0 +1,426 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright © 2024 Intel Corporation */ + +#include +#include +#include +#include + +#include "intel-thc-dev.h" +#include "intel-thc-dma.h" + +#include "quickspi-dev.h" +#include "quickspi-hid.h" +#include "quickspi-protocol.h" + +/* THC uses HW to accelerate HID over SPI protocol, THC_M_PRT_DEV_INT_CAUSE + * register is used to store message header and body header, below definition + * let driver retrieve needed data filed easier from THC_M_PRT_DEV_INT_CAUSE + * register. + */ +#define HIDSPI_IN_REP_BDY_HDR_REP_TYPE GENMASK(7, 0) + +static int write_cmd_to_txdma(struct quickspi_device *qsdev, + int report_type, int report_id, + u8 *report_buf, const int report_buf_len) +{ + struct output_report *write_buf; + int write_buf_len; + int ret; + + write_buf = (struct output_report *)qsdev->report_buf; + + if (HIDSPI_OUTPUT_REPORT_SIZE(report_buf_len) > qsdev->report_buf_size) + return -EINVAL; + + write_buf->output_hdr.report_type = report_type; + write_buf->output_hdr.content_len = cpu_to_le16(report_buf_len); + write_buf->output_hdr.content_id = report_id; + + if (report_buf && report_buf_len > 0) + memcpy(write_buf->content, report_buf, report_buf_len); + + write_buf_len = HIDSPI_OUTPUT_REPORT_SIZE(report_buf_len); + + ret = thc_dma_write(qsdev->thc_hw, write_buf, write_buf_len); + if (ret) + dev_err_once(qsdev->dev, "DMA write failed, ret = %d\n", ret); + + return ret; +} + +static int quickspi_get_device_descriptor(struct quickspi_device *qsdev) +{ + u8 read_buf[HIDSPI_INPUT_DEVICE_DESCRIPTOR_SIZE]; + struct output_report output_rep; + u32 input_len, read_len = 0; + u32 int_cause_val; + u8 input_rep_type; + int ret; + + output_rep.output_hdr.report_type = DEVICE_DESCRIPTOR; + output_rep.output_hdr.content_len = 0; + output_rep.output_hdr.content_id = 0; + + qsdev->nondma_int_received = false; + + ret = thc_tic_pio_write(qsdev->thc_hw, qsdev->output_report_addr, + HIDSPI_OUTPUT_REPORT_SIZE(0), (u32 *)&output_rep); + if (ret) { + dev_err_once(qsdev->dev, + "Write DEVICE_DESCRIPTOR command failed, ret = %d\n", ret); + return ret; + } + + ret = wait_event_interruptible_timeout(qsdev->nondma_int_received_wq, + qsdev->nondma_int_received, + QUICKSPI_ACK_WAIT_TIMEOUT * HZ); + if (ret <= 0 || !qsdev->nondma_int_received) { + dev_err_once(qsdev->dev, "Wait DEVICE_DESCRIPTOR timeout, ret:%d\n", ret); + return -ETIMEDOUT; + } + qsdev->nondma_int_received = false; + + int_cause_val = thc_int_cause_read(qsdev->thc_hw); + input_len = FIELD_GET(HIDSPI_INPUT_HEADER_REPORT_LEN, int_cause_val); + + input_len = input_len * sizeof(u32); + if (input_len != HIDSPI_INPUT_DEVICE_DESCRIPTOR_SIZE) { + dev_err_once(qsdev->dev, "Receive wrong DEVICE_DESCRIPTOR length, len = %u\n", + input_len); + return -EINVAL; + } + + ret = thc_tic_pio_read(qsdev->thc_hw, qsdev->input_report_bdy_addr, + input_len, &read_len, (u32 *)read_buf); + if (ret || read_len != input_len) { + dev_err_once(qsdev->dev, "Read DEVICE_DESCRIPTOR failed, ret = %d\n", ret); + dev_err_once(qsdev->dev, "DEVICE_DESCRIPTOR expected len = %u, actual read = %u\n", + input_len, read_len); + return ret ?: -EINVAL; + } + + input_rep_type = ((struct input_report_body_header *)read_buf)->input_report_type; + + if (input_rep_type == DEVICE_DESCRIPTOR_RESPONSE) { + memcpy(&qsdev->dev_desc, + read_buf + HIDSPI_INPUT_BODY_HEADER_SIZE, + HIDSPI_DEVICE_DESCRIPTOR_SIZE); + + return 0; + } + + dev_err_once(qsdev->dev, "Unexpected input report type: %d\n", input_rep_type); + return -EINVAL; +} + +int quickspi_get_report_descriptor(struct quickspi_device *qsdev) +{ + int ret; + + ret = write_cmd_to_txdma(qsdev, REPORT_DESCRIPTOR, 0, NULL, 0); + if (ret) { + dev_err_once(qsdev->dev, + "Write REPORT_DESCRIPTOR command failed, ret = %d\n", ret); + return ret; + } + + ret = wait_event_interruptible_timeout(qsdev->report_desc_got_wq, + qsdev->report_desc_got, + QUICKSPI_ACK_WAIT_TIMEOUT * HZ); + if (ret <= 0 || !qsdev->report_desc_got) { + dev_err_once(qsdev->dev, "Wait Report Descriptor timeout, ret:%d\n", ret); + return -ETIMEDOUT; + } + qsdev->report_desc_got = false; + + return 0; +} + +int quickspi_set_power(struct quickspi_device *qsdev, + enum hidspi_power_state power_state) +{ + u8 cmd_content = power_state; + int ret; + + ret = write_cmd_to_txdma(qsdev, COMMAND_CONTENT, + HIDSPI_SET_POWER_CMD_ID, + &cmd_content, + sizeof(cmd_content)); + if (ret) { + dev_err_once(qsdev->dev, "Write SET_POWER command failed, ret = %d\n", ret); + return ret; + } + + return 0; +} + +void quickspi_handle_input_data(struct quickspi_device *qsdev, u32 buf_len) +{ + struct input_report_body_header *body_hdr; + struct input_report_body *input_body; + u8 *input_report; + u32 input_len; + int ret = 0; + + input_body = (struct input_report_body *)qsdev->input_buf; + body_hdr = &input_body->body_hdr; + input_len = le16_to_cpu(body_hdr->content_len); + + if (HIDSPI_INPUT_BODY_SIZE(input_len) > buf_len) { + dev_err_once(qsdev->dev, "Wrong input report length: %u", + input_len); + return; + } + + switch (body_hdr->input_report_type) { + case REPORT_DESCRIPTOR_RESPONSE: + if (input_len != le16_to_cpu(qsdev->dev_desc.rep_desc_len)) { + dev_err_once(qsdev->dev, "Unexpected report descriptor length: %u\n", + input_len); + return; + } + + memcpy(qsdev->report_descriptor, input_body->content, input_len); + + qsdev->report_desc_got = true; + wake_up_interruptible(&qsdev->report_desc_got_wq); + + break; + + case COMMAND_RESPONSE: + if (body_hdr->content_id == HIDSPI_SET_POWER_CMD_ID) { + dev_dbg(qsdev->dev, "Receive set power on response\n"); + } else { + dev_err_once(qsdev->dev, "Unknown command response type: %u\n", + body_hdr->content_id); + } + + break; + + case RESET_RESPONSE: + if (qsdev->state == QUICKSPI_RESETING) { + qsdev->reset_ack = true; + wake_up_interruptible(&qsdev->reset_ack_wq); + dev_dbg(qsdev->dev, "Receive HIR reset response\n"); + } else { + dev_info(qsdev->dev, "Receive DIR\n"); + } + break; + + case GET_FEATURE_RESPONSE: + case GET_INPUT_REPORT_RESPONSE: + qsdev->report_len = sizeof(body_hdr->content_id) + input_len; + input_report = input_body->content - sizeof(body_hdr->content_id); + + memcpy(qsdev->report_buf, input_report, qsdev->report_len); + + qsdev->get_report_cmpl = true; + wake_up_interruptible(&qsdev->get_report_cmpl_wq); + + break; + + case SET_FEATURE_RESPONSE: + case OUTPUT_REPORT_RESPONSE: + qsdev->set_report_cmpl = true; + wake_up_interruptible(&qsdev->set_report_cmpl_wq); + + break; + + case DATA: + if (qsdev->state != QUICKSPI_ENABLED) + return; + + if (input_len > le16_to_cpu(qsdev->dev_desc.max_input_len)) { + dev_err_once(qsdev->dev, "Unexpected too large input report length: %u\n", + input_len); + return; + } + + input_len = sizeof(body_hdr->content_id) + input_len; + input_report = input_body->content - sizeof(body_hdr->content_id); + + ret = quickspi_hid_send_report(qsdev, input_report, input_len); + if (ret) + dev_err_once(qsdev->dev, "Failed to send HID input report: %d\n", ret); + + break; + + default: + dev_err_once(qsdev->dev, "Unsupported input report type: %u\n", + body_hdr->input_report_type); + break; + } +} + +static int acpi_tic_reset(struct quickspi_device *qsdev) +{ + acpi_status status = 0; + acpi_handle handle; + + if (!qsdev->acpi_dev) + return -ENODEV; + + handle = acpi_device_handle(qsdev->acpi_dev); + status = acpi_execute_simple_method(handle, "_RST", 0); + if (ACPI_FAILURE(status)) { + dev_err_once(qsdev->dev, + "Failed to reset device through ACPI method, ret = %d\n", status); + return -EIO; + } + + return 0; +} + +int reset_tic(struct quickspi_device *qsdev) +{ + u32 actual_read_len, read_len = 0; + u32 input_report_len, reset_response, int_cause_val; + u8 input_rep_type; + int ret; + + qsdev->state = QUICKSPI_RESETING; + + qsdev->reset_ack = false; + + thc_int_trigger_type_select(qsdev->thc_hw, true); + + ret = acpi_tic_reset(qsdev); + if (ret) + return ret; + + ret = thc_interrupt_quiesce(qsdev->thc_hw, false); + if (ret) + return ret; + + ret = wait_event_interruptible_timeout(qsdev->reset_ack_wq, + qsdev->reset_ack, + QUICKSPI_ACK_WAIT_TIMEOUT * HZ); + if (ret <= 0 || !qsdev->reset_ack) { + dev_err_once(qsdev->dev, "Wait RESET_RESPONSE timeout, ret:%d\n", ret); + return -ETIMEDOUT; + } + + int_cause_val = thc_int_cause_read(qsdev->thc_hw); + input_report_len = FIELD_GET(HIDSPI_INPUT_HEADER_REPORT_LEN, int_cause_val); + + read_len = input_report_len * sizeof(u32); + if (read_len != HIDSPI_INPUT_BODY_SIZE(0)) { + dev_err_once(qsdev->dev, "Receive wrong RESET_RESPONSE, len = %u\n", + read_len); + return -EINVAL; + } + + /* Switch to edge trigger matching with HIDSPI protocol definition */ + thc_int_trigger_type_select(qsdev->thc_hw, true); + + ret = thc_tic_pio_read(qsdev->thc_hw, qsdev->input_report_bdy_addr, + read_len, &actual_read_len, + (u32 *)&reset_response); + if (ret || actual_read_len != read_len) { + dev_err_once(qsdev->dev, "Read RESET_RESPONSE body failed, ret = %d\n", ret); + dev_err_once(qsdev->dev, "RESET_RESPONSE body expected len = %u, actual = %u\n", + read_len, actual_read_len); + return ret ?: -EINVAL; + } + + input_rep_type = FIELD_GET(HIDSPI_IN_REP_BDY_HDR_REP_TYPE, reset_response); + + if (input_rep_type == RESET_RESPONSE) { + dev_dbg(qsdev->dev, "RESET_RESPONSE received\n"); + } else { + dev_err_once(qsdev->dev, + "Unexpected input report type: %d, expect RESET_RESPONSE\n", + input_rep_type); + return -EINVAL; + } + + qsdev->state = QUICKSPI_RESET; + + ret = quickspi_get_device_descriptor(qsdev); + if (ret) + return ret; + + return 0; +} + +int quickspi_get_report(struct quickspi_device *qsdev, + u8 report_type, unsigned int report_id, void *buf, + u32 buf_len) +{ + int rep_type; + int ret; + u32 report_len; + + if (report_type == HID_INPUT_REPORT) { + rep_type = GET_INPUT_REPORT; + } else if (report_type == HID_FEATURE_REPORT) { + rep_type = GET_FEATURE; + } else { + dev_err_once(qsdev->dev, "Unsupported report type for GET REPORT: %d\n", + report_type); + return -EINVAL; + } + + ret = write_cmd_to_txdma(qsdev, rep_type, report_id, NULL, 0); + if (ret) { + dev_err_once(qsdev->dev, "Write GET_REPORT command failed, ret = %d\n", ret); + return ret; + } + + ret = wait_event_interruptible_timeout(qsdev->get_report_cmpl_wq, + qsdev->get_report_cmpl, + QUICKSPI_ACK_WAIT_TIMEOUT * HZ); + if (ret <= 0 || !qsdev->get_report_cmpl) { + dev_err_once(qsdev->dev, "Wait Get Report Response timeout, ret:%d\n", ret); + return -ETIMEDOUT; + } + qsdev->get_report_cmpl = false; + + /* quickspi_handle_input_data() updates this from IRQ context. */ + report_len = READ_ONCE(qsdev->report_len); + if (report_len > buf_len) { + dev_err_once(qsdev->dev, "Get report response too big, %u vs %u\n", + report_len, buf_len); + return -EINVAL; + } + + memcpy(buf, qsdev->report_buf, report_len); + + return report_len; +} + +int quickspi_set_report(struct quickspi_device *qsdev, + u8 report_type, unsigned int report_id, + void *buf, u32 buf_len) +{ + int rep_type; + int ret; + + if (report_type == HID_OUTPUT_REPORT) { + rep_type = OUTPUT_REPORT; + } else if (report_type == HID_FEATURE_REPORT) { + rep_type = SET_FEATURE; + } else { + dev_err_once(qsdev->dev, "Unsupported report type for SET REPORT: %d\n", + report_type); + return -EINVAL; + } + + ret = write_cmd_to_txdma(qsdev, rep_type, report_id, buf + 1, buf_len - 1); + if (ret) { + dev_err_once(qsdev->dev, "Write SET_REPORT command failed, ret = %d\n", ret); + return ret; + } + + ret = wait_event_interruptible_timeout(qsdev->set_report_cmpl_wq, + qsdev->set_report_cmpl, + QUICKSPI_ACK_WAIT_TIMEOUT * HZ); + if (ret <= 0 || !qsdev->set_report_cmpl) { + dev_err_once(qsdev->dev, "Wait Set Report Response timeout, ret:%d\n", ret); + return -ETIMEDOUT; + } + qsdev->set_report_cmpl = false; + + return buf_len; +} diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h new file mode 100644 index 000000000..8a2338bee --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _QUICKSPI_PROTOCOL_H_ +#define _QUICKSPI_PROTOCOL_H_ + +#include + +#define QUICKSPI_ACK_WAIT_TIMEOUT 5 + +struct quickspi_device; + +void quickspi_handle_input_data(struct quickspi_device *qsdev, u32 buf_len); +int quickspi_get_report(struct quickspi_device *qsdev, u8 report_type, + unsigned int report_id, void *buf, u32 buf_len); +int quickspi_set_report(struct quickspi_device *qsdev, u8 report_type, + unsigned int report_id, void *buf, u32 buf_len); +int quickspi_get_report_descriptor(struct quickspi_device *qsdev); + +int quickspi_set_power(struct quickspi_device *qsdev, + enum hidspi_power_state power_state); + +int reset_tic(struct quickspi_device *qsdev); + +#endif /* _QUICKSPI_PROTOCOL_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c new file mode 100644 index 000000000..7b4a58e14 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -0,0 +1,1787 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include +#include +#include +#include + +#include "intel-thc-dev.h" +#include "intel-thc-hw.h" + +static int thc_regmap_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct thc_device *thc_ctx = context; + void __iomem *base = thc_ctx->mmio_addr; + + *val = ioread32(base + reg); + return 0; +} + +static int thc_regmap_write(void *context, unsigned int reg, + unsigned int val) +{ + struct thc_device *thc_ctx = context; + void __iomem *base = thc_ctx->mmio_addr; + + iowrite32(val, base + reg); + return 0; +} + +static const struct regmap_range thc_rw_ranges[] = { + regmap_reg_range(0x10, 0x14), + regmap_reg_range(0x1000, 0x1320), +}; + +static const struct regmap_access_table thc_rw_table = { + .yes_ranges = thc_rw_ranges, + .n_yes_ranges = ARRAY_SIZE(thc_rw_ranges), +}; + +static const struct regmap_config thc_regmap_cfg = { + .name = "thc_regmap_common", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x1320, + .reg_read = thc_regmap_read, + .reg_write = thc_regmap_write, + .cache_type = REGCACHE_NONE, + .fast_io = true, + .rd_table = &thc_rw_table, + .wr_table = &thc_rw_table, + .volatile_table = &thc_rw_table, +}; + +/** + * thc_clear_state - Clear THC hardware state + * + * @dev: The pointer of THC device structure + */ +static void thc_clear_state(const struct thc_device *dev) +{ + u32 val; + + /* Clear interrupt cause register */ + val = THC_M_PRT_ERR_CAUSE_INVLD_DEV_ENTRY | + THC_M_PRT_ERR_CAUSE_FRAME_BABBLE_ERR | + THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR | + THC_M_PRT_ERR_CAUSE_PRD_ENTRY_ERR; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, val, val); + + /* Clear interrupt error state */ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_IE_STALL, + THC_M_PRT_READ_DMA_CNTRL_IE_STALL); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_IE_STALL, + THC_M_PRT_READ_DMA_CNTRL_IE_STALL); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS, + THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS, + THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS); + + val = THC_M_PRT_INT_EN_TXN_ERR_INT_EN | + THC_M_PRT_INT_EN_FATAL_ERR_INT_EN | + THC_M_PRT_INT_EN_BUF_OVRRUN_ERR_INT_EN; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET, val, val); + + val = THC_M_PRT_SW_SEQ_STS_THC_SS_ERR | + THC_M_PRT_SW_SEQ_STS_TSSDONE; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, val, val); + + /* Clear RxDMA state */ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_IE_EOF, 0); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_IE_EOF, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, + THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS, + THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, + THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS, + THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, + THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS, + THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS); + + /* Clear TxDMA state */ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET, + THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL, + THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL); + + val = THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ERROR_STS | + THC_M_PRT_WRITE_INT_STS_THC_WRDMA_IOC_STS | + THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET, val, val); + + /* Reset all DMAs count */ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_DB_CNT_1_OFFSET, + THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT_RST, + THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT_RST); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_DEVINT_CNT_OFFSET, + THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT_RST, + THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT_RST); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_TPCPR, + THC_M_PRT_READ_DMA_CNTRL_TPCPR); + + /* Reset THC hardware sequence state */ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRAME_DROP_CNT_1_OFFSET, + THC_M_PRT_FRAME_DROP_CNT_1_RFDC, + THC_M_PRT_FRAME_DROP_CNT_1_RFDC); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRAME_DROP_CNT_2_OFFSET, + THC_M_PRT_FRAME_DROP_CNT_2_RFDC, + THC_M_PRT_FRAME_DROP_CNT_2_RFDC); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRM_CNT_1_OFFSET, + THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT_RST, + THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT_RST); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRM_CNT_2_OFFSET, + THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT_RST, + THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT_RST); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_RXDMA_PKT_CNT_1_OFFSET, + THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT_RST, + THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT_RST); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_RXDMA_PKT_CNT_2_OFFSET, + THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT_RST, + THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT_RST); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SWINT_CNT_1_OFFSET, + THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST, + THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SWINT_CNT_1_OFFSET, + THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST, + THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_TX_FRM_CNT_OFFSET, + THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT_RST, + THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT_RST); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_TXDMA_PKT_CNT_OFFSET, + THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT_RST, + THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT_RST); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_UFRM_CNT_1_OFFSET, + THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT_RST, + THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT_RST); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_UFRM_CNT_2_OFFSET, + THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT_RST, + THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT_RST); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_PRD_EMPTY_CNT_1_OFFSET, + THC_M_PRT_PRD_EMPTY_CNT_1_RPTEC, + THC_M_PRT_PRD_EMPTY_CNT_1_RPTEC); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_PRD_EMPTY_CNT_2_OFFSET, + THC_M_PRT_PRD_EMPTY_CNT_2_RPTEC, + THC_M_PRT_PRD_EMPTY_CNT_2_RPTEC); +} + +/** + * thc_dev_init - Allocate and initialize the THC device structure + * + * @device: The pointer of device structure + * @mem_addr: The pointer of MMIO memory address + * + * Return: The thc_device pointer on success, NULL on failed. + */ +struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr) +{ + struct thc_device *thc_dev; + int ret; + + thc_dev = devm_kzalloc(device, sizeof(*thc_dev), GFP_KERNEL); + if (!thc_dev) + return ERR_PTR(-ENOMEM); + + thc_dev->dev = device; + thc_dev->mmio_addr = mem_addr; + thc_dev->thc_regmap = devm_regmap_init(device, NULL, thc_dev, &thc_regmap_cfg); + if (IS_ERR(thc_dev->thc_regmap)) { + ret = PTR_ERR(thc_dev->thc_regmap); + dev_err_once(device, "Failed to init thc_regmap: %d\n", ret); + return ERR_PTR(ret); + } + + thc_clear_state(thc_dev); + + mutex_init(&thc_dev->thc_bus_lock); + init_waitqueue_head(&thc_dev->write_complete_wait); + init_waitqueue_head(&thc_dev->swdma_complete_wait); + + thc_dev->dma_ctx = thc_dma_init(thc_dev); + if (!thc_dev->dma_ctx) { + dev_err_once(device, "DMA context init failed\n"); + return ERR_PTR(-ENOMEM); + } + + return thc_dev; +} +EXPORT_SYMBOL_NS_GPL(thc_dev_init, "INTEL_THC"); + +static int prepare_pio(const struct thc_device *dev, const u8 pio_op, + const u32 address, const u32 size) +{ + u32 sts, ctrl, addr, mask; + + regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &sts); + + /* Check if THC previous PIO still in progress */ + if (sts & THC_M_PRT_SW_SEQ_STS_THC_SS_CIP) { + dev_err_once(dev->dev, "THC PIO is still busy!\n"); + return -EBUSY; + } + + /* Clear error bit and complete bit in state register */ + sts |= THC_M_PRT_SW_SEQ_STS_THC_SS_ERR | + THC_M_PRT_SW_SEQ_STS_TSSDONE; + regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts); + + /* Set PIO data size, opcode and interrupt capability */ + ctrl = FIELD_PREP(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC, size) | + FIELD_PREP(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CMD, pio_op); + if (dev->pio_int_supported) + ctrl |= THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE; + + mask = THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC | + THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CMD | + THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE; + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_SW_SEQ_CNTRL_OFFSET, mask, ctrl); + + /* Set PIO target address */ + addr = FIELD_PREP(THC_M_PRT_SW_SEQ_DATA0_ADDR_THC_SW_SEQ_DATA0_ADDR, address); + mask = THC_M_PRT_SW_SEQ_DATA0_ADDR_THC_SW_SEQ_DATA0_ADDR; + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, mask, addr); + return 0; +} + +static void pio_start(const struct thc_device *dev, + u32 size_in_bytes, const u32 *buffer) +{ + if (size_in_bytes && buffer) + regmap_bulk_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET, + buffer, size_in_bytes / sizeof(u32)); + + /* Enable Start bit */ + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_SW_SEQ_CNTRL_OFFSET, + THC_M_PRT_SW_SEQ_CNTRL_TSSGO, + THC_M_PRT_SW_SEQ_CNTRL_TSSGO); +} + +static int pio_complete(const struct thc_device *dev, + u32 *buffer, u32 *size) +{ + u32 sts, ctrl; + + regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &sts); + if (sts & THC_M_PRT_SW_SEQ_STS_THC_SS_ERR) { + dev_err_once(dev->dev, "PIO operation error\n"); + return -EBUSY; + } + + if (buffer && size) { + regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_CNTRL_OFFSET, &ctrl); + *size = FIELD_GET(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC, ctrl); + + regmap_bulk_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET, + buffer, *size / sizeof(u32)); + } + + sts |= THC_M_PRT_SW_SEQ_STS_THC_SS_ERR | THC_M_PRT_SW_SEQ_STS_TSSDONE; + regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts); + return 0; +} + +static int pio_wait(const struct thc_device *dev) +{ + u32 sts = 0; + int ret; + + ret = regmap_read_poll_timeout(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts, + !(sts & THC_M_PRT_SW_SEQ_STS_THC_SS_CIP || + !(sts & THC_M_PRT_SW_SEQ_STS_TSSDONE)), + THC_REGMAP_POLLING_INTERVAL_US, THC_PIO_DONE_TIMEOUT_US); + if (ret) + dev_err_once(dev->dev, "Timeout while polling PIO operation done\n"); + + return ret; +} + +/** + * thc_tic_pio_read - Read data from touch device by PIO + * + * @dev: The pointer of THC private device context + * @address: Slave address for the PIO operation + * @size: Expected read data size + * @actual_size: The pointer of the actual data size read from touch device + * @buffer: The pointer of data buffer to store the data read from touch device + * + * Return: 0 on success, other error codes on failed. + */ +int thc_tic_pio_read(struct thc_device *dev, const u32 address, + const u32 size, u32 *actual_size, u32 *buffer) +{ + u8 opcode; + int ret; + + if (size <= 0 || !actual_size || !buffer) { + dev_err(dev->dev, "Invalid input parameters, size %u, actual_size %p, buffer %p\n", + size, actual_size, buffer); + return -EINVAL; + } + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + opcode = (dev->port_type == THC_PORT_TYPE_SPI) ? + THC_PIO_OP_SPI_TIC_READ : THC_PIO_OP_I2C_TIC_READ; + + ret = prepare_pio(dev, opcode, address, size); + if (ret < 0) + goto end; + + pio_start(dev, 0, NULL); + + ret = pio_wait(dev); + if (ret < 0) + goto end; + + ret = pio_complete(dev, buffer, actual_size); + +end: + mutex_unlock(&dev->thc_bus_lock); + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_tic_pio_read, "INTEL_THC"); + +/** + * thc_tic_pio_write - Write data to touch device by PIO + * + * @dev: The pointer of THC private device context + * @address: Slave address for the PIO operation + * @size: PIO write data size + * @buffer: The pointer of the write data buffer + * + * Return: 0 on success, other error codes on failed. + */ +int thc_tic_pio_write(struct thc_device *dev, const u32 address, + const u32 size, const u32 *buffer) +{ + u8 opcode; + int ret; + + if (size <= 0 || !buffer) { + dev_err(dev->dev, "Invalid input parameters, size %u, buffer %p\n", + size, buffer); + return -EINVAL; + } + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + opcode = (dev->port_type == THC_PORT_TYPE_SPI) ? + THC_PIO_OP_SPI_TIC_WRITE : THC_PIO_OP_I2C_TIC_WRITE; + + ret = prepare_pio(dev, opcode, address, size); + if (ret < 0) + goto end; + + pio_start(dev, size, buffer); + + ret = pio_wait(dev); + if (ret < 0) + goto end; + + ret = pio_complete(dev, NULL, NULL); + +end: + mutex_unlock(&dev->thc_bus_lock); + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_tic_pio_write, "INTEL_THC"); + +/** + * thc_tic_pio_write_and_read - Write data followed by read data by PIO + * + * @dev: The pointer of THC private device context + * @address: Slave address for the PIO operation + * @write_size: PIO write data size + * @write_buffer: The pointer of the write data buffer + * @read_size: Expected PIO read data size + * @actual_size: The pointer of the actual read data size + * @read_buffer: The pointer of PIO read data buffer + * + * Return: 0 on success, other error codes on failed. + */ +int thc_tic_pio_write_and_read(struct thc_device *dev, const u32 address, + const u32 write_size, const u32 *write_buffer, + const u32 read_size, u32 *actual_size, u32 *read_buffer) +{ + u32 i2c_ctrl, mask; + int ret; + + if (dev->port_type == THC_PORT_TYPE_SPI) { + dev_err(dev->dev, "SPI port type doesn't support pio write and read!"); + return -EINVAL; + } + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + /* Config i2c PIO write and read sequence */ + i2c_ctrl = FIELD_PREP(THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_PIO_I2C_WBC, write_size); + mask = THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_PIO_I2C_WBC; + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_OFFSET, + mask, i2c_ctrl); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_OFFSET, + THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_I2C_RW_PIO_EN, + THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_I2C_RW_PIO_EN); + + ret = prepare_pio(dev, THC_PIO_OP_I2C_TIC_WRITE_AND_READ, address, read_size); + if (ret < 0) + goto end; + + pio_start(dev, write_size, write_buffer); + + ret = pio_wait(dev); + if (ret < 0) + goto end; + + ret = pio_complete(dev, read_buffer, actual_size); + +end: + mutex_unlock(&dev->thc_bus_lock); + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_tic_pio_write_and_read, "INTEL_THC"); + +/** + * thc_interrupt_config - Configure THC interrupts + * + * @dev: The pointer of THC private device context + */ +void thc_interrupt_config(struct thc_device *dev) +{ + u32 mbits, mask, r_dma_ctrl_1; + + /* Clear Error reporting interrupt status bits */ + mbits = THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS | + THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS; + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_INT_STATUS_OFFSET, + mbits, mbits); + + /* Enable Error Reporting Interrupts */ + mbits = THC_M_PRT_INT_EN_TXN_ERR_INT_EN | + THC_M_PRT_INT_EN_FATAL_ERR_INT_EN | + THC_M_PRT_INT_EN_BUF_OVRRUN_ERR_INT_EN; + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_INT_EN_OFFSET, + mbits, mbits); + + /* Clear PIO Interrupt status bits */ + mbits = THC_M_PRT_SW_SEQ_STS_THC_SS_ERR | + THC_M_PRT_SW_SEQ_STS_TSSDONE; + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_SW_SEQ_STS_OFFSET, + mbits, mbits); + + /* Read Interrupts */ + regmap_read(dev->thc_regmap, + THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + &r_dma_ctrl_1); + /* Disable RxDMA1 */ + r_dma_ctrl_1 &= ~THC_M_PRT_READ_DMA_CNTRL_IE_EOF; + regmap_write(dev->thc_regmap, + THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + r_dma_ctrl_1); + + /* Ack EOF Interrupt RxDMA1 */ + mbits = THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS; + /* Ack NonDMA Interrupt */ + mbits |= THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS; + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, + mbits, mbits); + + /* Ack EOF Interrupt RxDMA2 */ + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, + THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS, + THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS); + + /* Write Interrupts */ + /* Disable TxDMA */ + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_WRITE_DMA_CNTRL_OFFSET, + THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL, + 0); + + /* Clear TxDMA interrupt status bits */ + mbits = THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ERROR_STS; + mbits |= THC_M_PRT_WRITE_INT_STS_THC_WRDMA_IOC_STS; + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_WRITE_INT_STS_OFFSET, + mbits, mbits); + + /* Enable Non-DMA device inband interrupt */ + r_dma_ctrl_1 |= THC_M_PRT_READ_DMA_CNTRL_IE_NDDI; + regmap_write(dev->thc_regmap, + THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + r_dma_ctrl_1); + + if (dev->port_type == THC_PORT_TYPE_SPI) { + /* Edge triggered interrupt */ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET, + THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN, + THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN); + } else { + /* Level triggered interrupt */ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET, + THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN, 0); + + mbits = THC_M_PRT_INT_EN_THC_I2C_IC_MST_ON_HOLD_INT_EN | + THC_M_PRT_INT_EN_THC_I2C_IC_SCL_STUCK_AT_LOW_DET_INT_EN | + THC_M_PRT_INT_EN_THC_I2C_IC_TX_ABRT_INT_EN | + THC_M_PRT_INT_EN_THC_I2C_IC_TX_OVER_INT_EN | + THC_M_PRT_INT_EN_THC_I2C_IC_RX_FULL_INT_EN | + THC_M_PRT_INT_EN_THC_I2C_IC_RX_OVER_INT_EN | + THC_M_PRT_INT_EN_THC_I2C_IC_RX_UNDER_INT_EN; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET, + mbits, mbits); + } + + thc_set_pio_interrupt_support(dev, false); + + /* HIDSPI specific settings */ + if (dev->port_type == THC_PORT_TYPE_SPI) { + mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_OFFSET, + THC_BIT_OFFSET_INTERRUPT_TYPE) | + FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_LEN, + THC_BIT_LENGTH_INTERRUPT_TYPE) | + FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_EOF_OFFSET, + THC_BIT_OFFSET_LAST_FRAGMENT_FLAG) | + FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL, + THC_BITMASK_INVALID_TYPE_DATA); + mask = THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_OFFSET | + THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_LEN | + THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_EOF_OFFSET | + THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_DEVINT_CFG_1_OFFSET, + mask, mbits); + + mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_OFFSET, + THC_BIT_OFFSET_MICROFRAME_SIZE) | + FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_LEN, + THC_BIT_LENGTH_MICROFRAME_SIZE) | + FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_UNIT, + THC_UNIT_MICROFRAME_SIZE) | + THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_IGNORE | + THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_VAL; + mask = THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_OFFSET | + THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_LEN | + THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_UNIT | + THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_IGNORE | + THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_VAL; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_DEVINT_CFG_2_OFFSET, + mask, mbits); + } +} +EXPORT_SYMBOL_NS_GPL(thc_interrupt_config, "INTEL_THC"); + +/** + * thc_int_trigger_type_select - Select THC interrupt trigger type + * + * @dev: the pointer of THC private device context + * @edge_trigger: determine the interrupt is edge triggered or level triggered + */ +void thc_int_trigger_type_select(struct thc_device *dev, bool edge_trigger) +{ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET, + THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN, + edge_trigger ? THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN : 0); +} +EXPORT_SYMBOL_NS_GPL(thc_int_trigger_type_select, "INTEL_THC"); + +/** + * thc_interrupt_enable - Enable or disable THC interrupt + * + * @dev: the pointer of THC private device context + * @int_enable: the flag to control THC interrupt enable or disable + */ +void thc_interrupt_enable(struct thc_device *dev, bool int_enable) +{ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET, + THC_M_PRT_INT_EN_GBL_INT_EN, + int_enable ? THC_M_PRT_INT_EN_GBL_INT_EN : 0); +} +EXPORT_SYMBOL_NS_GPL(thc_interrupt_enable, "INTEL_THC"); + +/** + * thc_interrupt_quiesce - Quiesce or unquiesce external touch device interrupt + * + * @dev: the pointer of THC private device context + * @int_quiesce: the flag to determine quiesce or unquiesce device interrupt + * + * Return: 0 on success, other error codes on failed + */ +int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce) +{ + u32 ctrl; + int ret; + + regmap_read(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, &ctrl); + if (!(ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN) && !int_quiesce) { + dev_warn(dev->dev, "THC interrupt already unquiesce\n"); + return 0; + } + + if ((ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN) && int_quiesce) { + dev_warn(dev->dev, "THC interrupt already quiesce\n"); + return 0; + } + + /* Quiesce device interrupt - Set quiesce bit and waiting for THC HW to ACK */ + if (int_quiesce) + regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, + THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN, + THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN); + + ret = regmap_read_poll_timeout(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, ctrl, + ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS, + THC_REGMAP_POLLING_INTERVAL_US, THC_QUIESCE_EN_TIMEOUT_US); + if (ret) { + dev_err_once(dev->dev, + "Timeout while waiting THC idle, target quiesce state = %s\n", + str_true_false(int_quiesce)); + return ret; + } + + /* Unquiesce device interrupt - Clear the quiesce bit */ + if (!int_quiesce) + regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, + THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN, 0); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_interrupt_quiesce, "INTEL_THC"); + +/** + * thc_set_pio_interrupt_support - Determine PIO interrupt is supported or not + * + * @dev: The pointer of THC private device context + * @supported: The flag to determine enabling PIO interrupt or not + */ +void thc_set_pio_interrupt_support(struct thc_device *dev, bool supported) +{ + dev->pio_int_supported = supported; +} +EXPORT_SYMBOL_NS_GPL(thc_set_pio_interrupt_support, "INTEL_THC"); + +/** + * thc_ltr_config - Configure THC Latency Tolerance Reporting(LTR) settings + * + * @dev: The pointer of THC private device context + * @active_ltr_us: active LTR value, unit is us + * @lp_ltr_us: low power LTR value, unit is us + */ +void thc_ltr_config(struct thc_device *dev, u32 active_ltr_us, u32 lp_ltr_us) +{ + u32 active_ltr_scale, lp_ltr_scale, ltr_ctrl, ltr_mask, orig, tmp; + + if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_3 && + active_ltr_us < THC_LTR_MAX_VAL_SCALE_3) { + active_ltr_scale = THC_LTR_SCALE_3; + active_ltr_us = active_ltr_us >> 5; + } else if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_4 && + active_ltr_us < THC_LTR_MAX_VAL_SCALE_4) { + active_ltr_scale = THC_LTR_SCALE_4; + active_ltr_us = active_ltr_us >> 10; + } else if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_5 && + active_ltr_us < THC_LTR_MAX_VAL_SCALE_5) { + active_ltr_scale = THC_LTR_SCALE_5; + active_ltr_us = active_ltr_us >> 15; + } else { + active_ltr_scale = THC_LTR_SCALE_2; + } + + if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_3 && + lp_ltr_us < THC_LTR_MAX_VAL_SCALE_3) { + lp_ltr_scale = THC_LTR_SCALE_3; + lp_ltr_us = lp_ltr_us >> 5; + } else if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_4 && + lp_ltr_us < THC_LTR_MAX_VAL_SCALE_4) { + lp_ltr_scale = THC_LTR_SCALE_4; + lp_ltr_us = lp_ltr_us >> 10; + } else if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_5 && + lp_ltr_us < THC_LTR_MAX_VAL_SCALE_5) { + lp_ltr_scale = THC_LTR_SCALE_5; + lp_ltr_us = lp_ltr_us >> 15; + } else { + lp_ltr_scale = THC_LTR_SCALE_2; + } + + regmap_read(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, &orig); + ltr_ctrl = FIELD_PREP(THC_M_CMN_LTR_CTRL_ACT_LTR_VAL, active_ltr_us) | + FIELD_PREP(THC_M_CMN_LTR_CTRL_ACT_LTR_SCALE, active_ltr_scale) | + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ | + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN | + FIELD_PREP(THC_M_CMN_LTR_CTRL_LP_LTR_VAL, lp_ltr_us) | + FIELD_PREP(THC_M_CMN_LTR_CTRL_LP_LTR_SCALE, lp_ltr_scale) | + THC_M_CMN_LTR_CTRL_LP_LTR_REQ; + + ltr_mask = THC_M_CMN_LTR_CTRL_ACT_LTR_VAL | + THC_M_CMN_LTR_CTRL_ACT_LTR_SCALE | + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ | + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN | + THC_M_CMN_LTR_CTRL_LP_LTR_VAL | + THC_M_CMN_LTR_CTRL_LP_LTR_SCALE | + THC_M_CMN_LTR_CTRL_LP_LTR_REQ | + THC_M_CMN_LTR_CTRL_LP_LTR_EN; + + tmp = orig & ~ltr_mask; + tmp |= ltr_ctrl & ltr_mask; + + regmap_write(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, tmp); +} +EXPORT_SYMBOL_NS_GPL(thc_ltr_config, "INTEL_THC"); + +/** + * thc_change_ltr_mode - Change THC LTR mode + * + * @dev: The pointer of THC private device context + * @ltr_mode: LTR mode(active or low power) + */ +void thc_change_ltr_mode(struct thc_device *dev, u32 ltr_mode) +{ + if (ltr_mode == THC_LTR_MODE_ACTIVE) { + regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, + THC_M_CMN_LTR_CTRL_LP_LTR_EN, 0); + regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN, + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN); + return; + } + + regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN, 0); + regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, + THC_M_CMN_LTR_CTRL_LP_LTR_EN, + THC_M_CMN_LTR_CTRL_LP_LTR_EN); +} +EXPORT_SYMBOL_NS_GPL(thc_change_ltr_mode, "INTEL_THC"); + +/** + * thc_ltr_unconfig - Unconfigure THC Latency Tolerance Reporting(LTR) settings + * + * @dev: The pointer of THC private device context + */ +void thc_ltr_unconfig(struct thc_device *dev) +{ + u32 ltr_ctrl, bits_clear; + + regmap_read(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, <r_ctrl); + bits_clear = THC_M_CMN_LTR_CTRL_LP_LTR_EN | + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN | + THC_M_CMN_LTR_CTRL_LP_LTR_REQ | + THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ; + + ltr_ctrl &= ~bits_clear; + + regmap_write(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, ltr_ctrl); +} +EXPORT_SYMBOL_NS_GPL(thc_ltr_unconfig, "INTEL_THC"); + +/** + * thc_int_cause_read - Read interrupt cause register value + * + * @dev: The pointer of THC private device context + * + * Return: The interrupt cause register value + */ +u32 thc_int_cause_read(struct thc_device *dev) +{ + u32 int_cause; + + regmap_read(dev->thc_regmap, + THC_M_PRT_DEV_INT_CAUSE_REG_VAL_OFFSET, &int_cause); + + return int_cause; +} +EXPORT_SYMBOL_NS_GPL(thc_int_cause_read, "INTEL_THC"); + +static void thc_print_txn_error_cause(const struct thc_device *dev) +{ + bool known_error = false; + u32 cause = 0; + + regmap_read(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, &cause); + + if (cause & THC_M_PRT_ERR_CAUSE_PRD_ENTRY_ERR) { + dev_err(dev->dev, "TXN Error: Invalid PRD Entry\n"); + known_error = true; + } + if (cause & THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR) { + dev_err(dev->dev, "TXN Error: THC Buffer Overrun\n"); + known_error = true; + } + if (cause & THC_M_PRT_ERR_CAUSE_FRAME_BABBLE_ERR) { + dev_err(dev->dev, "TXN Error: Frame Babble\n"); + known_error = true; + } + if (cause & THC_M_PRT_ERR_CAUSE_INVLD_DEV_ENTRY) { + dev_err(dev->dev, "TXN Error: Invalid Device Register Setting\n"); + known_error = true; + } + + /* Clear interrupt status bits */ + regmap_write(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, cause); + + if (!known_error) + dev_err(dev->dev, "TXN Error does not match any known value: 0x%X\n", + cause); +} + +/** + * thc_interrupt_handler - Handle THC interrupts + * + * THC interrupts include several types: external touch device (TIC) non-DMA + * interrupts, PIO completion interrupts, DMA interrtups, I2C subIP raw + * interrupts and error interrupts. + * + * This is a help function for interrupt processing, it detects interrupt + * type, clear the interrupt status bit and return the interrupt type to caller + * for future processing. + * + * @dev: The pointer of THC private device context + * + * Return: The combined flag for interrupt type + */ +int thc_interrupt_handler(struct thc_device *dev) +{ + u32 read_sts_1, read_sts_2, read_sts_sw, write_sts; + u32 int_sts, err_cause, seq_cntrl, seq_sts; + int interrupt_type = 0; + + regmap_read(dev->thc_regmap, + THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, &read_sts_1); + + if (read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS) { + dev_dbg(dev->dev, "THC non-DMA device interrupt\n"); + + regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, + NONDMA_INT_STS_BIT); + + interrupt_type |= BIT(THC_NONDMA_INT); + + return interrupt_type; + } + + regmap_read(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, &int_sts); + + if (int_sts & THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS) { + dev_err(dev->dev, "THC transaction error, int_sts: 0x%08X\n", int_sts); + thc_print_txn_error_cause(dev); + + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + TXN_ERR_INT_STS_BIT); + + interrupt_type |= BIT(THC_TXN_ERR_INT); + + return interrupt_type; + } + + regmap_read(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, &err_cause); + regmap_read(dev->thc_regmap, + THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, &read_sts_2); + + if (err_cause & THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR || + read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_STALL_STS || + read_sts_2 & THC_M_PRT_READ_DMA_INT_STS_STALL_STS) { + dev_err(dev->dev, "Buffer overrun or RxDMA engine stalled!\n"); + thc_print_txn_error_cause(dev); + + regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, + THC_M_PRT_READ_DMA_INT_STS_STALL_STS); + regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, + THC_M_PRT_READ_DMA_INT_STS_STALL_STS); + regmap_write(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, + THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR); + + interrupt_type |= BIT(THC_TXN_ERR_INT); + + return interrupt_type; + } + + if (int_sts & THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS) { + dev_err_once(dev->dev, "THC FATAL error, int_sts: 0x%08X\n", int_sts); + + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + TXN_FATAL_INT_STS_BIT); + + interrupt_type |= BIT(THC_FATAL_ERR_INT); + + return interrupt_type; + } + + regmap_read(dev->thc_regmap, + THC_M_PRT_SW_SEQ_CNTRL_OFFSET, &seq_cntrl); + regmap_read(dev->thc_regmap, + THC_M_PRT_SW_SEQ_STS_OFFSET, &seq_sts); + + if (seq_cntrl & THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE && + seq_sts & THC_M_PRT_SW_SEQ_STS_TSSDONE) { + dev_dbg(dev->dev, "THC_SS_CD_IE and TSSDONE are set\n"); + interrupt_type |= BIT(THC_PIO_DONE_INT); + } + + if (read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS) { + dev_dbg(dev->dev, "Got RxDMA1 Read Interrupt\n"); + + regmap_write(dev->thc_regmap, + THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, read_sts_1); + + interrupt_type |= BIT(THC_RXDMA1_INT); + } + + if (read_sts_2 & THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS) { + dev_dbg(dev->dev, "Got RxDMA2 Read Interrupt\n"); + + regmap_write(dev->thc_regmap, + THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, read_sts_2); + + interrupt_type |= BIT(THC_RXDMA2_INT); + } + + regmap_read(dev->thc_regmap, + THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET, &read_sts_sw); + + if (read_sts_sw & THC_M_PRT_READ_DMA_INT_STS_DMACPL_STS) { + dev_dbg(dev->dev, "Got SwDMA Read Interrupt\n"); + + regmap_write(dev->thc_regmap, + THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET, read_sts_sw); + + dev->swdma_done = true; + wake_up_interruptible(&dev->swdma_complete_wait); + + interrupt_type |= BIT(THC_SWDMA_INT); + } + + regmap_read(dev->thc_regmap, + THC_M_PRT_WRITE_INT_STS_OFFSET, &write_sts); + + if (write_sts & THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS) { + dev_dbg(dev->dev, "Got TxDMA Write complete Interrupt\n"); + + regmap_write(dev->thc_regmap, + THC_M_PRT_WRITE_INT_STS_OFFSET, write_sts); + + dev->write_done = true; + wake_up_interruptible(&dev->write_complete_wait); + + interrupt_type |= BIT(THC_TXDMA_INT); + } + + if (int_sts & THC_M_PRT_INT_STATUS_DEV_RAW_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_DEV_RAW_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_UNDER_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_UNDER_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_OVER_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_OVER_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_FULL_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_FULL_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_OVER_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_OVER_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_EMPTY_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_EMPTY_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_ABRT_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_ABRT_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_ACTIVITY_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_ACTIVITY_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_SCL_STUCK_AT_LOW_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_SCL_STUCK_AT_LOW_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_STOP_DET_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_STOP_DET_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_START_DET_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_START_DET_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_MST_ON_HOLD_INT_STS) { + regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, + THC_M_PRT_INT_STATUS_THC_I2C_IC_MST_ON_HOLD_INT_STS); + interrupt_type |= BIT(THC_I2CSUBIP_INT); + } + + if (!interrupt_type) + interrupt_type |= BIT(THC_UNKNOWN_INT); + + return interrupt_type; +} +EXPORT_SYMBOL_NS_GPL(thc_interrupt_handler, "INTEL_THC"); + +/** + * thc_port_select - Set THC port type + * + * @dev: The pointer of THC private device context + * @port_type: THC port type to use for current device + * + * Return: 0 on success, other error codes on failed. + */ +int thc_port_select(struct thc_device *dev, enum thc_port_type port_type) +{ + u32 ctrl, mask; + + if (port_type == THC_PORT_TYPE_SPI) { + dev_dbg(dev->dev, "Set THC port type to SPI\n"); + dev->port_type = THC_PORT_TYPE_SPI; + + /* Enable delay of CS assertion and set to default value */ + ctrl = THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN | + FIELD_PREP(THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL, + THC_CSA_CK_DELAY_VAL_DEFAULT); + mask = THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN | + THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + mask, ctrl); + } else if (port_type == THC_PORT_TYPE_I2C) { + dev_dbg(dev->dev, "Set THC port type to I2C\n"); + dev->port_type = THC_PORT_TYPE_I2C; + + /* Set THC transition arbitration policy to frame boundary for I2C */ + ctrl = FIELD_PREP(THC_M_PRT_CONTROL_THC_ARB_POLICY, + THC_ARB_POLICY_FRAME_BOUNDARY); + mask = THC_M_PRT_CONTROL_THC_ARB_POLICY; + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, mask, ctrl); + } else { + dev_err(dev->dev, "unsupported THC port type: %d\n", port_type); + return -EINVAL; + } + + ctrl = FIELD_PREP(THC_M_PRT_CONTROL_PORT_TYPE, port_type); + mask = THC_M_PRT_CONTROL_PORT_TYPE; + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, mask, ctrl); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_port_select, "INTEL_THC"); + +#define THC_SPI_FREQUENCY_7M 7812500 +#define THC_SPI_FREQUENCY_10M 10416700 +#define THC_SPI_FREQUENCY_15M 15625000 +#define THC_SPI_FREQUENCY_17M 17857100 +#define THC_SPI_FREQUENCY_20M 20833000 +#define THC_SPI_FREQUENCY_25M 25000000 +#define THC_SPI_FREQUENCY_31M 31250000 +#define THC_SPI_FREQUENCY_35M 35714200 +#define THC_SPI_FREQUENCY_41M 41666700 +#define THC_SPI_FREQUENCY_50M 50000000 + +#define THC_SPI_LOW_FREQUENCY THC_SPI_FREQUENCY_17M + +static u8 thc_get_spi_freq_div_val(struct thc_device *dev, u32 spi_freq_val) +{ + static const int frequency[] = { + THC_SPI_FREQUENCY_7M, + THC_SPI_FREQUENCY_10M, + THC_SPI_FREQUENCY_15M, + THC_SPI_FREQUENCY_17M, + THC_SPI_FREQUENCY_20M, + THC_SPI_FREQUENCY_25M, + THC_SPI_FREQUENCY_31M, + THC_SPI_FREQUENCY_35M, + THC_SPI_FREQUENCY_41M, + THC_SPI_FREQUENCY_50M, + }; + static const u8 frequency_div[] = { + THC_SPI_FRQ_DIV_2, + THC_SPI_FRQ_DIV_1, + THC_SPI_FRQ_DIV_1, + THC_SPI_FRQ_DIV_7, + THC_SPI_FRQ_DIV_6, + THC_SPI_FRQ_DIV_5, + THC_SPI_FRQ_DIV_4, + THC_SPI_FRQ_DIV_3, + THC_SPI_FRQ_DIV_3, + THC_SPI_FRQ_DIV_2, + }; + int size = ARRAY_SIZE(frequency); + u32 closest_freq; + u8 freq_div; + int i; + + for (i = size - 1; i >= 0; i--) + if ((int)spi_freq_val - frequency[i] >= 0) + break; + + if (i < 0) { + dev_err_once(dev->dev, "Not supported SPI frequency %d\n", spi_freq_val); + return THC_SPI_FRQ_RESERVED; + } + + closest_freq = frequency[i]; + freq_div = frequency_div[i]; + + dev_dbg(dev->dev, + "Setting SPI frequency: spi_freq_val = %u, Closest freq = %u\n", + spi_freq_val, closest_freq); + + return freq_div; +} + +/** + * thc_spi_read_config - Configure SPI bus read attributes + * + * @dev: The pointer of THC private device context + * @spi_freq_val: SPI read frequecy value + * @io_mode: SPI read IO mode + * @opcode: Read opcode + * @spi_rd_mps: SPI read max packet size + * + * Return: 0 on success, other error codes on failed. + */ +int thc_spi_read_config(struct thc_device *dev, u32 spi_freq_val, + u32 io_mode, u32 opcode, u32 spi_rd_mps) +{ + bool is_low_freq = false; + u32 cfg, mask; + u8 freq_div; + + freq_div = thc_get_spi_freq_div_val(dev, spi_freq_val); + if (freq_div == THC_SPI_FRQ_RESERVED) + return -EINVAL; + + if (spi_freq_val < THC_SPI_LOW_FREQUENCY) + is_low_freq = true; + + /* 10M, 35M and 50M CLK need 1.5, 3.5 and 2.5 half divider */ + if ((freq_div == THC_SPI_FRQ_DIV_2 && spi_freq_val >= THC_SPI_FREQUENCY_50M) || + (freq_div == THC_SPI_FRQ_DIV_3 && spi_freq_val < THC_SPI_FREQUENCY_41M) || + (freq_div == THC_SPI_FRQ_DIV_1 && spi_freq_val < THC_SPI_FREQUENCY_15M)) { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE); + } else { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, 0); + } + + cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCRF, freq_div) | + FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TRMODE, io_mode) | + (is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) | + FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_RD_MPS, spi_rd_mps); + mask = THC_M_PRT_SPI_CFG_SPI_TCRF | + THC_M_PRT_SPI_CFG_SPI_TRMODE | + THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN | + THC_M_PRT_SPI_CFG_SPI_RD_MPS; + + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_SPI_CFG_OFFSET, mask, cfg); + + if (io_mode == THC_QUAD_IO) + opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_QIO, opcode); + else if (io_mode == THC_DUAL_IO) + opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_DIO, opcode); + else + opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_SIO, opcode); + + regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, opcode); + regmap_write(dev->thc_regmap, THC_M_PRT_SPI_DMARD_OPCODE_OFFSET, opcode); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_spi_read_config, "INTEL_THC"); + +/** + * thc_spi_write_config - Configure SPI bus write attributes + * + * @dev: The pointer of THC private device context + * @spi_freq_val: SPI write frequecy value + * @io_mode: SPI write IO mode + * @opcode: Write opcode + * @spi_wr_mps: SPI write max packet size + * @perf_limit: Performance limitation in unit of 10us + * + * Return: 0 on success, other error codes on failed. + */ +int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val, + u32 io_mode, u32 opcode, u32 spi_wr_mps, + u32 perf_limit) +{ + bool is_low_freq = false; + u32 cfg, mask; + u8 freq_div; + + freq_div = thc_get_spi_freq_div_val(dev, spi_freq_val); + if (freq_div == THC_SPI_FRQ_RESERVED) + return -EINVAL; + + if (spi_freq_val < THC_SPI_LOW_FREQUENCY) + is_low_freq = true; + + /* 10M, 35M and 50M CLK need 1.5, 3.5 and 2.5 half divider */ + if ((freq_div == THC_SPI_FRQ_DIV_2 && spi_freq_val >= THC_SPI_FREQUENCY_50M) || + (freq_div == THC_SPI_FRQ_DIV_3 && spi_freq_val < THC_SPI_FREQUENCY_41M) || + (freq_div == THC_SPI_FRQ_DIV_1 && spi_freq_val < THC_SPI_FREQUENCY_15M)) { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE); + } else { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, 0); + } + + cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCWF, freq_div) | + FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TWMODE, io_mode) | + (is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) | + FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_WR_MPS, spi_wr_mps); + mask = THC_M_PRT_SPI_CFG_SPI_TCWF | + THC_M_PRT_SPI_CFG_SPI_TWMODE | + THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN | + THC_M_PRT_SPI_CFG_SPI_WR_MPS; + + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_SPI_CFG_OFFSET, mask, cfg); + + if (io_mode == THC_QUAD_IO) + opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_QIO, opcode); + else if (io_mode == THC_DUAL_IO) + opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_DIO, opcode); + else + opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_SIO, opcode); + + regmap_write(dev->thc_regmap, THC_M_PRT_SPI_WR_OPCODE_OFFSET, opcode); + + dev->perf_limit = perf_limit; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_spi_write_config, "INTEL_THC"); + +/** + * thc_spi_input_output_address_config - Configure SPI input and output addresses + * + * @dev: the pointer of THC private device context + * @input_hdr_addr: input report header address + * @input_bdy_addr: input report body address + * @output_addr: output report address + */ +void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_addr, + u32 input_bdy_addr, u32 output_addr) +{ + regmap_write(dev->thc_regmap, + THC_M_PRT_DEV_INT_CAUSE_ADDR_OFFSET, input_hdr_addr); + regmap_write(dev->thc_regmap, + THC_M_PRT_RD_BULK_ADDR_1_OFFSET, input_bdy_addr); + regmap_write(dev->thc_regmap, + THC_M_PRT_RD_BULK_ADDR_2_OFFSET, input_bdy_addr); + regmap_write(dev->thc_regmap, + THC_M_PRT_WR_BULK_ADDR_OFFSET, output_addr); +} +EXPORT_SYMBOL_NS_GPL(thc_spi_input_output_address_config, "INTEL_THC"); + +static int thc_i2c_subip_pio_read(struct thc_device *dev, const u32 address, + u32 *size, u32 *buffer) +{ + int ret; + + if (!size || *size == 0 || !buffer) { + dev_err(dev->dev, "Invalid input parameters, size %p, buffer %p\n", + size, buffer); + return -EINVAL; + } + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + ret = prepare_pio(dev, THC_PIO_OP_I2C_SUBSYSTEM_READ, address, *size); + if (ret < 0) + goto end; + + pio_start(dev, 0, NULL); + + ret = pio_wait(dev); + if (ret < 0) + goto end; + + ret = pio_complete(dev, buffer, size); + if (ret < 0) + goto end; + +end: + mutex_unlock(&dev->thc_bus_lock); + + if (ret) + dev_err_once(dev->dev, "Read THC I2C SubIP register failed %d, offset %u\n", + ret, address); + + return ret; +} + +static int thc_i2c_subip_pio_write(struct thc_device *dev, const u32 address, + const u32 size, const u32 *buffer) +{ + int ret; + + if (size == 0 || !buffer) { + dev_err(dev->dev, "Invalid input parameters, size %u, buffer %p\n", + size, buffer); + return -EINVAL; + } + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + ret = prepare_pio(dev, THC_PIO_OP_I2C_SUBSYSTEM_WRITE, address, size); + if (ret < 0) + goto end; + + pio_start(dev, size, buffer); + + ret = pio_wait(dev); + if (ret < 0) + goto end; + + ret = pio_complete(dev, NULL, NULL); + if (ret < 0) + goto end; + +end: + mutex_unlock(&dev->thc_bus_lock); + + if (ret) + dev_err_once(dev->dev, "Write THC I2C SubIP register failed %d, offset %u\n", + ret, address); + + return ret; +} + +#define I2C_SUBIP_CON_DEFAULT 0x663 +#define I2C_SUBIP_INT_MASK_DEFAULT 0x7FFF +#define I2C_SUBIP_RX_TL_DEFAULT 62 +#define I2C_SUBIP_TX_TL_DEFAULT 0 +#define I2C_SUBIP_DMA_TDLR_DEFAULT 7 +#define I2C_SUBIP_DMA_RDLR_DEFAULT 7 + +static int thc_i2c_subip_bus_config(struct thc_device *dev, const struct thc_i2c_config *i2c_config) +{ + u32 hcnt_offset, lcnt_offset; + u32 read_size = sizeof(u32); + u32 val = 0; + int ret; + + ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val); + if (ret < 0) + return ret; + + val &= ~(THC_I2C_IC_TAR_IC_TAR | THC_I2C_IC_TAR_IC_10BITADDR_MASTER); + val |= FIELD_PREP(THC_I2C_IC_TAR_IC_10BITADDR_MASTER, i2c_config->addr_mode); + val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, i2c_config->target_addr); + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + switch (i2c_config->speed) { + case THC_I2C_STANDARD: + hcnt_offset = THC_I2C_IC_SS_SCL_HCNT_OFFSET; + lcnt_offset = THC_I2C_IC_SS_SCL_LCNT_OFFSET; + break; + + case THC_I2C_FAST_AND_PLUS: + hcnt_offset = THC_I2C_IC_FS_SCL_HCNT_OFFSET; + lcnt_offset = THC_I2C_IC_FS_SCL_LCNT_OFFSET; + break; + + case THC_I2C_HIGH_SPEED: + hcnt_offset = THC_I2C_IC_HS_SCL_HCNT_OFFSET; + lcnt_offset = THC_I2C_IC_HS_SCL_LCNT_OFFSET; + break; + + default: + dev_err_once(dev->dev, "Unsupported i2c speed %d\n", i2c_config->speed); + ret = -EINVAL; + return ret; + } + + ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &i2c_config->scl_hcnt); + if (ret < 0) + return ret; + + ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &i2c_config->scl_lcnt); + if (ret < 0) + return ret; + + val = I2C_SUBIP_CON_DEFAULT & ~THC_I2C_IC_CON_SPEED; + val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, i2c_config->speed); + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_CON_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_SDA_HOLD_OFFSET, &read_size, &val); + if (ret < 0) + return ret; + + if (i2c_config->sda_tx_hold) { + val &= ~THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD; + val |= FIELD_PREP(THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD, i2c_config->sda_tx_hold); + } + + if (i2c_config->sda_rx_hold) { + val &= ~THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD; + val |= FIELD_PREP(THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD, i2c_config->sda_rx_hold); + } + + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_SDA_HOLD_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + return 0; +} + +static u32 i2c_subip_regs[] = { + THC_I2C_IC_CON_OFFSET, + THC_I2C_IC_TAR_OFFSET, + THC_I2C_IC_INTR_MASK_OFFSET, + THC_I2C_IC_RX_TL_OFFSET, + THC_I2C_IC_TX_TL_OFFSET, + THC_I2C_IC_SDA_HOLD_OFFSET, + THC_I2C_IC_DMA_CR_OFFSET, + THC_I2C_IC_DMA_TDLR_OFFSET, + THC_I2C_IC_DMA_RDLR_OFFSET, + THC_I2C_IC_SS_SCL_HCNT_OFFSET, + THC_I2C_IC_SS_SCL_LCNT_OFFSET, + THC_I2C_IC_FS_SCL_HCNT_OFFSET, + THC_I2C_IC_FS_SCL_LCNT_OFFSET, + THC_I2C_IC_HS_SCL_HCNT_OFFSET, + THC_I2C_IC_HS_SCL_LCNT_OFFSET, + THC_I2C_IC_ENABLE_OFFSET, +}; + +/** + * thc_i2c_subip_init - Initialize and configure THC I2C subsystem + * + * @dev: The pointer of THC private device context + * @i2c_config: The pointer of THC I2C bus configure structure + * + * Return: 0 on success, other error codes on failed. + */ +int thc_i2c_subip_init(struct thc_device *dev, const struct thc_i2c_config *i2c_config) +{ + u32 read_size = sizeof(u32); + u32 val; + int ret; + + if (!dev || !i2c_config) + return -EINVAL; + + ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val); + if (ret < 0) + return ret; + + val &= ~THC_I2C_IC_ENABLE_ENABLE; + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_ENABLE_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + ret = thc_i2c_subip_bus_config(dev, i2c_config); + if (ret < 0) + return ret; + + val = I2C_SUBIP_INT_MASK_DEFAULT; + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_INTR_MASK_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + val = I2C_SUBIP_RX_TL_DEFAULT; + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_RX_TL_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + val = I2C_SUBIP_TX_TL_DEFAULT; + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TX_TL_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + val = THC_I2C_IC_DMA_CR_RDMAE | THC_I2C_IC_DMA_CR_TDMAE; + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_CR_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + val = I2C_SUBIP_DMA_TDLR_DEFAULT; + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_TDLR_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + val = I2C_SUBIP_DMA_RDLR_DEFAULT; + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_RDLR_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val); + if (ret < 0) + return ret; + + val |= THC_I2C_IC_ENABLE_ENABLE; + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_ENABLE_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + dev->i2c_subip_regs = devm_kzalloc(dev->dev, sizeof(i2c_subip_regs), GFP_KERNEL); + if (!dev->i2c_subip_regs) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_init, "INTEL_THC"); + +/** + * thc_i2c_subip_regs_save - Save THC I2C sub-subsystem register values to THC device context + * + * @dev: The pointer of THC private device context + * + * Return: 0 on success, other error codes on failed. + */ +int thc_i2c_subip_regs_save(struct thc_device *dev) +{ + int ret; + u32 read_size = sizeof(u32); + + for (int i = 0; i < ARRAY_SIZE(i2c_subip_regs); i++) { + ret = thc_i2c_subip_pio_read(dev, i2c_subip_regs[i], + &read_size, &dev->i2c_subip_regs[i]); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_save, "INTEL_THC"); + +/** + * thc_i2c_subip_regs_restore - Restore THC I2C subsystem registers from THC device context + * + * @dev: The pointer of THC private device context + * + * Return: 0 on success, other error codes on failed. + */ +int thc_i2c_subip_regs_restore(struct thc_device *dev) +{ + int ret; + u32 write_size = sizeof(u32); + + for (int i = 0; i < ARRAY_SIZE(i2c_subip_regs); i++) { + ret = thc_i2c_subip_pio_write(dev, i2c_subip_regs[i], + write_size, &dev->i2c_subip_regs[i]); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_restore, "INTEL_THC"); + +/** + * thc_i2c_set_rx_max_size - Set I2C Rx transfer max input size + * @dev: The pointer of THC private device context + * @max_rx_size: Max input report packet size for input report + * + * Set @max_rx_size for I2C RxDMA max input size control feature. + * + * Return: 0 on success, other error codes on failure. + */ +int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size) +{ + u32 val; + int ret; + + if (!dev) + return -EINVAL; + + if (!max_rx_size) + return -EOPNOTSUPP; + + ret = regmap_read(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, &val); + if (ret) + return ret; + + val = val & ~THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE; + val |= FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE, max_rx_size); + + ret = regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, val); + if (ret) + return ret; + + dev->i2c_max_rx_size = max_rx_size; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_i2c_set_rx_max_size, "INTEL_THC"); + +/** + * thc_i2c_rx_max_size_enable - Enable I2C Rx max input size control + * @dev: The pointer of THC private device context + * @enable: Enable max input size control or not + * + * Enable or disable I2C RxDMA max input size control feature. + * Max input size control only can be enabled after max input size + * was set by thc_i2c_set_rx_max_size(). + * + * Return: 0 on success, other error codes on failure. + */ +int thc_i2c_rx_max_size_enable(struct thc_device *dev, bool enable) +{ + u32 mask = THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE_EN; + u32 val = enable ? mask : 0; + int ret; + + if (!dev) + return -EINVAL; + + if (!dev->i2c_max_rx_size) + return -EOPNOTSUPP; + + ret = regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, mask, val); + if (ret) + return ret; + + dev->i2c_max_rx_size_en = enable; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_i2c_rx_max_size_enable, "INTEL_THC"); + +/** + * thc_i2c_set_rx_int_delay - Set I2C Rx input interrupt delay value + * @dev: The pointer of THC private device context + * @delay_us: Interrupt delay value, unit is us + * + * Set @delay_us for I2C RxDMA input interrupt delay feature. + * + * Return: 0 on success, other error codes on failure. + */ +int thc_i2c_set_rx_int_delay(struct thc_device *dev, u32 delay_us) +{ + u32 val; + int ret; + + if (!dev) + return -EINVAL; + + if (!delay_us) + return -EOPNOTSUPP; + + ret = regmap_read(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, &val); + if (ret) + return ret; + + /* THC hardware counts at 10us unit */ + val = val & ~THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL; + val |= FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL, DIV_ROUND_UP(delay_us, 10)); + + ret = regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, val); + if (ret) + return ret; + + dev->i2c_int_delay_us = delay_us; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_i2c_set_rx_int_delay, "INTEL_THC"); + +/** + * thc_i2c_rx_int_delay_enable - Enable I2C Rx interrupt delay + * @dev: The pointer of THC private device context + * @enable: Enable interrupt delay or not + * + * Enable or disable I2C RxDMA input interrupt delay feature. + * Input interrupt delay can only be enabled after interrupt delay value + * was set by thc_i2c_set_rx_int_delay(). + * + * Return: 0 on success, other error codes on failure. + */ +int thc_i2c_rx_int_delay_enable(struct thc_device *dev, bool enable) +{ + u32 mask = THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL_EN; + u32 val = enable ? mask : 0; + int ret; + + if (!dev) + return -EINVAL; + + if (!dev->i2c_int_delay_us) + return -EOPNOTSUPP; + + ret = regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, mask, val); + if (ret) + return ret; + + dev->i2c_int_delay_en = enable; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_i2c_rx_int_delay_enable, "INTEL_THC"); + +MODULE_AUTHOR("Xinpeng Sun "); +MODULE_AUTHOR("Even Xu "); + +MODULE_DESCRIPTION("Intel(R) Intel THC Hardware Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h new file mode 100644 index 000000000..be8a9605d --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _INTEL_THC_DEV_H_ +#define _INTEL_THC_DEV_H_ + +#include +#include +#include + +#include "intel-thc-dma.h" +#include "intel-thc-wot.h" + +#define THC_REGMAP_COMMON_OFFSET 0x10 +#define THC_REGMAP_MMIO_OFFSET 0x1000 + +/* + * THC Port type + * @THC_PORT_TYPE_SPI: This port is used for HIDSPI + * @THC_PORT_TYPE_I2C: This port is used for HIDI2C + */ +enum thc_port_type { + THC_PORT_TYPE_SPI = 0, + THC_PORT_TYPE_I2C = 1, +}; + +/** + * THC interrupt flag + * @THC_NONDMA_INT: THC non-DMA interrupt + * @THC_RXDMA1_INT: THC RxDMA1 interrupt + * @THC_RXDMA2_INT: THC RxDMA2 interrupt + * @THC_SWDMA_INT: THC SWDMA interrupt + * @THC_TXDMA_INT: THC TXDMA interrupt + * @THC_PIO_DONE_INT: THC PIO complete interrupt + * @THC_I2CSUBIP_INT: THC I2C subsystem interrupt + * @THC_TXN_ERR_INT: THC transfer error interrupt + * @THC_FATAL_ERR_INT: THC fatal error interrupt + */ +enum thc_int_type { + THC_NONDMA_INT = 0, + THC_RXDMA1_INT = 1, + THC_RXDMA2_INT = 2, + THC_SWDMA_INT = 3, + THC_TXDMA_INT = 4, + THC_PIO_DONE_INT = 5, + THC_I2CSUBIP_INT = 6, + THC_TXN_ERR_INT = 7, + THC_FATAL_ERR_INT = 8, + THC_UNKNOWN_INT +}; + +/** + * struct thc_i2c_config - THC I2C bus configuration + * @target_addr: Slave address of touch device (TIC) + * @addr_mode: Slave address mode of touch device (TIC), 7bit or 10bit + * @speed: I2C bus frequency speed mode + * @scl_hcnt: I2C clock SCL high count + * @scl_lcnt: I2C clock SCL low count + * @sda_tx_hold: I2C Data SDA transmit hold period + * @sda_rx_hold: I2C Data SDA receive hold period + */ +struct thc_i2c_config { + u16 target_addr; + u8 addr_mode; + u32 speed; + u32 scl_hcnt; + u32 scl_lcnt; + u32 sda_tx_hold; + u32 sda_rx_hold; +}; + +/** + * struct thc_device - THC private device struct + * @thc_regmap: MMIO regmap structure for accessing THC registers + * @mmio_addr: MMIO registers address + * @thc_bus_lock: Mutex locker for THC config + * @port_type: Port type of THC port instance + * @pio_int_supported: PIO interrupt supported flag + * @dma_ctx: DMA specific data + * @wot: THC Wake-on-Touch data + * @write_complete_wait: Signal event for DMA write complete + * @swdma_complete_wait: Signal event for SWDMA sequence complete + * @write_done: Bool value that indicates if DMA write is done + * @swdma_done: Bool value that indicates if SWDMA sequence is done + * @perf_limit: The delay between read operation and write operation + * @i2c_subip_regs: The copy of THC I2C sub-system registers for resuming restore + * @i2c_max_rx_size: I2C Rx transfer max input size + * @i2c_int_delay_us: I2C input interrupt delay, unit is us + * @i2c_max_rx_size_en: Bool value that indicates I2C max input size control enabled or not + * @i2c_int_delay_en: Bool value that indicates I2C input interrupt delay enabled or not + */ +struct thc_device { + struct device *dev; + struct regmap *thc_regmap; + void __iomem *mmio_addr; + struct mutex thc_bus_lock; + enum thc_port_type port_type; + bool pio_int_supported; + + struct thc_dma_context *dma_ctx; + + struct thc_wot wot; + + wait_queue_head_t write_complete_wait; + wait_queue_head_t swdma_complete_wait; + bool write_done; + bool swdma_done; + + u32 perf_limit; + + u32 *i2c_subip_regs; + + u32 i2c_max_rx_size; + u32 i2c_int_delay_us; + bool i2c_max_rx_size_en; + bool i2c_int_delay_en; +}; + +struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr); +int thc_tic_pio_read(struct thc_device *dev, const u32 address, + const u32 size, u32 *actual_size, u32 *buffer); +int thc_tic_pio_write(struct thc_device *dev, const u32 address, + const u32 size, const u32 *buffer); +int thc_tic_pio_write_and_read(struct thc_device *dev, const u32 address, + const u32 write_size, const u32 *write_buffer, + const u32 read_size, u32 *actual_size, u32 *read_buffer); +void thc_interrupt_config(struct thc_device *dev); +void thc_int_trigger_type_select(struct thc_device *dev, bool edge_trigger); +void thc_interrupt_enable(struct thc_device *dev, bool int_enable); +void thc_set_pio_interrupt_support(struct thc_device *dev, bool supported); +int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce); +void thc_ltr_config(struct thc_device *dev, u32 active_ltr_us, u32 lp_ltr_us); +void thc_change_ltr_mode(struct thc_device *dev, u32 ltr_mode); +void thc_ltr_unconfig(struct thc_device *dev); +u32 thc_int_cause_read(struct thc_device *dev); +int thc_interrupt_handler(struct thc_device *dev); +int thc_port_select(struct thc_device *dev, enum thc_port_type port_type); +int thc_spi_read_config(struct thc_device *dev, u32 spi_freq_val, + u32 io_mode, u32 opcode, u32 spi_rd_mps); +int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val, + u32 io_mode, u32 opcode, u32 spi_wr_mps, u32 perf_limit); +void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_addr, + u32 input_bdy_addr, u32 output_addr); +int thc_i2c_subip_init(struct thc_device *dev, const struct thc_i2c_config *i2c_config); +int thc_i2c_subip_regs_save(struct thc_device *dev); +int thc_i2c_subip_regs_restore(struct thc_device *dev); +int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size); +int thc_i2c_rx_max_size_enable(struct thc_device *dev, bool enable); +int thc_i2c_set_rx_int_delay(struct thc_device *dev, u32 delay_us); +int thc_i2c_rx_int_delay_enable(struct thc_device *dev, bool enable); + +#endif /* _INTEL_THC_DEV_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c new file mode 100644 index 000000000..7ceb8aeec --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c @@ -0,0 +1,1067 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include +#include +#include +#include +#include + +#include "intel-thc-dev.h" +#include "intel-thc-dma.h" +#include "intel-thc-hw.h" + +static void dma_set_prd_base_addr(struct thc_device *dev, u64 physical_addr, + struct thc_dma_configuration *dma_config) +{ + u32 addr_high, addr_low; + + if (!dma_config->is_enabled) + return; + + addr_high = upper_32_bits(physical_addr); + addr_low = lower_32_bits(physical_addr); + + regmap_write(dev->thc_regmap, dma_config->prd_base_addr_high, addr_high); + regmap_write(dev->thc_regmap, dma_config->prd_base_addr_low, addr_low); +} + +static void dma_set_start_bit(struct thc_device *dev, + struct thc_dma_configuration *dma_config) +{ + u32 ctrl, mask, mbits, data, offset; + + if (!dma_config->is_enabled) + return; + + switch (dma_config->dma_channel) { + case THC_RXDMA1: + case THC_RXDMA2: + if (dma_config->dma_channel == THC_RXDMA2) { + mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL, + THC_BITMASK_INTERRUPT_TYPE_DATA); + mask = THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL; + regmap_write_bits(dev->thc_regmap, + THC_M_PRT_DEVINT_CFG_1_OFFSET, mask, mbits); + } + + mbits = THC_M_PRT_READ_DMA_CNTRL_IE_EOF | + THC_M_PRT_READ_DMA_CNTRL_SOO | + THC_M_PRT_READ_DMA_CNTRL_IE_STALL | + THC_M_PRT_READ_DMA_CNTRL_IE_ERROR | + THC_M_PRT_READ_DMA_CNTRL_START; + + mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP | mbits; + mask |= THC_M_PRT_READ_DMA_CNTRL_INT_SW_DMA_EN; + ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, THC_POINTER_WRAPAROUND) | mbits; + offset = dma_config->dma_channel == THC_RXDMA1 ? + THC_M_PRT_READ_DMA_CNTRL_1_OFFSET : THC_M_PRT_READ_DMA_CNTRL_2_OFFSET; + regmap_write_bits(dev->thc_regmap, offset, mask, ctrl); + break; + + case THC_SWDMA: + mbits = THC_M_PRT_READ_DMA_CNTRL_IE_DMACPL | + THC_M_PRT_READ_DMA_CNTRL_IE_IOC | + THC_M_PRT_READ_DMA_CNTRL_SOO | + THC_M_PRT_READ_DMA_CNTRL_START; + + mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP | mbits; + ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, THC_POINTER_WRAPAROUND) | mbits; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET, + mask, ctrl); + break; + + case THC_TXDMA: + regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET, + THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS, + THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS); + + /* Select interrupt or polling method upon Write completion */ + if (dev->dma_ctx->use_write_interrupts) + data = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL; + else + data = 0; + + data |= THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START; + mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL | + THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START; + regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET, + mask, data); + break; + + default: + break; + } +} + +static void dma_set_prd_control(struct thc_device *dev, u8 entry_count, u8 cb_depth, + struct thc_dma_configuration *dma_config) +{ + u32 ctrl, mask; + + if (!dma_config->is_enabled) + return; + + if (dma_config->dma_channel == THC_TXDMA) { + mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC; + ctrl = FIELD_PREP(THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC, entry_count); + } else { + mask = THC_M_PRT_RPRD_CNTRL_PTEC | THC_M_PRT_RPRD_CNTRL_PCD; + ctrl = FIELD_PREP(THC_M_PRT_RPRD_CNTRL_PTEC, entry_count) | + FIELD_PREP(THC_M_PRT_RPRD_CNTRL_PCD, cb_depth); + } + + regmap_write_bits(dev->thc_regmap, dma_config->prd_cntrl, mask, ctrl); +} + +static void dma_clear_prd_control(struct thc_device *dev, + struct thc_dma_configuration *dma_config) +{ + u32 mask; + + if (!dma_config->is_enabled) + return; + + if (dma_config->dma_channel == THC_TXDMA) + mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC; + else + mask = THC_M_PRT_RPRD_CNTRL_PTEC | THC_M_PRT_RPRD_CNTRL_PCD; + + regmap_write_bits(dev->thc_regmap, dma_config->prd_cntrl, mask, 0); +} + +static u8 dma_get_read_pointer(struct thc_device *dev, + struct thc_dma_configuration *dma_config) +{ + u32 ctrl, read_pointer; + + regmap_read(dev->thc_regmap, dma_config->dma_cntrl, &ctrl); + read_pointer = FIELD_GET(THC_M_PRT_READ_DMA_CNTRL_TPCRP, ctrl); + + dev_dbg(dev->dev, "THC_M_PRT_READ_DMA_CNTRL 0x%x offset 0x%x TPCRP 0x%x\n", + ctrl, dma_config->dma_cntrl, read_pointer); + + return read_pointer; +} + +static u8 dma_get_write_pointer(struct thc_device *dev, + struct thc_dma_configuration *dma_config) +{ + u32 ctrl, write_pointer; + + regmap_read(dev->thc_regmap, dma_config->dma_cntrl, &ctrl); + write_pointer = FIELD_GET(THC_M_PRT_READ_DMA_CNTRL_TPCWP, ctrl); + + dev_dbg(dev->dev, "THC_M_PRT_READ_DMA_CNTRL 0x%x offset 0x%x TPCWP 0x%x\n", + ctrl, dma_config->dma_cntrl, write_pointer); + + return write_pointer; +} + +static void dma_set_write_pointer(struct thc_device *dev, u8 value, + struct thc_dma_configuration *dma_config) +{ + u32 ctrl, mask; + + mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP; + ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, value); + regmap_write_bits(dev->thc_regmap, dma_config->dma_cntrl, mask, ctrl); +} + +static size_t dma_get_max_packet_size(struct thc_device *dev, + struct thc_dma_configuration *dma_config) +{ + return dma_config->max_packet_size; +} + +static void dma_set_max_packet_size(struct thc_device *dev, size_t size, + struct thc_dma_configuration *dma_config) +{ + if (size) { + dma_config->max_packet_size = ALIGN(size, SZ_4K); + dma_config->is_enabled = true; + } +} + +static void thc_copy_one_sgl_to_prd(struct thc_device *dev, + struct thc_dma_configuration *config, + unsigned int ind) +{ + struct thc_prd_table *prd_tbl; + struct scatterlist *sg; + int j; + + prd_tbl = &config->prd_tbls[ind]; + + for_each_sg(config->sgls[ind], sg, config->sgls_nent[ind], j) { + prd_tbl->entries[j].dest_addr = + sg_dma_address(sg) >> THC_ADDRESS_SHIFT; + prd_tbl->entries[j].len = sg_dma_len(sg); + prd_tbl->entries[j].hw_status = 0; + prd_tbl->entries[j].end_of_prd = 0; + } + + /* Set the end_of_prd flag in the last filled entry */ + if (j > 0) + prd_tbl->entries[j - 1].end_of_prd = 1; +} + +static void thc_copy_sgls_to_prd(struct thc_device *dev, + struct thc_dma_configuration *config) +{ + unsigned int i; + + memset(config->prd_tbls, 0, array_size(PRD_TABLE_SIZE, config->prd_tbl_num)); + + for (i = 0; i < config->prd_tbl_num; i++) + thc_copy_one_sgl_to_prd(dev, config, i); +} + +static int setup_dma_buffers(struct thc_device *dev, + struct thc_dma_configuration *config, + enum dma_data_direction dir) +{ + size_t prd_tbls_size = array_size(PRD_TABLE_SIZE, config->prd_tbl_num); + unsigned int i, nent = PRD_ENTRIES_NUM; + dma_addr_t dma_handle; + void *cpu_addr; + size_t buf_sz; + int count; + + if (!config->is_enabled) + return 0; + + memset(config->sgls, 0, sizeof(config->sgls)); + memset(config->sgls_nent_pages, 0, sizeof(config->sgls_nent_pages)); + memset(config->sgls_nent, 0, sizeof(config->sgls_nent)); + + cpu_addr = dma_alloc_coherent(dev->dev, prd_tbls_size, + &dma_handle, GFP_KERNEL); + if (!cpu_addr) + return -ENOMEM; + + config->prd_tbls = cpu_addr; + config->prd_tbls_dma_handle = dma_handle; + + buf_sz = dma_get_max_packet_size(dev, config); + + /* Allocate and map the scatter-gather lists, one for each PRD table */ + for (i = 0; i < config->prd_tbl_num; i++) { + config->sgls[i] = sgl_alloc(buf_sz, GFP_KERNEL, &nent); + if (!config->sgls[i] || nent > PRD_ENTRIES_NUM) { + dev_err_once(dev->dev, "sgl_alloc (%uth) failed, nent %u\n", + i, nent); + return -ENOMEM; + } + count = dma_map_sg(dev->dev, config->sgls[i], nent, dir); + + config->sgls_nent_pages[i] = nent; + config->sgls_nent[i] = count; + } + + thc_copy_sgls_to_prd(dev, config); + + return 0; +} + +static void thc_reset_dma_settings(struct thc_device *dev) +{ + /* Stop all DMA channels and reset DMA read pointers */ + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_START, 0); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_START, 0); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_START, 0); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET, + THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_TPCPR, + THC_M_PRT_READ_DMA_CNTRL_TPCPR); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_TPCPR, + THC_M_PRT_READ_DMA_CNTRL_TPCPR); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_TPCPR, + THC_M_PRT_READ_DMA_CNTRL_TPCPR); +} + +static void release_dma_buffers(struct thc_device *dev, + struct thc_dma_configuration *config) +{ + size_t prd_tbls_size = array_size(PRD_TABLE_SIZE, config->prd_tbl_num); + unsigned int i; + + if (!config->is_enabled) + return; + + for (i = 0; i < config->prd_tbl_num; i++) { + if (!config->sgls[i] || !config->sgls_nent[i]) + continue; + + dma_unmap_sg(dev->dev, config->sgls[i], + config->sgls_nent_pages[i], + config->dir); + + sgl_free(config->sgls[i]); + config->sgls[i] = NULL; + } + + memset(config->prd_tbls, 0, prd_tbls_size); + + if (config->prd_tbls) { + dma_free_coherent(dev->dev, prd_tbls_size, config->prd_tbls, + config->prd_tbls_dma_handle); + config->prd_tbls = NULL; + config->prd_tbls_dma_handle = 0; + } +} + +struct thc_dma_context *thc_dma_init(struct thc_device *dev) +{ + struct thc_dma_context *dma_ctx; + + dma_ctx = devm_kzalloc(dev->dev, sizeof(*dma_ctx), GFP_KERNEL); + if (!dma_ctx) + return NULL; + + dev->dma_ctx = dma_ctx; + + dma_ctx->dma_config[THC_RXDMA1].dma_channel = THC_RXDMA1; + dma_ctx->dma_config[THC_RXDMA2].dma_channel = THC_RXDMA2; + dma_ctx->dma_config[THC_TXDMA].dma_channel = THC_TXDMA; + dma_ctx->dma_config[THC_SWDMA].dma_channel = THC_SWDMA; + + dma_ctx->dma_config[THC_RXDMA1].dir = DMA_FROM_DEVICE; + dma_ctx->dma_config[THC_RXDMA2].dir = DMA_FROM_DEVICE; + dma_ctx->dma_config[THC_TXDMA].dir = DMA_TO_DEVICE; + dma_ctx->dma_config[THC_SWDMA].dir = DMA_FROM_DEVICE; + + dma_ctx->dma_config[THC_RXDMA1].prd_tbl_num = PRD_TABLES_NUM; + dma_ctx->dma_config[THC_RXDMA2].prd_tbl_num = PRD_TABLES_NUM; + dma_ctx->dma_config[THC_TXDMA].prd_tbl_num = 1; + dma_ctx->dma_config[THC_SWDMA].prd_tbl_num = 1; + + dma_ctx->dma_config[THC_RXDMA1].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_1_OFFSET; + dma_ctx->dma_config[THC_RXDMA2].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_2_OFFSET; + dma_ctx->dma_config[THC_TXDMA].prd_base_addr_high = THC_M_PRT_WPRD_BA_HI_OFFSET; + dma_ctx->dma_config[THC_SWDMA].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_SW_OFFSET; + + dma_ctx->dma_config[THC_RXDMA1].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_1_OFFSET; + dma_ctx->dma_config[THC_RXDMA2].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_2_OFFSET; + dma_ctx->dma_config[THC_TXDMA].prd_base_addr_low = THC_M_PRT_WPRD_BA_LOW_OFFSET; + dma_ctx->dma_config[THC_SWDMA].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_SW_OFFSET; + + dma_ctx->dma_config[THC_RXDMA1].prd_cntrl = THC_M_PRT_RPRD_CNTRL_1_OFFSET; + dma_ctx->dma_config[THC_RXDMA2].prd_cntrl = THC_M_PRT_RPRD_CNTRL_2_OFFSET; + dma_ctx->dma_config[THC_TXDMA].prd_cntrl = THC_M_PRT_WRITE_DMA_CNTRL_OFFSET; + dma_ctx->dma_config[THC_SWDMA].prd_cntrl = THC_M_PRT_RPRD_CNTRL_SW_OFFSET; + + dma_ctx->dma_config[THC_RXDMA1].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_1_OFFSET; + dma_ctx->dma_config[THC_RXDMA2].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_2_OFFSET; + dma_ctx->dma_config[THC_TXDMA].dma_cntrl = THC_M_PRT_WRITE_DMA_CNTRL_OFFSET; + dma_ctx->dma_config[THC_SWDMA].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET; + + /* Enable write DMA completion interrupt by default */ + dma_ctx->use_write_interrupts = 1; + + return dma_ctx; +} + +/** + * thc_dma_set_max_packet_sizes - Set max packet sizes for all DMA engines + * + * @dev: The pointer of THC private device context + * @mps_read1: RxDMA1 max packet size + * @mps_read2: RxDMA2 max packet size + * @mps_write: TxDMA max packet size + * @mps_swdma: Software DMA max packet size + * + * If mps is not 0, it means the corresponding DMA channel is used, then set + * the flag to turn on this channel. + * + * Return: 0 on success, other error codes on failed. + */ +int thc_dma_set_max_packet_sizes(struct thc_device *dev, size_t mps_read1, + size_t mps_read2, size_t mps_write, + size_t mps_swdma) +{ + if (!dev->dma_ctx) { + dev_err_once(dev->dev, + "Cannot set max packet sizes because DMA context is NULL!\n"); + return -EINVAL; + } + + dma_set_max_packet_size(dev, mps_read1, &dev->dma_ctx->dma_config[THC_RXDMA1]); + dma_set_max_packet_size(dev, mps_read2, &dev->dma_ctx->dma_config[THC_RXDMA2]); + dma_set_max_packet_size(dev, mps_write, &dev->dma_ctx->dma_config[THC_TXDMA]); + dma_set_max_packet_size(dev, mps_swdma, &dev->dma_ctx->dma_config[THC_SWDMA]); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_dma_set_max_packet_sizes, "INTEL_THC"); + +/** + * thc_dma_allocate - Allocate DMA buffers for all DMA engines + * + * @dev: The pointer of THC private device context + * + * Return: 0 on success, other error codes on failed. + */ +int thc_dma_allocate(struct thc_device *dev) +{ + int ret, chan; + + for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) { + ret = setup_dma_buffers(dev, &dev->dma_ctx->dma_config[chan], + dev->dma_ctx->dma_config[chan].dir); + if (ret < 0) { + dev_err_once(dev->dev, "DMA setup failed for DMA channel %d\n", chan); + goto release_bufs; + } + } + + return 0; + +release_bufs: + while (chan--) + release_dma_buffers(dev, &dev->dma_ctx->dma_config[chan]); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_dma_allocate, "INTEL_THC"); + +/** + * thc_dma_release - Release DMA buffers for all DMA engines + * + * @dev: The pointer of THC private device context + */ +void thc_dma_release(struct thc_device *dev) +{ + int chan; + + for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) + release_dma_buffers(dev, &dev->dma_ctx->dma_config[chan]); +} +EXPORT_SYMBOL_NS_GPL(thc_dma_release, "INTEL_THC"); + +static int calc_prd_entries_num(struct thc_prd_table *prd_tbl, + size_t mes_len, u8 *nent) +{ + *nent = DIV_ROUND_UP(mes_len, THC_MIN_BYTES_PER_SG_LIST_ENTRY); + if (*nent > PRD_ENTRIES_NUM) + return -EMSGSIZE; + + return 0; +} + +static size_t calc_message_len(struct thc_prd_table *prd_tbl, u8 *nent) +{ + size_t mes_len = 0; + unsigned int j; + + for (j = 0; j < PRD_ENTRIES_NUM; j++) { + mes_len += prd_tbl->entries[j].len; + if (prd_tbl->entries[j].end_of_prd) + break; + } + + *nent = j + 1; + + return mes_len; +} + +/** + * thc_dma_configure - Configure DMA settings for all DMA engines + * + * @dev: The pointer of THC private device context + * + * Return: 0 on success, other error codes on failed. + */ +int thc_dma_configure(struct thc_device *dev) +{ + struct thc_dma_context *dma_ctx = dev->dma_ctx; + int chan; + + thc_reset_dma_settings(dev); + + if (!dma_ctx) { + dev_err_once(dev->dev, "Cannot do DMA configure because DMA context is NULL\n"); + return -EINVAL; + } + + for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) { + dma_set_prd_base_addr(dev, + dma_ctx->dma_config[chan].prd_tbls_dma_handle, + &dma_ctx->dma_config[chan]); + + dma_set_prd_control(dev, PRD_ENTRIES_NUM - 1, + dma_ctx->dma_config[chan].prd_tbl_num - 1, + &dma_ctx->dma_config[chan]); + } + + /* Start read2 DMA engine */ + dma_set_start_bit(dev, &dma_ctx->dma_config[THC_RXDMA2]); + + dev_dbg(dev->dev, "DMA configured successfully!\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_dma_configure, "INTEL_THC"); + +/** + * thc_dma_unconfigure - Unconfigure DMA settings for all DMA engines + * + * @dev: The pointer of THC private device context + */ +void thc_dma_unconfigure(struct thc_device *dev) +{ + int chan; + + for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) { + dma_set_prd_base_addr(dev, 0, &dev->dma_ctx->dma_config[chan]); + dma_clear_prd_control(dev, &dev->dma_ctx->dma_config[chan]); + } + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_START, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET, + THC_M_PRT_READ_DMA_CNTRL_START, 0); +} +EXPORT_SYMBOL_NS_GPL(thc_dma_unconfigure, "INTEL_THC"); + +static int thc_wait_for_dma_pause(struct thc_device *dev, enum thc_dma_channel channel) +{ + u32 ctrl_reg, sts_reg, sts; + int ret; + + ctrl_reg = (channel == THC_RXDMA1) ? THC_M_PRT_READ_DMA_CNTRL_1_OFFSET : + ((channel == THC_RXDMA2) ? THC_M_PRT_READ_DMA_CNTRL_2_OFFSET : + THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET); + + regmap_write_bits(dev->thc_regmap, ctrl_reg, THC_M_PRT_READ_DMA_CNTRL_START, 0); + + sts_reg = (channel == THC_RXDMA1) ? THC_M_PRT_READ_DMA_INT_STS_1_OFFSET : + ((channel == THC_RXDMA2) ? THC_M_PRT_READ_DMA_INT_STS_2_OFFSET : + THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET); + + ret = regmap_read_poll_timeout(dev->thc_regmap, sts_reg, sts, + !(sts & THC_M_PRT_READ_DMA_INT_STS_ACTIVE), + THC_DEFAULT_RXDMA_POLLING_US_INTERVAL, + THC_DEFAULT_RXDMA_POLLING_US_TIMEOUT); + + if (ret) { + dev_err_once(dev->dev, + "Timeout while waiting for DMA %d stop\n", channel); + return ret; + } + + return 0; +} + +/** + * thc_rxdma_reset - Reset all read DMA engines + * + * @dev: The pointer of THC private device context + * + * This is a helper function to reset RxDMA configure. It's typically used + * for RxDMA recovery when fatal error happens. + * + * Return: 0 if successful or error code on failure. + */ +int thc_rxdma_reset(struct thc_device *dev) +{ + int ret; + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + ret = thc_interrupt_quiesce(dev, true); + if (ret) { + dev_err(dev->dev, "Quiesce interrupt failed during RxDMA reset\n"); + goto end; + } + + ret = thc_wait_for_dma_pause(dev, THC_RXDMA1); + if (ret) { + dev_err(dev->dev, "Wait for RxDMA1 pause failed during RxDMA reset\n"); + goto end; + } + + ret = thc_wait_for_dma_pause(dev, THC_RXDMA2); + if (ret) { + dev_err(dev->dev, "Wait for RxDMA2 pause failed during RxDMA reset\n"); + goto end; + } + + thc_dma_unconfigure(dev); + + ret = thc_dma_configure(dev); + if (ret) { + dev_err(dev->dev, "Re-config DMA failed during RxDMA reset\n"); + goto end; + } + + thc_interrupt_quiesce(dev, false); + +end: + mutex_unlock(&dev->thc_bus_lock); + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_rxdma_reset, "INTEL_THC"); + +static int read_dma_buffer(struct thc_device *dev, + struct thc_dma_configuration *read_config, + u8 prd_table_index, void *read_buff) +{ + struct thc_prd_table *prd_tbl; + struct scatterlist *sg; + size_t mes_len, ret; + u8 nent; + + if (prd_table_index >= read_config->prd_tbl_num) { + dev_err_once(dev->dev, "PRD table index %d too big\n", prd_table_index); + return -EINVAL; + } + + if (!read_config->prd_tbls || !read_config->sgls[prd_table_index]) { + dev_err_once(dev->dev, "PRD tables are not ready yet\n"); + return -EINVAL; + } + + prd_tbl = &read_config->prd_tbls[prd_table_index]; + mes_len = calc_message_len(prd_tbl, &nent); + if (mes_len > read_config->max_packet_size) { + dev_err(dev->dev, + "Message length %zu is bigger than buffer length %lu\n", + mes_len, read_config->max_packet_size); + return -EMSGSIZE; + } + + sg = read_config->sgls[prd_table_index]; + ret = sg_copy_to_buffer(sg, nent, read_buff, mes_len); + if (ret != mes_len) { + dev_err_once(dev->dev, "Copied %zu bytes instead of requested %zu\n", + ret, mes_len); + return -EIO; + } + + return mes_len; +} + +static void update_write_pointer(struct thc_device *dev, + struct thc_dma_configuration *read_config) +{ + u8 write_ptr = dma_get_write_pointer(dev, read_config); + + if (write_ptr + 1 == THC_WRAPAROUND_VALUE_ODD) + dma_set_write_pointer(dev, THC_POINTER_WRAPAROUND, read_config); + else if (write_ptr + 1 == THC_WRAPAROUND_VALUE_EVEN) + dma_set_write_pointer(dev, 0, read_config); + else + dma_set_write_pointer(dev, write_ptr + 1, read_config); +} + +static int is_dma_buf_empty(struct thc_device *dev, + struct thc_dma_configuration *read_config, + u8 *read_ptr, u8 *write_ptr) +{ + *read_ptr = dma_get_read_pointer(dev, read_config); + *write_ptr = dma_get_write_pointer(dev, read_config); + + if ((*read_ptr & THC_POINTER_MASK) == (*write_ptr & THC_POINTER_MASK)) + if (*read_ptr != *write_ptr) + return true; + + return false; +} + +static int thc_dma_read(struct thc_device *dev, + struct thc_dma_configuration *read_config, + void *read_buff, size_t *read_len, int *read_finished) +{ + u8 read_ptr, write_ptr, prd_table_index; + int status; + + if (!is_dma_buf_empty(dev, read_config, &read_ptr, &write_ptr)) { + prd_table_index = write_ptr & THC_POINTER_MASK; + + status = read_dma_buffer(dev, read_config, prd_table_index, read_buff); + if (status <= 0) { + dev_err_once(dev->dev, "read DMA buffer failed %d\n", status); + return -EIO; + } + + *read_len = status; + + /* Clear the relevant PRD table */ + thc_copy_one_sgl_to_prd(dev, read_config, prd_table_index); + + /* Increment the write pointer to let the HW know we have processed this PRD */ + update_write_pointer(dev, read_config); + } + + /* + * This function only reads one frame from PRD table for each call, so we need to + * check if all DMAed data is read out and return the flag to the caller. Caller + * should repeatedly call thc_dma_read() until all DMAed data is handled. + */ + if (read_finished) + *read_finished = is_dma_buf_empty(dev, read_config, &read_ptr, &write_ptr) ? 1 : 0; + + return 0; +} + +/** + * thc_rxdma_read - Read data from RXDMA buffer + * + * @dev: The pointer of THC private device context + * @dma_channel: The RXDMA engine of read data source + * @read_buff: The pointer of the read data buffer + * @read_len: The pointer of the read data length + * @read_finished: The pointer of the flag indicating if all pending data has been read out + * + * Return: 0 on success, other error codes on failed. + */ +int thc_rxdma_read(struct thc_device *dev, enum thc_dma_channel dma_channel, + void *read_buff, size_t *read_len, int *read_finished) +{ + struct thc_dma_configuration *dma_config; + int ret; + + dma_config = &dev->dma_ctx->dma_config[dma_channel]; + + if (!dma_config->is_enabled) { + dev_err_once(dev->dev, "The DMA channel %d is not enabled", dma_channel); + return -EINVAL; + } + + if (!read_buff || !read_len) { + dev_err(dev->dev, "Invalid input parameters, read_buff %p, read_len %p\n", + read_buff, read_len); + return -EINVAL; + } + + if (dma_channel >= THC_TXDMA) { + dev_err(dev->dev, "Unsupported DMA channel for RxDMA read, %d\n", dma_channel); + return -EINVAL; + } + + ret = thc_dma_read(dev, dma_config, read_buff, read_len, read_finished); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_rxdma_read, "INTEL_THC"); + +static int thc_swdma_read_start(struct thc_device *dev, void *write_buff, + size_t write_len, u32 *prd_tbl_len) +{ + u32 mask, val, data0 = 0, data1 = 0; + int ret; + + ret = thc_interrupt_quiesce(dev, true); + if (ret) + return ret; + + if (thc_wait_for_dma_pause(dev, THC_RXDMA1) || thc_wait_for_dma_pause(dev, THC_RXDMA2)) + return -EIO; + + thc_reset_dma_settings(dev); + + /* + * Max input size control feature is only available for RxDMA, it must keep disabled + * during SWDMA operation, and restore to previous state after SWDMA is done. + * Max input size variables in THC device context track hardware state, and keep change + * when feature state was changed, so those variables cannot be used to record feature + * state after state was changed during SWDMA operation. Here have to use a temp variable + * in DMA context to record feature state before SWDMA operation. + */ + if (dev->i2c_max_rx_size_en) { + thc_i2c_rx_max_size_enable(dev, false); + dev->dma_ctx->rx_max_size_en = true; + } + + /* + * Interrupt delay feature is in the same situation with max input size control feature, + * needs record feature state before SWDMA. + */ + if (dev->i2c_int_delay_en) { + thc_i2c_rx_int_delay_enable(dev, false); + dev->dma_ctx->rx_int_delay_en = true; + } + + mask = THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC | + THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN; + val = FIELD_PREP(THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC, write_len) | + ((!prd_tbl_len) ? THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN : 0); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_RPRD_CNTRL_SW_OFFSET, + mask, val); + + if (prd_tbl_len) { + mask = THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN; + val = FIELD_PREP(THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN, + *prd_tbl_len); + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_DMA_PRD_TABLE_LEN_OFFSET, + mask, val); + } + + if (write_len <= sizeof(u32)) { + for (int i = 0; i < write_len; i++) + data0 |= *(((u8 *)write_buff) + i) << (i * 8); + + regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, data0); + } else if (write_len <= 2 * sizeof(u32)) { + data0 = *(u32 *)write_buff; + regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, data0); + + for (int i = 0; i < write_len - sizeof(u32); i++) + data1 |= *(((u8 *)write_buff) + sizeof(u32) + i) << (i * 8); + + regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET, data1); + } + dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_SWDMA]); + + return 0; +} + +static int thc_swdma_read_completion(struct thc_device *dev) +{ + int ret; + + ret = thc_wait_for_dma_pause(dev, THC_SWDMA); + if (ret) + return ret; + + /* + * Restore max input size control feature to previous state after SWDMA if it was + * enabled before SWDMA, and reset temp rx_max_size_en variable for next time. + */ + if (dev->dma_ctx->rx_max_size_en) { + thc_i2c_rx_max_size_enable(dev, true); + dev->dma_ctx->rx_max_size_en = false; + } + + /* + * Restore input interrupt delay feature to previous state after SWDMA if it was + * enabled before SWDMA, and reset temp rx_int_delay_en variable for next time. + */ + if (dev->dma_ctx->rx_int_delay_en) { + thc_i2c_rx_int_delay_enable(dev, true); + dev->dma_ctx->rx_int_delay_en = false; + } + + thc_reset_dma_settings(dev); + + dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_RXDMA2]); + + ret = thc_interrupt_quiesce(dev, false); + + return ret; +} + +/** + * thc_swdma_read - Use software DMA to read data from touch device + * + * @dev: The pointer of THC private device context + * @write_buff: The pointer of write buffer for SWDMA sequence + * @write_len: The write data length for SWDMA sequence + * @prd_tbl_len: The prd table length of SWDMA engine, can be set to NULL + * @read_buff: The pointer of the read data buffer + * @read_len: The pointer of the read data length + * + * Return: 0 on success, other error codes on failed. + */ +int thc_swdma_read(struct thc_device *dev, void *write_buff, size_t write_len, + u32 *prd_tbl_len, void *read_buff, size_t *read_len) +{ + int ret; + + if (!(&dev->dma_ctx->dma_config[THC_SWDMA])->is_enabled) { + dev_err_once(dev->dev, "The SWDMA channel is not enabled"); + return -EINVAL; + } + + if (!read_buff || !read_len) { + dev_err(dev->dev, "Invalid input parameters, read_buff %p, read_len %p\n", + read_buff, read_len); + return -EINVAL; + } + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + dev->swdma_done = false; + + ret = thc_swdma_read_start(dev, write_buff, write_len, prd_tbl_len); + if (ret) + goto end; + + ret = wait_event_interruptible_timeout(dev->swdma_complete_wait, dev->swdma_done, 1 * HZ); + if (ret <= 0 || !dev->swdma_done) { + dev_err_once(dev->dev, "timeout for waiting SWDMA completion\n"); + ret = -ETIMEDOUT; + goto end; + } + + ret = thc_dma_read(dev, &dev->dma_ctx->dma_config[THC_SWDMA], read_buff, read_len, NULL); + if (ret) + goto end; + + ret = thc_swdma_read_completion(dev); + +end: + mutex_unlock(&dev->thc_bus_lock); + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_swdma_read, "INTEL_THC"); + +static int write_dma_buffer(struct thc_device *dev, + void *buffer, size_t buf_len) +{ + struct thc_dma_configuration *write_config = &dev->dma_ctx->dma_config[THC_TXDMA]; + struct thc_prd_table *prd_tbl; + struct scatterlist *sg; + unsigned long len_left; + size_t ret; + u8 nent; + int i; + + /* There is only one PRD table for write */ + prd_tbl = &write_config->prd_tbls[0]; + + if (calc_prd_entries_num(prd_tbl, buf_len, &nent) < 0) { + dev_err(dev->dev, "Tx message length too big (%zu)\n", buf_len); + return -EOVERFLOW; + } + + sg = write_config->sgls[0]; + ret = sg_copy_from_buffer(sg, nent, buffer, buf_len); + if (ret != buf_len) { + dev_err_once(dev->dev, "Copied %zu bytes instead of requested %zu\n", + ret, buf_len); + return -EIO; + } + + prd_tbl = &write_config->prd_tbls[0]; + len_left = buf_len; + + for_each_sg(write_config->sgls[0], sg, write_config->sgls_nent[0], i) { + if (sg_dma_address(sg) == 0 || sg_dma_len(sg) == 0) { + dev_err_once(dev->dev, "SGList: zero address or length\n"); + return -EINVAL; + } + + prd_tbl->entries[i].dest_addr = + sg_dma_address(sg) >> THC_ADDRESS_SHIFT; + + if (len_left < sg_dma_len(sg)) { + prd_tbl->entries[i].len = len_left; + prd_tbl->entries[i].end_of_prd = 1; + break; + } + + prd_tbl->entries[i].len = sg_dma_len(sg); + prd_tbl->entries[i].end_of_prd = 0; + + len_left -= sg_dma_len(sg); + } + + dma_set_prd_control(dev, i, 0, write_config); + + return 0; +} + +static void thc_ensure_performance_limitations(struct thc_device *dev) +{ + unsigned long delay_usec = 0; + /* + * Minimum amount of delay the THC / QUICKSPI driver must wait + * between end of write operation and begin of read operation. + * This value shall be in 10us multiples. + */ + if (dev->perf_limit > 0) { + delay_usec = dev->perf_limit * 10; + udelay(delay_usec); + } +} + +static void thc_dma_write_completion(struct thc_device *dev) +{ + thc_ensure_performance_limitations(dev); +} + +/** + * thc_dma_write - Use TXDMA to write data to touch device + * + * @dev: The pointer of THC private device context + * @buffer: The pointer of write data buffer + * @buf_len: The write data length + * + * Return: 0 on success, other error codes on failed. + */ +int thc_dma_write(struct thc_device *dev, void *buffer, size_t buf_len) +{ + bool restore_interrupts = false; + u32 sts, ctrl; + int ret; + + if (!(&dev->dma_ctx->dma_config[THC_TXDMA])->is_enabled) { + dev_err_once(dev->dev, "The TxDMA channel is not enabled\n"); + return -EINVAL; + } + + if (!buffer || buf_len <= 0) { + dev_err(dev->dev, "Invalid input parameters, buffer %p\n, buf_len %zu\n", + buffer, buf_len); + return -EINVAL; + } + + regmap_read(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET, &sts); + if (sts & THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ACTIVE) { + dev_err_once(dev->dev, "THC TxDMA is till active and can't start again\n"); + return -EBUSY; + } + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + regmap_read(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, &ctrl); + + ret = write_dma_buffer(dev, buffer, buf_len); + if (ret) + goto end; + + if (dev->perf_limit && !(ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS)) { + ret = thc_interrupt_quiesce(dev, true); + if (ret) + goto end; + + restore_interrupts = true; + } + + dev->write_done = false; + + dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_TXDMA]); + + ret = wait_event_interruptible_timeout(dev->write_complete_wait, dev->write_done, 1 * HZ); + if (ret <= 0 || !dev->write_done) { + dev_err_once(dev->dev, "timeout for waiting TxDMA completion\n"); + ret = -ETIMEDOUT; + goto end; + } + + thc_dma_write_completion(dev); + mutex_unlock(&dev->thc_bus_lock); + return 0; + +end: + mutex_unlock(&dev->thc_bus_lock); + + if (restore_interrupts) + ret = thc_interrupt_quiesce(dev, false); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_dma_write, "INTEL_THC"); diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h new file mode 100644 index 000000000..715423453 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _INTEL_THC_DMA_H_ +#define _INTEL_THC_DMA_H_ + +#include +#include +#include +#include +#include + +#define THC_POINTER_MASK GENMASK(6, 0) +#define THC_POINTER_WRAPAROUND 0x80 +#define THC_WRAPAROUND_VALUE_ODD 0x10 +#define THC_WRAPAROUND_VALUE_EVEN 0x90 +#define THC_MIN_BYTES_PER_SG_LIST_ENTRY SZ_4K + +#define THC_DEFAULT_RXDMA_POLLING_US_INTERVAL 100 +#define THC_DEFAULT_RXDMA_POLLING_US_TIMEOUT (10 * USEC_PER_MSEC) + +/* + * THC needs 1KB aligned address, dest_addr is 54 bits, not 64, + * so don't need to send the lower 10-bits of address. + */ +#define THC_ADDRESS_SHIFT 10 + +/** + * THC DMA channels: + * @THC_RXDMA1: Legacy channel, reserved for raw data reading + * @THC_RXDMA2: DMA to read HID data from touch device + * @THC_TXDMA: DMA to write to touch device + * @THC_SWDMA: SW triggered DMA to write and read from touch device + */ +enum thc_dma_channel { + THC_RXDMA1 = 0, + THC_RXDMA2 = 1, + THC_TXDMA = 2, + THC_SWDMA = 3, + MAX_THC_DMA_CHANNEL +}; + +/** + * THC DMA Physical Memory Descriptor (PRD) + * @dest_addr: Bit[53:0], destination address in system memory + * @int_on_completion: Bit[63], if set, thc will trigger interrupt to driver + * @len: Bit[87:64], length of this entry + * @end_of_prd: Bit[88], if set, this entry is last one of current PRD table + * @hw_status: Bit[90:89], hardware status bits + */ +struct thc_prd_entry { + u64 dest_addr : 54; + u64 reserved1 : 9; + u64 int_on_completion : 1; + u64 len : 24; + u64 end_of_prd : 1; + u64 hw_status : 2; + u64 reserved2 : 37; +}; + +/* + * Max OS memory fragmentation will be at a 4KB boundary, thus to address 1MB + * of virtually contiguous memory 256 PRD entries are required for a single + * PRD Table. SW writes the number of PRD Entries for each PRD table in the + * THC_M_PRT_RPRD_CNTRL.PTEC register field. The PRD entry's length must be + * multiple of 4KB except for the last entry in a PRD table. + * This is the max possible number of etries supported by HW, in practise we + * there will be less entries in each prd table(the actual number will be + * given by scatter-gather list allocation). + */ +#define PRD_ENTRIES_NUM 16 + +/* + * Number of PRD tables equals to number of data buffers. + * The max number of PRD tables supported by the HW is 128, + * but we allocate only 16. + */ +#define PRD_TABLES_NUM 16 + +/* THC DMA Physical Memory Descriptor Table */ +struct thc_prd_table { + struct thc_prd_entry entries[PRD_ENTRIES_NUM]; +}; + +#define PRD_TABLE_SIZE sizeof(struct thc_prd_table) + +/** + * struct thc_dma_configuration - THC DMA configure + * @dma_channel: DMA channel for current DMA configuration + * @prd_tbls_dma_handle: DMA buffer handle + * @dir: Direction of DMA for this config + * @prd_tbls: PRD tables for current DMA + * @sgls: Array of pointers to scatter-gather lists + * @sgls_nent_pages: Number of pages per scatter-gather list + * @sgls_nent: Actual number of entries per scatter-gather list + * @prd_tbl_num: Actual number of PRD tables + * @max_packet_size: Size of the buffer needed for 1 DMA message (1 PRD table) + * @prd_base_addr_high: High 32bits memory address where stores PRD table + * @prd_base_addr_low: Low 32bits memory address where stores PRD table + * @prd_cntrl: PRD control register value + * @dma_cntrl: DMA control register value + */ +struct thc_dma_configuration { + enum thc_dma_channel dma_channel; + dma_addr_t prd_tbls_dma_handle; + enum dma_data_direction dir; + bool is_enabled; + + struct thc_prd_table *prd_tbls; + struct scatterlist *sgls[PRD_TABLES_NUM]; + u8 sgls_nent_pages[PRD_TABLES_NUM]; + u8 sgls_nent[PRD_TABLES_NUM]; + u8 prd_tbl_num; + + size_t max_packet_size; + u32 prd_base_addr_high; + u32 prd_base_addr_low; + u32 prd_cntrl; + u32 dma_cntrl; +}; + +/** + * struct thc_dma_context - THC DMA context + * @thc_dma_configuration: Array of all THC Channel configures + * @use_write_interrupts: Indicate TxDMA using interrupt or polling + * @rx_max_size_en: Temp flag to indicate THC I2C Rx max input size control feature + * enabled or not, only be used during SWDMA operation. + * @rx_int_delay_en: Temp flag to indicate THC I2C Rx interrupt delay feature + * enabled or not, only be used during SWDMA operation. + */ +struct thc_dma_context { + struct thc_dma_configuration dma_config[MAX_THC_DMA_CHANNEL]; + u8 use_write_interrupts; + + bool rx_max_size_en; + bool rx_int_delay_en; +}; + +struct thc_device; + +int thc_dma_set_max_packet_sizes(struct thc_device *dev, + size_t mps_read1, size_t mps_read2, + size_t mps_write, size_t mps_swdma); +int thc_dma_allocate(struct thc_device *dev); +int thc_dma_configure(struct thc_device *dev); +void thc_dma_unconfigure(struct thc_device *dev); +void thc_dma_release(struct thc_device *dev); +int thc_rxdma_reset(struct thc_device *dev); +int thc_rxdma_read(struct thc_device *dev, enum thc_dma_channel dma_channel, + void *read_buff, size_t *read_len, int *read_finished); +int thc_swdma_read(struct thc_device *dev, void *write_buff, size_t write_len, + u32 *prd_tbl_len, void *read_buff, size_t *read_len); +int thc_dma_write(struct thc_device *dev, void *buffer, size_t buf_len); + +struct thc_dma_context *thc_dma_init(struct thc_device *dev); + +#endif /* _INTEL_THC_DMA_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h new file mode 100644 index 000000000..a21222543 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h @@ -0,0 +1,893 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _INTEL_THC_HW_H_ +#define _INTEL_THC_HW_H_ + +#include + +/* THC registers offset */ +/* Touch Host Controller Control Register */ +#define THC_M_PRT_CONTROL_OFFSET 0x1008 +/* THC SPI Bus Configuration Register */ +#define THC_M_PRT_SPI_CFG_OFFSET 0x1010 +/* THC SPI Bus Read Opcode Register */ +#define THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET 0x1014 +/* THC SPI Bus Read Opcode Register */ +#define THC_M_PRT_SPI_DMARD_OPCODE_OFFSET 0x1018 +/* THC SPI Bus Write Opcode Register */ +#define THC_M_PRT_SPI_WR_OPCODE_OFFSET 0x101C +/* THC Interrupt Enable Register */ +#define THC_M_PRT_INT_EN_OFFSET 0x1020 +/* THC Interrupt Status Register */ +#define THC_M_PRT_INT_STATUS_OFFSET 0x1024 +/* THC Error Cause Register */ +#define THC_M_PRT_ERR_CAUSE_OFFSET 0x1028 +/* THC SW sequencing Control */ +#define THC_M_PRT_SW_SEQ_CNTRL_OFFSET 0x1040 +/* THC SW sequencing Status */ +#define THC_M_PRT_SW_SEQ_STS_OFFSET 0x1044 +/* THC SW Sequencing Data DW0 or SPI Address Register */ +#define THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET 0x1048 +/* THC SW sequencing Data DW1 */ +#define THC_M_PRT_SW_SEQ_DATA1_OFFSET 0x104C +/* THC SW sequencing Data DW2 */ +#define THC_M_PRT_SW_SEQ_DATA2_OFFSET 0x1050 +/* THC SW sequencing Data DW3 */ +#define THC_M_PRT_SW_SEQ_DATA3_OFFSET 0x1054 +/* THC SW sequencing Data DW4 */ +#define THC_M_PRT_SW_SEQ_DATA4_OFFSET 0x1058 +/* THC SW sequencing Data DW5 */ +#define THC_M_PRT_SW_SEQ_DATA5_OFFSET 0x105C +/* THC SW sequencing Data DW6 */ +#define THC_M_PRT_SW_SEQ_DATA6_OFFSET 0x1060 +/* THC SW sequencing Data DW7 */ +#define THC_M_PRT_SW_SEQ_DATA7_OFFSET 0x1064 +/* THC SW sequencing Data DW8 */ +#define THC_M_PRT_SW_SEQ_DATA8_OFFSET 0x1068 +/* THC SW sequencing Data DW9 */ +#define THC_M_PRT_SW_SEQ_DATA9_OFFSET 0x106C +/* THC SW sequencing Data DW10 */ +#define THC_M_PRT_SW_SEQ_DATA10_OFFSET 0x1070 +/* THC SW sequencing Data DW11 */ +#define THC_M_PRT_SW_SEQ_DATA11_OFFSET 0x1074 +/* THC SW sequencing Data DW12 */ +#define THC_M_PRT_SW_SEQ_DATA12_OFFSET 0x1078 +/* THC SW sequencing Data DW13 */ +#define THC_M_PRT_SW_SEQ_DATA13_OFFSET 0x107C +/* THC SW sequencing Data DW14 */ +#define THC_M_PRT_SW_SEQ_DATA14_OFFSET 0x1080 +/* THC SW sequencing Data DW15 */ +#define THC_M_PRT_SW_SEQ_DATA15_OFFSET 0x1084 +/* THC SW sequencing Data DW16 */ +#define THC_M_PRT_SW_SEQ_DATA16_OFFSET 0x1088 +/* THC Write PRD Base Address Register Low */ +#define THC_M_PRT_WPRD_BA_LOW_OFFSET 0x1090 +/* THC Write PRD Base Address Register High */ +#define THC_M_PRT_WPRD_BA_HI_OFFSET 0x1094 +/* THC Write DMA Control */ +#define THC_M_PRT_WRITE_DMA_CNTRL_OFFSET 0x1098 +/* THC Write Interrupt Status */ +#define THC_M_PRT_WRITE_INT_STS_OFFSET 0x109C +/* THC Write DMA Error Register */ +#define THC_M_PRT_WRITE_DMA_ERR_OFFSET 0x10A0 +/* THC device address for the bulk write */ +#define THC_M_PRT_WR_BULK_ADDR_OFFSET 0x10B4 +/* THC Device Interrupt Cause Register Address */ +#define THC_M_PRT_DEV_INT_CAUSE_ADDR_OFFSET 0x10B8 +/* THC Device Interrupt Cause Register Value */ +#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_OFFSET 0x10BC +/* THC TXDMA Frame Count */ +#define THC_M_PRT_TX_FRM_CNT_OFFSET 0x10E0 +/* THC TXDMA Packet Count */ +#define THC_M_PRT_TXDMA_PKT_CNT_OFFSET 0x10E4 +/* THC Device Interrupt Count on this port */ +#define THC_M_PRT_DEVINT_CNT_OFFSET 0x10E8 +/* Touch Device Interrupt Cause register Format Configuration Register 1 */ +#define THC_M_PRT_DEVINT_CFG_1_OFFSET 0x10EC +/* Touch Device Interrupt Cause register Format Configuration Register 2 */ +#define THC_M_PRT_DEVINT_CFG_2_OFFSET 0x10F0 +/* THC Read PRD Base Address Low for the 1st RXDMA */ +#define THC_M_PRT_RPRD_BA_LOW_1_OFFSET 0x1100 +/* THC Read PRD Base Address High for the 1st RXDMA */ +#define THC_M_PRT_RPRD_BA_HI_1_OFFSET 0x1104 +/* THC Read PRD Control for the 1st RXDMA */ +#define THC_M_PRT_RPRD_CNTRL_1_OFFSET 0x1108 +/* THC Read DMA Control for the 1st RXDMA */ +#define THC_M_PRT_READ_DMA_CNTRL_1_OFFSET 0x110C +/* THC Read Interrupt Status for the 1st RXDMA */ +#define THC_M_PRT_READ_DMA_INT_STS_1_OFFSET 0x1110 +/* THC Read DMA Error Register for the 1st RXDMA */ +#define THC_M_PRT_READ_DMA_ERR_1_OFFSET 0x1114 +/* Touch Sequencer GuC Tail Offset Address Low for the 1st RXDMA */ +#define THC_M_PRT_GUC_OFFSET_LOW_1_OFFSET 0x1118 +/* Touch Sequencer GuC Tail Offset Address High for the 1st RXDMA */ +#define THC_M_PRT_GUC_OFFSET_HI_1_OFFSET 0x111C +/* Touch Host Controller GuC Work Queue Item Size for the 1st RXDMA */ +#define THC_M_PRT_GUC_WORKQ_ITEM_SZ_1_OFFSET 0x1120 +/* Touch Host Controller GuC Control register for the 1st RXDMA */ +#define THC_M_PRT_GUC_WORKQ_SZ_1_OFFSET 0x1124 +/* Touch Sequencer Control for the 1st DMA */ +#define THC_M_PRT_TSEQ_CNTRL_1_OFFSET 0x1128 +/* Touch Sequencer GuC Doorbell Address Low for the 1st RXDMA */ +#define THC_M_PRT_GUC_DB_ADDR_LOW_1_OFFSET 0x1130 +/* Touch Sequencer GuC Doorbell Address High for the 1st RXDMA */ +#define THC_M_PRT_GUC_DB_ADDR_HI_1_OFFSET 0x1134 +/* Touch Sequencer GuC Doorbell Data */ +#define THC_M_PRT_GUC_DB_DATA_1_OFFSET 0x1138 +/* Touch Sequencer GuC Tail Offset Initial Value for the 1st RXDMA */ +#define THC_M_PRT_GUC_OFFSET_INITVAL_1_OFFSET 0x1140 +/* THC Device Address for the bulk/touch data read for the 1st RXDMA */ +#define THC_M_PRT_RD_BULK_ADDR_1_OFFSET 0x1170 +/* THC Gfx/SW Doorbell Count from the 1st Stream RXDMA on this port */ +#define THC_M_PRT_DB_CNT_1_OFFSET 0x11A0 +/* THC Frame Count from the 1st Stream RXDMA on this port */ +#define THC_M_PRT_FRM_CNT_1_OFFSET 0x11A4 +/* THC Micro Frame Count from the 1st Stream RXDMA on this port */ +#define THC_M_PRT_UFRM_CNT_1_OFFSET 0x11A8 +/* THC Packet Count from the 1st Stream RXDMA on this port */ +#define THC_M_PRT_RXDMA_PKT_CNT_1_OFFSET 0x11AC +/* + * THC Software Interrupt Count from the 1st Stream RXDMA + * on this port + */ +#define THC_M_PRT_SWINT_CNT_1_OFFSET 0x11B0 +/* Touch Sequencer Frame Drop Counter for the 1st RXDMA */ +#define THC_M_PRT_FRAME_DROP_CNT_1_OFFSET 0x11B4 +/* THC Coaescing 1 */ +#define THC_M_PRT_COALESCE_1_OFFSET 0x11B8 +/* THC Read PRD Base Address Low for the 2nd RXDMA */ +#define THC_M_PRT_RPRD_BA_LOW_2_OFFSET 0x1200 +/* THC Read PRD Base Address High for the 2nd RXDMA */ +#define THC_M_PRT_RPRD_BA_HI_2_OFFSET 0x1204 +/* THC Read PRD Control for the 2nd RXDMA */ +#define THC_M_PRT_RPRD_CNTRL_2_OFFSET 0x1208 +/* THC Read DMA Control for the 2nd RXDMA */ +#define THC_M_PRT_READ_DMA_CNTRL_2_OFFSET 0x120C +/* THC Read Interrupt Status for the 2nd RXDMA */ +#define THC_M_PRT_READ_DMA_INT_STS_2_OFFSET 0x1210 +/* THC Read DMA Error Register for the 2nd RXDMA */ +#define THC_M_PRT_READ_DMA_ERR_2_OFFSET 0x1214 +/* Touch Sequencer GuC Tail Offset Address Low for the 2nd RXDMA */ +#define THC_M_PRT_GUC_OFFSET_LOW_2_OFFSET 0x1218 +/* Touch Sequencer GuC Tail Offset Address High for the 2nd RXDMA */ +#define THC_M_PRT_GUC_OFFSET_HI_2_OFFSET 0x121C +/* Touch Host Controller GuC Work Queue Item Size for the 2nd RXDMA */ +#define THC_M_PRT_GUC_WORKQ_ITEM_SZ_2_OFFSET 0x1220 +/* Touch Host Controller GuC Control register for the 2nd RXDMA */ +#define THC_M_PRT_GUC_WORKQ_SZ_2_OFFSET 0x1224 +/* Touch Sequencer Control for the 2nd DMA */ +#define THC_M_PRT_TSEQ_CNTRL_2_OFFSET 0x1228 +/* Touch Sequencer GuC Doorbell Address Low for the 2nd RXDMA */ +#define THC_M_PRT_GUC_DB_ADDR_LOW_2_OFFSET 0x1230 +/* Touch Sequencer GuC Doorbell Address High for the 2nd RXDMA */ +#define THC_M_PRT_GUC_DB_ADDR_HI_2_OFFSET 0x1234 +/* Touch Sequencer GuC Doorbell Data for PRD2 */ +#define THC_M_PRT_GUC_DB_DATA_2_OFFSET 0x1238 +/* Touch Sequencer GuC Tail Offset Initial Value for the 2nd RXDMA */ +#define THC_M_PRT_GUC_OFFSET_INITVAL_2_OFFSET 0x1240 +/* THC Device Address for the bulk/touch data read for the 2nd RXDMA */ +#define THC_M_PRT_RD_BULK_ADDR_2_OFFSET 0x1270 +/* THC Gfx/SW Doorbell Count from the 2nd Stream RXDMA on this port */ +#define THC_M_PRT_DB_CNT_2_OFFSET 0x12A0 +/* THC Frame Count from the 2nd Stream RXDMA on this port */ +#define THC_M_PRT_FRM_CNT_2_OFFSET 0x12A4 +/* THC Micro Frame Count from the 2nd Stream RXDMA on this port */ +#define THC_M_PRT_UFRM_CNT_2_OFFSET 0x12A8 +/* THC Packet Count from the 2nd Stream RXDMA on this port */ +#define THC_M_PRT_RXDMA_PKT_CNT_2_OFFSET 0x12AC +/* + * THC Software Interrupt Count from the 2nd Stream RXDMA + * on this port + */ +#define THC_M_PRT_SWINT_CNT_2_OFFSET 0x12B0 +/* Touch Sequencer Frame Drop Counter for the 2nd RXDMA */ +#define THC_M_PRT_FRAME_DROP_CNT_2_OFFSET 0x12B4 +/* THC Coaescing 2 */ +#define THC_M_PRT_COALESCE_2_OFFSET 0x12B8 +/* THC SPARE REGISTER */ +#define THC_M_PRT_SPARE_REG_OFFSET 0x12BC +/* THC Read PRD Base Address Low for the SW RXDMA */ +#define THC_M_PRT_RPRD_BA_LOW_SW_OFFSET 0x12C0 +/* THC Read PRD Base Address High for the SW RXDMA */ +#define THC_M_PRT_RPRD_BA_HI_SW_OFFSET 0x12C4 +/* THC Read PRD Control for the SW RXDMA */ +#define THC_M_PRT_RPRD_CNTRL_SW_OFFSET 0x12C8 +/* THC Read DMA Control for the SW RXDMA */ +#define THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET 0x12CC +/* THC Read Interrupt Status for the SW RXDMA */ +#define THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET 0x12D0 +/* Touch Sequencer Control for the SW DMA */ +#define THC_M_PRT_TSEQ_CNTRL_SW_OFFSET 0x12D4 +/* Address for the bulk read for SW DMA engine */ +#define THC_M_PRT_RD_BULK_ADDR_SW_OFFSET 0x12D8 +/* THC Frame Count from the SW RXDMA on this port */ +#define THC_M_PRT_FRM_CNT_SW_OFFSET 0x12DC +/* THC Packet Count from the SW RXDMA on this port */ +#define THC_M_PRT_RXDMA_PKT_CNT_SW_OFFSET 0x12E0 +/* SW DMA PRD Table Length */ +#define THC_M_PRT_SW_DMA_PRD_TABLE_LEN_OFFSET 0x12E4 +/* THC timing based Frame/Interrupt caolescing control register for 1st RXDMA */ +#define THC_M_PRT_COALESCE_CNTRL_1_OFFSET 0x12E8 +/* THC timing based Frame/Interrupt caolescing control register for 2nd RXDMA */ +#define THC_M_PRT_COALESCE_CNTRL_2_OFFSET 0x12EC +/* Touch Sequencer PRD Table Empty Counter for the 1st RXDMA */ +#define THC_M_PRT_PRD_EMPTY_CNT_1_OFFSET 0x12F0 +/* Touch Sequencer PRD Table Empty Counter for the 2nd RXDM */ +#define THC_M_PRT_PRD_EMPTY_CNT_2_OFFSET 0x12F4 +/* THC coalescing status to reflect the current coalescing FSM state for 1st RXDMA */ +#define THC_M_PRT_COALESCE_STS_1_OFFSET 0x12F8 +/* THC coalescing status to reflect the current coalescing FSM state for 2nd RXDMA */ +#define THC_M_PRT_COALESCE_STS_2_OFFSET 0x12FC +/* THC Register for the SPI Port Duty Cycle Configuration */ +#define THC_M_PRT_SPI_DUTYC_CFG_OFFSET 0x1300 +/* THC Register for SW I2C Wtite Sequecning control */ +#define THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_OFFSET 0x1304 +/* THC current Timestamp Register for RXDMA1 */ +#define THC_M_PRT_TIMESTAMP_1_OFFSET 0x1308 +/* THC current Timestamp Register for RXDMA2 */ +#define THC_M_PRT_TIMESTAMP_2_OFFSET 0x130C +/* Current SYNC Event Timestamp Register */ +#define THC_M_PRT_SYNC_TIMESTAMP_OFFSET 0x1310 +/* THC Display Sync Register */ +#define THC_M_PRT_DISP_SYNC_OFFSET 0x1314 +/* THC Display Sync Register */ +#define THC_M_PRT_DISP_SYNC_2_OFFSET 0x1318 +/* THC Register for SW I2C Wtite Sequecning control */ +#define THC_M_PRT_I2C_CFG_OFFSET 0x131C + +/* THC register bits definition */ +#define TXN_ERR_INT_STS_BIT BIT(28) +#define TXN_FATAL_INT_STS_BIT BIT(30) + +#define NONDMA_INT_STS_BIT BIT(4) +#define EOF_INT_STS_BIT BIT(5) + +#define THC_CFG_DID_VID_VID GENMASK(15, 0) +#define THC_CFG_DID_VID_DID GENMASK(31, 16) + +#define THC_CFG_STS_CMD_IOSE BIT(0) +#define THC_CFG_STS_CMD_MSE BIT(1) +#define THC_CFG_STS_CMD_BME BIT(2) +#define THC_CFG_STS_CMD_SPCYC BIT(3) +#define THC_CFG_STS_CMD_MWRIEN BIT(4) +#define THC_CFG_STS_CMD_VGAPS BIT(5) +#define THC_CFG_STS_CMD_PERRR BIT(6) +#define THC_CFG_STS_CMD_SERREN BIT(8) +#define THC_CFG_STS_CMD_FBTBEN BIT(9) +#define THC_CFG_STS_CMD_INTD BIT(10) +#define THC_CFG_STS_CMD_INTS BIT(19) +#define THC_CFG_STS_CMD_CAPL BIT(20) +#define THC_CFG_STS_CMD_MCAP BIT(21) +#define THC_CFG_STS_CMD_FBTBC BIT(23) +#define THC_CFG_STS_CMD_MDPE BIT(24) +#define THC_CFG_STS_CMD_DEVT GENMASK(26, 25) +#define THC_CFG_STS_CMD_STA BIT(27) +#define THC_CFG_STS_CMD_RTA BIT(28) +#define THC_CFG_STS_CMD_RMA BIT(29) +#define THC_CFG_STS_CMD_SSE BIT(30) +#define THC_CFG_STS_CMD_DPE BIT(31) + +#define THC_CFG_CC_RID_RID GENMASK(7, 0) +#define THC_CFG_CC_RID_PI GENMASK(15, 8) +#define THC_CFG_CC_RID_SCC GENMASK(23, 16) +#define THC_CFG_CC_RID_BCC GENMASK(31, 24) + +#define THC_CFG_BIST_HTYPE_LT_CLS_CLSZ GENMASK(7, 0) +#define THC_CFG_BIST_HTYPE_LT_CLS_LT GENMASK(15, 8) +#define THC_CFG_BIST_HTYPE_LT_CLS_HTYPE GENMASK(22, 16) +#define THC_CFG_BIST_HTYPE_LT_CLS_MFD BIT(23) + +#define THC_CFG_BAR0_LOW_MEMSPACE BIT(0) +#define THC_CFG_BAR0_LOW_TYP GENMASK(2, 1) +#define THC_CFG_BAR0_LOW_PREFETCH BIT(3) +#define THC_CFG_BAR0_LOW_MEMSIZE GENMASK(14, 4) +#define THC_CFG_BAR0_LOW_MEMBAR GENMASK(31, 15) +#define THC_CFG_BAR0_HI_MEMBAR GENMASK(31, 0) + +#define THC_CFG_SID_SVID_SSVID GENMASK(15, 0) +#define THC_CFG_SID_SVID_SSID GENMASK(31, 16) + +#define THC_CFG_CAPP_CP GENMASK(7, 0) + +#define THC_CFG_INT_ILINE GENMASK(7, 0) +#define THC_CFG_INT_IPIN GENMASK(15, 8) + +#define THC_CFG_UR_STS_CTL_URRE BIT(0) +#define THC_CFG_UR_STS_CTL_URD BIT(1) +#define THC_CFG_UR_STS_CTL_FD BIT(2) + +#define THC_CFG_MSIMC_MSINP_MSICID_CAPID GENMASK(7, 0) +#define THC_CFG_MSIMC_MSINP_MSICID_NXTP GENMASK(15, 8) +#define THC_CFG_MSIMC_MSINP_MSICID_MSIE BIT(16) +#define THC_CFG_MSIMC_MSINP_MSICID_MMC GENMASK(19, 17) +#define THC_CFG_MSIMC_MSINP_MSICID_MMEN GENMASK(22, 20) +#define THC_CFG_MSIMC_MSINP_MSICID_XAC BIT(23) +#define THC_CFG_MSIMC_MSINP_MSICID_PVMC BIT(24) +#define THC_CFG_MSIMA_MADDR GENMASK(31, 2) +#define THC_CFG_MSIMUA_MAUDDR GENMASK(31, 0) +#define THC_CFG_MSIMD_MDAT GENMASK(15, 0) + +#define THC_CFG_PMCAP_PMNP_PMCID_CAPP GENMASK(7, 0) +#define THC_CFG_PMCAP_PMNP_PMCID_NXTP GENMASK(15, 8) +#define THC_CFG_PMCAP_PMNP_PMCID_VER GENMASK(18, 16) +#define THC_CFG_PMCAP_PMNP_PMCID_PMECLK BIT(19) +#define THC_CFG_PMCAP_PMNP_PMCID_DSI BIT(21) +#define THC_CFG_PMCAP_PMNP_PMCID_AUXC GENMASK(24, 22) +#define THC_CFG_PMCAP_PMNP_PMCID_D1S BIT(25) +#define THC_CFG_PMCAP_PMNP_PMCID_D2S BIT(26) +#define THC_CFG_PMCAP_PMNP_PMCID_PMES GENMASK(31, 27) + +#define THC_CFG_PMD_PMCSRBSE_PMCSR_PWRST GENMASK(1, 0) +#define THC_CFG_PMD_PMCSRBSE_PMCSR_NSR BIT(3) +#define THC_CFG_PMD_PMCSRBSE_PMCSR_PMEEN BIT(8) +#define THC_CFG_PMD_PMCSRBSE_PMCSR_DSEL GENMASK(12, 9) +#define THC_CFG_PMD_PMCSRBSE_PMCSR_DS GENMASK(14, 13) +#define THC_CFG_PMD_PMCSRBSE_PMCSR_PMESTS BIT(15) + +#define THC_CFG_DEVIDLE_CAPPID GENMASK(7, 0) +#define THC_CFG_DEVIDLE_NCAPPP GENMASK(15, 8) +#define THC_CFG_DEVIDLE_LENGTH GENMASK(23, 16) +#define THC_CFG_DEVIDLE_REV GENMASK(27, 24) +#define THC_CFG_DEVIDLE_VID GENMASK(31, 28) + +#define THC_CFG_VSHDR_VSECID GENMASK(15, 0) +#define THC_CFG_VSHDR_VSECR GENMASK(19, 16) +#define THC_CFG_VSHDR_VSECL GENMASK(31, 20) + +#define THC_CFG_SWLTRPTR_VALID BIT(0) +#define THC_CFG_SWLTRPTR_BARNUM GENMASK(3, 1) +#define THC_CFG_SWLTRPTR_SWLTRLOC GENMASK(31, 4) + +#define THC_CFG_DEVIDLEPTR_VALID BIT(0) +#define THC_CFG_DEVIDLEPTR_BARNUM GENMASK(3, 1) +#define THC_CFG_DEVIDLEPTR_DEVIDLELOC GENMASK(31, 4) +#define THC_CFG_DEVIDLEPOL_POLV GENMASK(9, 0) +#define THC_CFG_DEVIDLEPOL_POLS GENMASK(12, 10) + +#define THC_CFG_PCE_SPE BIT(0) +#define THC_CFG_PCE_I3E BIT(1) +#define THC_CFG_PCE_D3HE BIT(2) +#define THC_CFG_PCE_SE BIT(3) +#define THC_CFG_PCE_HAE BIT(5) + +#define THC_CFG_MANID_PROC GENMASK(7, 0) +#define THC_CFG_MANID_MID GENMASK(15, 8) +#define THC_CFG_MANID_MSID GENMASK(23, 16) +#define THC_CFG_MANID_DOT GENMASK(27, 24) + +#define THC_M_CMN_DEVIDLECTRL_CIP BIT(0) +#define THC_M_CMN_DEVIDLECTRL_IR BIT(1) +#define THC_M_CMN_DEVIDLECTRL_DEVIDLE BIT(2) +#define THC_M_CMN_DEVIDLECTRL_RR BIT(3) +#define THC_M_CMN_DEVIDLECTRL_IRC BIT(4) + +#define THC_M_CMN_LTR_CTRL_OFFSET 0x14 +#define THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ BIT(0) +#define THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN BIT(1) +#define THC_M_CMN_LTR_CTRL_LP_LTR_REQ BIT(2) +#define THC_M_CMN_LTR_CTRL_LP_LTR_EN BIT(3) +#define THC_M_CMN_LTR_CTRL_LP_LTR_SCALE GENMASK(6, 4) +#define THC_M_CMN_LTR_CTRL_LP_LTR_VAL GENMASK(16, 7) +#define THC_M_CMN_LTR_CTRL_ACT_LTR_SCALE GENMASK(19, 17) +#define THC_M_CMN_LTR_CTRL_ACT_LTR_VAL GENMASK(29, 20) +#define THC_M_CMN_LTR_CTRL_LAST_LTR_SENT GENMASK(31, 30) + +#define THC_M_PRT_CONTROL_TSFTRST BIT(0) +#define THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN BIT(1) +#define THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS BIT(2) +#define THC_M_PRT_CONTROL_DEVRST BIT(3) +#define THC_M_PRT_CONTROL_THC_DRV_LOCK_EN BIT(13) +#define THC_M_PRT_CONTROL_THC_INSTANCE_INDEX GENMASK(18, 16) +#define THC_M_PRT_CONTROL_PORT_INDEX GENMASK(22, 20) +#define THC_M_PRT_CONTROL_THC_ARB_POLICY GENMASK(25, 24) +#define THC_M_PRT_CONTROL_THC_BIOS_LOCK_EN BIT(27) +#define THC_M_PRT_CONTROL_PORT_SUPPORTED BIT(28) +#define THC_M_PRT_CONTROL_SPI_IO_RDY BIT(29) +#define THC_M_PRT_CONTROL_PORT_TYPE GENMASK(31, 30) + +#define THC_M_PRT_SPI_CFG_SPI_TRDC GENMASK(1, 0) +#define THC_M_PRT_SPI_CFG_SPI_TRMODE GENMASK(3, 2) +#define THC_M_PRT_SPI_CFG_SPI_TCRF GENMASK(6, 4) +#define THC_M_PRT_SPI_CFG_SPI_RD_MPS GENMASK(15, 7) +#define THC_M_PRT_SPI_CFG_SPI_TWMODE GENMASK(19, 18) +#define THC_M_PRT_SPI_CFG_SPI_TCWF GENMASK(22, 20) +#define THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN BIT(23) +#define THC_M_PRT_SPI_CFG_SPI_WR_MPS GENMASK(31, 24) + +#define THC_M_PRT_SPI_ICRRD_OPCODE_SPI_SIO GENMASK(31, 24) +#define THC_M_PRT_SPI_ICRRD_OPCODE_SPI_DIO GENMASK(23, 16) +#define THC_M_PRT_SPI_ICRRD_OPCODE_SPI_QIO GENMASK(15, 8) + +#define THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE GENMASK(15, 0) +#define THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL GENMASK(23, 16) +#define THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL_EN BIT(30) +#define THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE_EN BIT(31) + +#define THC_M_PRT_INT_EN_SIPE BIT(0) +#define THC_M_PRT_INT_EN_SBO BIT(1) +#define THC_M_PRT_INT_EN_SIDR BIT(2) +#define THC_M_PRT_INT_EN_SOFB BIT(3) +#define THC_M_PRT_INT_EN_INVLD_DEV_ENTRY_INT_EN BIT(9) +#define THC_M_PRT_INT_EN_FRAME_BABBLE_ERR_INT_EN BIT(10) +#define THC_M_PRT_INT_EN_BUF_OVRRUN_ERR_INT_EN BIT(12) +#define THC_M_PRT_INT_EN_PRD_ENTRY_ERR_INT_EN BIT(13) +#define THC_M_PRT_INT_EN_DISP_SYNC_EVT_INT_EN BIT(14) +#define THC_M_PRT_INT_EN_DEV_RAW_INT_EN BIT(15) +#define THC_M_PRT_INT_EN_FATAL_ERR_INT_EN BIT(16) +#define THC_M_PRT_INT_EN_THC_I2C_IC_RX_UNDER_INT_EN BIT(17) +#define THC_M_PRT_INT_EN_THC_I2C_IC_RX_OVER_INT_EN BIT(18) +#define THC_M_PRT_INT_EN_THC_I2C_IC_RX_FULL_INT_EN BIT(19) +#define THC_M_PRT_INT_EN_THC_I2C_IC_TX_OVER_INT_EN BIT(20) +#define THC_M_PRT_INT_EN_THC_I2C_IC_TX_EMPTY_INT_EN BIT(21) +#define THC_M_PRT_INT_EN_THC_I2C_IC_TX_ABRT_INT_EN BIT(22) +#define THC_M_PRT_INT_EN_THC_I2C_IC_SCL_STUCK_AT_LOW_DET_INT_EN BIT(24) +#define THC_M_PRT_INT_EN_THC_I2C_IC_STOP_DET_INT_EN BIT(25) +#define THC_M_PRT_INT_EN_THC_I2C_IC_START_DET_INT_EN BIT(26) +#define THC_M_PRT_INT_EN_THC_I2C_IC_MST_ON_HOLD_INT_EN BIT(27) +#define THC_M_PRT_INT_EN_TXN_ERR_INT_EN BIT(29) +#define THC_M_PRT_INT_EN_GBL_INT_EN BIT(31) + +#define THC_M_PRT_INT_STATUS_DISP_SYNC_EVT_INT_STS BIT(14) +#define THC_M_PRT_INT_STATUS_DEV_RAW_INT_STS BIT(15) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_UNDER_INT_STS BIT(17) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_OVER_INT_STS BIT(18) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_FULL_INT_STS BIT(19) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_OVER_INT_STS BIT(20) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_EMPTY_INT_STS BIT(21) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_ABRT_INT_STS BIT(22) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_ACTIVITY_INT_STS BIT(23) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_SCL_STUCK_AT_LOW_INT_STS BIT(24) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_STOP_DET_INT_STS BIT(25) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_START_DET_INT_STS BIT(26) +#define THC_M_PRT_INT_STATUS_THC_I2C_IC_MST_ON_HOLD_INT_STS BIT(27) +#define THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS BIT(28) +#define THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS BIT(30) + +#define THC_M_PRT_ERR_CAUSE_INVLD_DEV_ENTRY BIT(9) +#define THC_M_PRT_ERR_CAUSE_FRAME_BABBLE_ERR BIT(10) +#define THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR BIT(12) +#define THC_M_PRT_ERR_CAUSE_PRD_ENTRY_ERR BIT(13) +#define THC_M_PRT_ERR_CAUSE_FATAL_ERR_CAUSE GENMASK(23, 16) + +#define THC_M_PRT_SW_SEQ_CNTRL_TSSGO BIT(0) +#define THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE BIT(1) +#define THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CMD GENMASK(15, 8) +#define THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC GENMASK(31, 16) +#define THC_M_PRT_SW_SEQ_STS_TSSDONE BIT(0) +#define THC_M_PRT_SW_SEQ_STS_THC_SS_ERR BIT(1) +#define THC_M_PRT_SW_SEQ_STS_THC_SS_CIP BIT(3) +#define THC_M_PRT_SW_SEQ_DATA0_ADDR_THC_SW_SEQ_DATA0_ADDR GENMASK(31, 0) +#define THC_M_PRT_SW_SEQ_DATA1_THC_SW_SEQ_DATA1 GENMASK(31, 0) + +#define THC_M_PRT_WPRD_BA_LOW_THC_M_PRT_WPRD_BA_LOW GENMASK(31, 12) +#define THC_M_PRT_WPRD_BA_HI_THC_M_PRT_WPRD_BA_HI GENMASK(31, 0) + +#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START BIT(0) +#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_ERROR BIT(1) +#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC BIT(2) +#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL BIT(3) +#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_UHS BIT(23) +#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC GENMASK(31, 24) + +#define THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS BIT(0) +#define THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ERROR_STS BIT(1) +#define THC_M_PRT_WRITE_INT_STS_THC_WRDMA_IOC_STS BIT(2) +#define THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ACTIVE BIT(3) + +#define THC_M_PRT_WR_BULK_ADDR_THC_M_PRT_WR_BULK_ADDR GENMASK(31, 0) + +#define THC_M_PRT_DEV_INT_CAUSE_ADDR_THC_M_PRT_DEV_INT_CAUSE_ADDR GENMASK(31, 0) +#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_INTERRUPT_TYPE GENMASK(3, 0) +#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_MICRO_FRAME_SIZE GENMASK(23, 4) +#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_BEGINNING_OF_FRAME BIT(29) +#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_END_OF_FRAME BIT(30) +#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_FRAME_TYPE BIT(31) + +#define THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT GENMASK(30, 0) +#define THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT_RST BIT(31) + +#define THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT GENMASK(30, 0) +#define THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT_RST BIT(31) + +#define THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT GENMASK(30, 0) +#define THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT_RST BIT(31) + +#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_OFFSET GENMASK(4, 0) +#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_LEN GENMASK(9, 5) +#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_EOF_OFFSET GENMASK(14, 10) +#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_SEND_ICR_US_EN BIT(15) +#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL GENMASK(31, 16) + +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_OFFSET GENMASK(4, 0) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_LEN GENMASK(9, 5) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_UNIT GENMASK(15, 12) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_IGNORE BIT(16) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_VAL BIT(17) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_RXDMA_ADDRINC_DIS BIT(24) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_TXDMA_ADDRINC_DIS BIT(25) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_RXDMA_PKT_STRM_EN BIT(26) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_TXDMA_PKT_STRM_EN BIT(27) +#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_DEVINT_POL BIT(28) + +#define THC_M_PRT_RPRD_BA_LOW_1_THC_M_PRT_RPRD_BA_LOW GENMASK(31, 12) +#define THC_M_PRT_RPRD_BA_HI_1_THC_M_PRT_RPRD_BA_HI GENMASK(31, 0) + +#define THC_M_PRT_RPRD_CNTRL_PCD GENMASK(6, 0) +#define THC_M_PRT_RPRD_CNTRL_PTEC GENMASK(15, 8) +#define THC_M_PRT_RPRD_CNTRL_PREFETCH_WM GENMASK(19, 16) + +#define THC_M_PRT_READ_DMA_CNTRL_START BIT(0) +#define THC_M_PRT_READ_DMA_CNTRL_IE_ERROR BIT(1) +#define THC_M_PRT_READ_DMA_CNTRL_IE_IOC BIT(2) +#define THC_M_PRT_READ_DMA_CNTRL_IE_STALL BIT(3) +#define THC_M_PRT_READ_DMA_CNTRL_IE_NDDI BIT(4) +#define THC_M_PRT_READ_DMA_CNTRL_IE_EOF BIT(5) +#define THC_M_PRT_READ_DMA_CNTRL_IE_DMACPL BIT(7) +#define THC_M_PRT_READ_DMA_CNTRL_TPCRP GENMASK(15, 8) +#define THC_M_PRT_READ_DMA_CNTRL_TPCWP GENMASK(23, 16) +#define THC_M_PRT_READ_DMA_CNTRL_INT_SW_DMA_EN BIT(28) +#define THC_M_PRT_READ_DMA_CNTRL_SOO BIT(29) +#define THC_M_PRT_READ_DMA_CNTRL_UHS BIT(30) +#define THC_M_PRT_READ_DMA_CNTRL_TPCPR BIT(31) + +#define THC_M_PRT_READ_DMA_INT_STS_DMACPL_STS BIT(0) +#define THC_M_PRT_READ_DMA_INT_STS_ERROR_STS BIT(1) +#define THC_M_PRT_READ_DMA_INT_STS_IOC_STS BIT(2) +#define THC_M_PRT_READ_DMA_INT_STS_STALL_STS BIT(3) +#define THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS BIT(4) +#define THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS BIT(5) +#define THC_M_PRT_READ_DMA_INT_STS_ACTIVE BIT(8) + +#define THC_M_PRT_READ_DMA_ERR_1_DLERR BIT(0) + +#define THC_M_PRT_GUC_OFFSET_LOW_1_THC_M_PRT_GUC_OFFSET_LOW GENMASK(31, 3) +#define THC_M_PRT_GUC_OFFSET_HI_1_THC_M_PRT_GUC_OFFSET_HI GENMASK(31, 0) +#define THC_M_PRT_GUC_WORKQ_ITEM_SZ_1_WORKQ_ITEM_SZ GENMASK(23, 0) +#define THC_M_PRT_GUC_WORKQ_SZ_1_WORKQ_SZ GENMASK(23, 0) +#define THC_M_PRT_GUC_WORKQ_SZ_1_FCD GENMASK(27, 24) +#define THC_M_PRT_GUC_WORKQ_SZ_1_GIC GENMASK(31, 28) + +#define THC_M_PRT_TSEQ_CNTRL_1_RGD BIT(2) +#define THC_M_PRT_TSEQ_CNTRL_1_EGP BIT(3) +#define THC_M_PRT_TSEQ_CNTRL_1_RTO BIT(4) +#define THC_M_PRT_TSEQ_CNTRL_1_EWOG BIT(5) +#define THC_M_PRT_TSEQ_CNTRL_1_RWOGC BIT(6) +#define THC_M_PRT_TSEQ_CNTRL_1_RX_DATA_FIFO_WR_WM GENMASK(25, 16) +#define THC_M_PRT_TSEQ_CNTRL_1_RESET_PREP_CHICKEN BIT(30) +#define THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN BIT(31) + +#define THC_M_PRT_GUC_DB_ADDR_LOW_1_GUC_DB_ADDR_LOW GENMASK(31, 2) +#define THC_M_PRT_GUC_DB_ADDR_HI_1_GUC_DB_ADDR_HI GENMASK(31, 0) +#define THC_M_PRT_GUC_DB_DATA_1_GUC_DB_DATA GENMASK(31, 0) +#define THC_M_PRT_GUC_OFFSET_INITVAL_1_THC_M_PRT_GUC_OFFSET_INITVAL GENMASK(31, 0) + +#define THC_M_PRT_RD_BULK_ADDR_1_THC_M_PRT_RD_BULK_ADDR GENMASK(31, 0) + +#define THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT GENMASK(30, 0) +#define THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT_RST BIT(31) + +#define THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT GENMASK(30, 0) +#define THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT_RST BIT(31) + +#define THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT GENMASK(30, 0) +#define THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT_RST BIT(31) + +#define THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT GENMASK(30, 0) +#define THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT_RST BIT(31) + +#define THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT GENMASK(30, 0) +#define THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST BIT(31) + +#define THC_M_PRT_FRAME_DROP_CNT_1_NOFD GENMASK(30, 0) +#define THC_M_PRT_FRAME_DROP_CNT_1_RFDC BIT(31) + +#define THC_M_PRT_COALESCE_1_COALESCE_TIMEOUT GENMASK(6, 0) + +#define THC_M_PRT_RPRD_BA_LOW_2_THC_M_PRT_RPRD_BA_LOW GENMASK(31, 12) +#define THC_M_PRT_RPRD_BA_HI_2_THC_M_PRT_RPRD_BA_HI GENMASK(31, 0) + +#define THC_M_PRT_READ_DMA_ERR_2_DLERR BIT(0) + +#define THC_M_PRT_GUC_OFFSET_LOW_2_THC_M_PRT_GUC_OFFSET_LOW GENMASK(31, 3) +#define THC_M_PRT_GUC_OFFSET_HI_2_THC_M_PRT_GUC_OFFSET_HI GENMASK(31, 0) + +#define THC_M_PRT_GUC_WORKQ_ITEM_SZ_2_WORKQ_ITEM_SZ GENMASK(23, 0) +#define THC_M_PRT_GUC_WORKQ_SZ_2_WORKQ_SZ GENMASK(23, 0) +#define THC_M_PRT_GUC_WORKQ_SZ_2_FCD GENMASK(27, 24) +#define THC_M_PRT_GUC_WORKQ_SZ_2_GIC GENMASK(31, 28) + +#define THC_M_PRT_TSEQ_CNTRL_2_RGD BIT(2) +#define THC_M_PRT_TSEQ_CNTRL_2_EGP BIT(3) +#define THC_M_PRT_TSEQ_CNTRL_2_RTO BIT(4) + +#define THC_M_PRT_GUC_DB_ADDR_LOW_2_GUC_DB_ADDR_LOW GENMASK(31, 2) +#define THC_M_PRT_GUC_DB_ADDR_HI_2_GUC_DB_ADDR_HI GENMASK(31, 0) + +#define THC_M_PRT_GUC_DB_DATA_2_GUC_DB_DATA GENMASK(31, 0) + +#define THC_M_PRT_GUC_OFFSET_INITVAL_2_THC_M_PRT_GUC_OFFSET_INITVAL GENMASK(31, 0) + +#define THC_M_PRT_RD_BULK_ADDR_2_THC_M_PRT_RD_BULK_ADDR GENMASK(31, 0) + +#define THC_M_PRT_DB_CNT_2_THC_M_PRT_DB_CNT GENMASK(30, 0) +#define THC_M_PRT_DB_CNT_2_THC_M_PRT_DB_CNT_RST BIT(31) + +#define THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT GENMASK(30, 0) +#define THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT_RST BIT(31) + +#define THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT GENMASK(30, 0) +#define THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT_RST BIT(31) + +#define THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT GENMASK(30, 0) +#define THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT_RST BIT(31) + +#define THC_M_PRT_SWINT_CNT_2_THC_M_PRT_SWINT_CNT GENMASK(30, 0) +#define THC_M_PRT_SWINT_CNT_2_THC_M_PRT_SWINT_CNT_RST BIT(31) + +#define THC_M_PRT_FRAME_DROP_CNT_2_NOFD GENMASK(30, 0) +#define THC_M_PRT_FRAME_DROP_CNT_2_RFDC BIT(31) + +#define THC_M_PRT_COALESCE_2_COALESCE_TIMEOUT GENMASK(6, 0) + +#define THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_I2C_RW_PIO_EN BIT(23) +#define THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_PIO_I2C_WBC GENMASK(31, 26) + +#define THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN BIT(23) +#define THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC GENMASK(31, 26) + +#define THC_M_PRT_PRD_EMPTY_CNT_1_RPTEC BIT(31) +#define THC_M_PRT_PRD_EMPTY_CNT_2_RPTEC BIT(31) + +#define THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN GENMASK(23, 0) + +#define THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL GENMASK(3, 0) +#define THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN BIT(25) +#define THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN BIT(30) +#define THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN BIT(31) + +#define THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE BIT(2) + +/* CS Assertion delay default value */ +#define THC_CSA_CK_DELAY_VAL_DEFAULT 4 + +/* ARB policy definition */ +/* Arbiter switches on packet boundary */ +#define THC_ARB_POLICY_PACKET_BOUNDARY 0 +/* Arbiter switches on Micro Frame boundary */ +#define THC_ARB_POLICY_UFRAME_BOUNDARY 1 +/* Arbiter switches on Frame boundary */ +#define THC_ARB_POLICY_FRAME_BOUNDARY 2 + +#define THC_REGMAP_POLLING_INTERVAL_US 10 /* 10us */ +#define THC_PIO_DONE_TIMEOUT_US USEC_PER_SEC /* 1s */ + +/* Default configures for HIDSPI */ +#define THC_BIT_OFFSET_INTERRUPT_TYPE 4 +/* input_report_type is 4 bits for HIDSPI */ +#define THC_BIT_LENGTH_INTERRUPT_TYPE 4 +/* Last fragment indicator is bit 15 for HIDSPI */ +#define THC_BIT_OFFSET_LAST_FRAGMENT_FLAG 22 +#define THC_BIT_OFFSET_MICROFRAME_SIZE 8 +/* input_report_length is 14 bits for HIDSPI */ +#define THC_BIT_LENGTH_MICROFRAME_SIZE 14 +/* MFS unit in power of 2 */ +#define THC_UNIT_MICROFRAME_SIZE 2 +#define THC_BITMASK_INTERRUPT_TYPE_DATA 1 +#define THC_BITMASK_INVALID_TYPE_DATA 2 + +/* Interrupt Quiesce default timeout value */ +#define THC_QUIESCE_EN_TIMEOUT_US USEC_PER_SEC /* 1s */ + +/* LTR definition */ +/* + * THC uses scale to calculate final LTR value. + * Scale is geometric progression of 2^5 step, starting from 2^0. + * For example, THC_LTR_SCALE_2(2) means 2^(5 * 2) = 1024, unit is ns. + */ +#define THC_LTR_SCALE_0 0 +#define THC_LTR_SCALE_1 1 +#define THC_LTR_SCALE_2 2 +#define THC_LTR_SCALE_3 3 +#define THC_LTR_SCALE_4 4 +#define THC_LTR_SCALE_5 5 +#define THC_LTR_MODE_ACTIVE 0 +#define THC_LTR_MODE_LP 1 +#define THC_LTR_MIN_VAL_SCALE_3 BIT(10) +#define THC_LTR_MAX_VAL_SCALE_3 BIT(15) +#define THC_LTR_MIN_VAL_SCALE_4 BIT(15) +#define THC_LTR_MAX_VAL_SCALE_4 BIT(20) +#define THC_LTR_MIN_VAL_SCALE_5 BIT(20) +#define THC_LTR_MAX_VAL_SCALE_5 BIT(25) + +/* + * THC PIO opcode default value + * @THC_PIO_OP_SPI_TIC_READ: THC opcode for SPI PIO read + * @THC_PIO_OP_SPI_TIC_WRITE: THC opcode for SPI PIO write + * @THC_PIO_OP_I2C_SUBSYSTEM_READ: THC opcode for read I2C subsystem registers + * @THC_PIO_OP_I2C_SUBSYSTEM_WRITE: THC opcode for write I2C subsystem registers + * @THC_PIO_OP_I2C_TIC_READ: THC opcode for read I2C device + * @THC_PIO_OP_I2C_TIC_WRITE: THC opcode for write I2C device + * @THC_PIO_OP_I2C_TIC_WRITE_AND_READ: THC opcode for write followed by read I2C device + */ +enum thc_pio_opcode { + THC_PIO_OP_SPI_TIC_READ = 0x4, + THC_PIO_OP_SPI_TIC_WRITE = 0x6, + THC_PIO_OP_I2C_SUBSYSTEM_READ = 0x12, + THC_PIO_OP_I2C_SUBSYSTEM_WRITE = 0x13, + THC_PIO_OP_I2C_TIC_READ = 0x14, + THC_PIO_OP_I2C_TIC_WRITE = 0x18, + THC_PIO_OP_I2C_TIC_WRITE_AND_READ = 0x1C, +}; + +/** + * THC SPI IO mode + * @THC_SINGLE_IO: single IO mode, 1(opcode) - 1(address) - 1(data) + * @THC_DUAL_IO: dual IO mode, 1(opcode) - 2(address) - 2(data) + * @THC_QUAD_IO: quad IO mode, 1(opcode) - 4(address) - 4(data) + * @THC_QUAD_PARALLEL_IO: parallel quad IO mode, 4(opcode) - 4(address) - 4(data) + */ +enum thc_spi_iomode { + THC_SINGLE_IO = 0, + THC_DUAL_IO = 1, + THC_QUAD_IO = 2, + THC_QUAD_PARALLEL_IO = 3, +}; + +/** + * THC SPI frequency divider + * + * This DIV final value is determined by THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN bit. + * If THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN isn't be set, THC takes the DIV value directly; + * If THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN is set, THC takes the DIV value multiply by 8. + * + * For example, if THC input clock is 125MHz: + * When THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN isn't set, THC_SPI_FRQ_DIV_3 means DIV is 3, + * THC final clock is 125 / 3 = 41.667MHz; + * When THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN is set, THC_SPI_FRQ_DIV_3 means DIV is 3 * 8, + * THC final clock is 125 / (3 * 8) = 5.208MHz; + */ +enum thc_spi_frq_div { + THC_SPI_FRQ_RESERVED = 0, + THC_SPI_FRQ_DIV_1 = 1, + THC_SPI_FRQ_DIV_2 = 2, + THC_SPI_FRQ_DIV_3 = 3, + THC_SPI_FRQ_DIV_4 = 4, + THC_SPI_FRQ_DIV_5 = 5, + THC_SPI_FRQ_DIV_6 = 6, + THC_SPI_FRQ_DIV_7 = 7, +}; + +/* THC I2C sub-system registers */ +#define THC_I2C_IC_CON_OFFSET 0x0 +#define THC_I2C_IC_TAR_OFFSET 0x4 +#define THC_I2C_IC_SAR_OFFSET 0x8 +#define THC_I2C_IC_HS_MADDR_OFFSET 0xC +#define THC_I2C_IC_DATA_CMD_OFFSET 0x10 +#define THC_I2C_IC_SS_SCL_HCNT_OFFSET 0x14 +#define THC_I2C_IC_UFM_SCL_HCNT_OFFSET 0x14 +#define THC_I2C_IC_SS_SCL_LCNT_OFFSET 0x18 +#define THC_I2C_IC_UFM_SCL_LCNT_OFFSET 0x18 +#define THC_I2C_IC_FS_SCL_HCNT_OFFSET 0x1C +#define THC_I2C_IC_UFM_TBUF_CNT_OFFSET 0x1C +#define THC_I2C_IC_FS_SCL_LCNT_OFFSET 0x20 +#define THC_I2C_IC_HS_SCL_HCNT_OFFSET 0x24 +#define THC_I2C_IC_HS_SCL_LCNT_OFFSET 0x28 +#define THC_I2C_IC_INTR_STAT_OFFSET 0x2C +#define THC_I2C_IC_INTR_MASK_OFFSET 0x30 +#define THC_I2C_IC_RAW_INTR_STAT_OFFSET 0x34 +#define THC_I2C_IC_RX_TL_OFFSET 0x38 +#define THC_I2C_IC_TX_TL_OFFSET 0x3C +#define THC_I2C_IC_CLR_INTR_OFFSET 0x40 +#define THC_I2C_IC_CLR_RX_UNDER_OFFSET 0x44 +#define THC_I2C_IC_CLR_RX_OVER_OFFSET 0x48 +#define THC_I2C_IC_CLR_TX_OVER_OFFSET 0x4C +#define THC_I2C_IC_CLR_RD_REQ_OFFSET 0x50 +#define THC_I2C_IC_CLR_TX_ABRT_OFFSET 0x54 +#define THC_I2C_IC_CLR_RX_DONE_OFFSET 0x58 +#define THC_I2C_IC_CLR_ACTIVITY_OFFSET 0x5C +#define THC_I2C_IC_CLR_STOP_DET_OFFSET 0x60 +#define THC_I2C_IC_CLR_START_DET_OFFSET 0x64 +#define THC_I2C_IC_CLR_GEN_CALL_OFFSET 0x68 +#define THC_I2C_IC_ENABLE_OFFSET 0x6C +#define THC_I2C_IC_STATUS_OFFSET 0x70 +#define THC_I2C_IC_TXFLR_OFFSET 0x74 +#define THC_I2C_IC_RXFLR_OFFSET 0x78 +#define THC_I2C_IC_SDA_HOLD_OFFSET 0x7C +#define THC_I2C_IC_TX_ABRT_SOURCE_OFFSET 0x80 +#define THC_I2C_IC_SLV_DATA_NACK_ONLY_OFFSET 0x84 +#define THC_I2C_IC_DMA_CR_OFFSET 0x88 +#define THC_I2C_IC_DMA_TDLR_OFFSET 0x8C +#define THC_I2C_IC_DMA_RDLR_OFFSET 0x90 +#define THC_I2C_IC_SDA_SETUP_OFFSET 0x94 +#define THC_I2C_IC_ACK_GENERAL_CALL_OFFSET 0x98 +#define THC_I2C_IC_ENABLE_STATUS_OFFSET 0x9C +#define THC_I2C_IC_FS_SPKLEN_OFFSET 0xA0 +#define THC_I2C_IC_UFM_SPKLEN_OFFSET 0xA0 +#define THC_I2C_IC_HS_SPKLEN_OFFSET 0xA4 +#define THC_I2C_IC_CLR_RESTART_DET_OFFSET 0xA8 +#define THC_I2C_IC_SCL_STUCK_AT_LOW_TIMEOUT_OFFSET 0xAC +#define THC_I2C_IC_SDA_STUCK_AT_LOW_TIMEOUT_OFFSET 0xB0 +#define THC_I2C_IC_CLR_SCL_STUCK_DET_OFFSET 0xB4 +#define THC_I2C_IC_DEVICE_ID_OFFSET 0xB8 +#define THC_I2C_IC_SMBUS_CLK_LOW_SEXT_OFFSET 0xBC +#define THC_I2C_IC_SMBUS_CLK_LOW_MEXT_OFFSET 0xC0 +#define THC_I2C_IC_SMBUS_THIGH_MAX_IDLE_COUNT_OFFSET 0xC4 +#define THC_I2C_IC_SMBUS_INTR_STAT_OFFSET 0xC8 +#define THC_I2C_IC_SMBUS_INTR_MASK_OFFSET 0xCC +#define THC_I2C_IC_SMBUS_RAW_INTR_STAT_OFFSET 0xD0 +#define THC_I2C_IC_CLR_SMBUS_INTR_OFFSET 0xD4 +#define THC_I2C_IC_OPTIONAL_SAR_OFFSET 0xD8 +#define THC_I2C_IC_SMBUS_UDID_LSB_OFFSET 0xDC +#define THC_I2C_IC_SMBUS_UDID_WORD0_OFFSET 0xDC +#define THC_I2C_IC_SMBUS_UDID_WORD1_OFFSET 0xE0 +#define THC_I2C_IC_SMBUS_UDID_WORD2_OFFSET 0xE4 +#define THC_I2C_IC_SMBUS_UDID_WORD3_OFFSET 0xE8 +#define THC_I2C_IC_COMP_PARAM_1_OFFSET 0xF4 +#define THC_I2C_IC_COMP_VERSION_OFFSET 0xF8 +#define THC_I2C_IC_COMP_TYPE_OFFSET 0xFC + +/** + * THC I2C sub-system supported speed mode + */ +enum THC_I2C_SPEED_MODE { + THC_I2C_STANDARD = 1, + THC_I2C_FAST_AND_PLUS = 2, + THC_I2C_HIGH_SPEED = 3, +}; + +/* THC I2C sub-system register bits definition */ +#define THC_I2C_IC_ENABLE_ENABLE BIT(0) +#define THC_I2C_IC_ENABLE_ABORT BIT(1) +#define THC_I2C_IC_ENABLE_TX_CMD_BLOCK BIT(2) +#define THC_I2C_IC_ENABLE_SDA_STUCK_RECOVERY_ENABLE BIT(3) +#define THC_I2C_IC_ENABLE_SMBUS_CLK_RESET BIT(16) +#define THC_I2C_IC_ENABLE_SMBUS_SUSPEND_EN BIT(17) +#define THC_I2C_IC_ENABLE_SMBUS_ALERT_EN BIT(18) + +#define THC_I2C_IC_CON_MASTER_MODE BIT(0) +#define THC_I2C_IC_CON_SPEED GENMASK(2, 1) +#define THC_I2C_IC_CON_IC_10BITADDR_SLAVE BIT(3) +#define THC_I2C_IC_CON_IC_10BITADDR_MASTER BIT(4) +#define THC_I2C_IC_CON_IC_RESTART_EN BIT(5) +#define THC_I2C_IC_CON_IC_SLAVE_DISABLE BIT(6) +#define THC_I2C_IC_CON_STOP_DET_IFADDRESSED BIT(7) +#define THC_I2C_IC_CON_TX_EMPTY_CTRL BIT(8) +#define THC_I2C_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(9) +#define THC_I2C_IC_CON_STOP_DET_IF_MASTER_ACTIVE BIT(10) +#define THC_I2C_IC_CON_BUS_CLEAR_FEATURE_CTRL BIT(11) +#define THC_I2C_IC_CON_OPTIONAL_SAR_CTRL BIT(16) +#define THC_I2C_IC_CON_SMBUS_SLAVE_QUICK_EN BIT(17) +#define THC_I2C_IC_CON_SMBUS_ARP_EN BIT(18) +#define THC_I2C_IC_CON_SMBUS_PERSISTENT_SLV_ADDR_EN BIT(19) + +#define THC_I2C_IC_TAR_IC_TAR GENMASK(9, 0) +#define THC_I2C_IC_TAR_GC_OR_START BIT(10) +#define THC_I2C_IC_TAR_SPECIAL BIT(11) +#define THC_I2C_IC_TAR_IC_10BITADDR_MASTER BIT(12) +#define THC_I2C_IC_TAR_DEVICE_ID BIT(13) +#define THC_I2C_IC_TAR_SMBUS_QUICK_CMD BIT(16) + +#define THC_I2C_IC_INTR_MASK_M_RX_UNDER BIT(0) +#define THC_I2C_IC_INTR_MASK_M_RX_OVER BIT(1) +#define THC_I2C_IC_INTR_MASK_M_RX_FULL BIT(2) +#define THC_I2C_IC_INTR_MASK_M_TX_OVER BIT(3) +#define THC_I2C_IC_INTR_MASK_M_TX_EMPTY BIT(4) +#define THC_I2C_IC_INTR_MASK_M_RD_REQ BIT(5) +#define THC_I2C_IC_INTR_MASK_M_TX_ABRT BIT(6) +#define THC_I2C_IC_INTR_MASK_M_RX_DONE BIT(7) +#define THC_I2C_IC_INTR_MASK_M_ACTIVITY BIT(8) +#define THC_I2C_IC_INTR_MASK_M_STOP_DET BIT(9) +#define THC_I2C_IC_INTR_MASK_M_START_DET BIT(10) +#define THC_I2C_IC_INTR_MASK_M_GEN_CALL BIT(11) +#define THC_I2C_IC_INTR_MASK_M_RESTART_DET BIT(12) +#define THC_I2C_IC_INTR_MASK_M_MASTER_ON_HOLD BIT(13) +#define THC_I2C_IC_INTR_MASK_M_SCL_STUCK_AT_LOW BIT(14) + +#define THC_I2C_IC_DMA_CR_RDMAE BIT(0) +#define THC_I2C_IC_DMA_CR_TDMAE BIT(1) + +#define THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD GENMASK(15, 0) +#define THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD GENMASK(23, 16) + +#endif /* _INTEL_THC_HW_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.c new file mode 100644 index 000000000..1291b4ea2 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Intel Corporation */ + +#include +#include + +#include "intel-thc-dev.h" +#include "intel-thc-wot.h" + +/** + * thc_wot_config - Query and configure wake-on-touch feature + * @thc_dev: Point to thc_device structure + * @gpio_map: Point to ACPI GPIO resource mapping structure + * + * THC ACPI device only provides _CRS with GpioInt() resources, doesn't contain + * _DSD to map this GPIO resource, so this function first registers wake GPIO + * mapping manually, then queries wake-on-touch GPIO resource from ACPI, + * if it exists and is wake-able, configure driver to enable it, otherwise, + * return immediately. + * This function will not return error as it doesn't impact major function. + */ +void thc_wot_config(struct thc_device *thc_dev, const struct acpi_gpio_mapping *gpio_map) +{ + struct acpi_device *adev; + struct thc_wot *wot; + int ret; + + if (!thc_dev) + return; + + adev = ACPI_COMPANION(thc_dev->dev); + if (!adev) + return; + + wot = &thc_dev->wot; + + ret = acpi_dev_add_driver_gpios(adev, gpio_map); + if (ret) { + dev_warn(thc_dev->dev, "Can't add wake GPIO resource, ret = %d\n", ret); + return; + } + + wot->gpio_irq = acpi_dev_gpio_irq_wake_get_by(adev, "wake-on-touch", 0, + &wot->gpio_irq_wakeable); + if (wot->gpio_irq <= 0) { + dev_warn(thc_dev->dev, "Can't find wake GPIO resource\n"); + return; + } + + if (!wot->gpio_irq_wakeable) { + dev_warn(thc_dev->dev, "GPIO resource isn't wakeable\n"); + return; + } + + ret = device_init_wakeup(thc_dev->dev, true); + if (ret) { + dev_warn(thc_dev->dev, "Failed to init wake up.\n"); + return; + } + + ret = dev_pm_set_dedicated_wake_irq(thc_dev->dev, wot->gpio_irq); + if (ret) { + dev_warn(thc_dev->dev, "Failed to set wake up IRQ.\n"); + device_init_wakeup(thc_dev->dev, false); + } +} +EXPORT_SYMBOL_NS_GPL(thc_wot_config, "INTEL_THC"); + +/** + * thc_wot_unconfig - Unconfig wake-on-touch feature + * @thc_dev: Point to thc_device structure + * + * Configure driver to disable wake-on-touch and release ACPI resource. + */ +void thc_wot_unconfig(struct thc_device *thc_dev) +{ + struct acpi_device *adev; + + if (!thc_dev) + return; + + adev = ACPI_COMPANION(thc_dev->dev); + if (!adev) + return; + + if (thc_dev->wot.gpio_irq_wakeable) + device_init_wakeup(thc_dev->dev, false); + + if (thc_dev->wot.gpio_irq > 0) { + dev_pm_clear_wake_irq(thc_dev->dev); + acpi_dev_remove_driver_gpios(adev); + } +} +EXPORT_SYMBOL_NS_GPL(thc_wot_unconfig, "INTEL_THC"); diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.h new file mode 100644 index 000000000..6c700621b --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Intel Corporation */ + +#ifndef _INTEL_THC_WOT_H_ +#define _INTEL_THC_WOT_H_ + +#include + +#include + +/** + * struct thc_wot - THC Wake-on-Touch data structure + * @gpio_irq : GPIO interrupt IRQ number for wake-on-touch + * @gpio_irq_wakeable : Indicate GPIO IRQ workable or not + */ +struct thc_wot { + int gpio_irq; + bool gpio_irq_wakeable; +}; + +struct thc_device; + +void thc_wot_config(struct thc_device *thc_dev, const struct acpi_gpio_mapping *gpio_map); +void thc_wot_unconfig(struct thc_device *thc_dev); + +#endif /* _INTEL_THC_WOT_H_ */ -- cgit v1.3.1