diff options
Diffstat (limited to 'drivers/hid/intel-thc-hid/intel-quicki2c')
6 files changed, 1570 insertions, 0 deletions
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 000000000000..8a8c4a46f927 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -0,0 +1,960 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/irqreturn.h> +#include <linux/pci.h> +#include <linux/sizes.h> +#include <linux/pm_runtime.h> + +#include "intel-thc-dev.h" +#include "intel-thc-hw.h" + +#include "quicki2c-dev.h" +#include "quicki2c-hid.h" +#include "quicki2c-protocol.h" + +/* 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_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 failed. + */ +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 + * + * This function gets all quicki2c devices' ACPI resource. + * + * Return: 0 if success or error code on failed. + */ +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_slave_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_speed_mode = THC_I2C_STANDARD; + qcdev->i2c_clock_hcnt = i2c_config.SMHX; + qcdev->i2c_clock_lcnt = i2c_config.SMLX; + } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED && + i2c_param.connection_speed <= QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED) { + qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS; + qcdev->i2c_clock_hcnt = i2c_config.FMHX; + qcdev->i2c_clock_lcnt = i2c_config.FMLX; + } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED && + i2c_param.connection_speed <= QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED) { + qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS; + qcdev->i2c_clock_hcnt = i2c_config.FPHX; + qcdev->i2c_clock_lcnt = i2c_config.FPLX; + } 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_speed_mode = THC_I2C_HIGH_SPEED; + qcdev->i2c_clock_hcnt = i2c_config.HMHX; + qcdev->i2c_clock_lcnt = i2c_config.HMLX; + } else { + return -EOPNOTSUPP; + } + + return 0; +} + +/** + * quicki2c_irq_quick_handler - The ISR of the quicki2c driver + * + * @irq: The irq number + * @dev_id: pointer to the 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 - Try to recovery THC and Device + * @qcdev: pointer to quicki2c device + * + * This function is a error handler, called when fatal error happens. + * It try to reset Touch Device and re-configure THC to recovery + * transferring between Device and THC. + * + * Return: 0 if successful or error code on failed + */ +static int try_recover(struct quicki2c_device *qcdev) +{ + int ret; + + thc_dma_unconfigure(qcdev->thc_hw); + + ret = thc_dma_configure(qcdev->thc_hw); + if (ret) { + dev_err(qcdev->dev, "Reconfig DMA failed\n"); + return ret; + } + + return 0; +} + +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: + thc_interrupt_enable(qcdev->thc_hw, true); + + if (err_recover) + if (try_recover(qcdev)) + qcdev->state = QUICKI2C_DISABLED; + + pm_runtime_mark_last_busy(qcdev->dev); + 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 + * + * 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 failed. + */ +static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __iomem *mem_addr) +{ + 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; + + init_waitqueue_head(&qcdev->reset_ack_wq); + + /* thc hw 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_slave_addr, + qcdev->i2c_speed_mode, + qcdev->i2c_clock_hcnt, + qcdev->i2c_clock_lcnt); + 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); + + 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) +{ + thc_interrupt_enable(qcdev->thc_hw, false); + thc_ltr_unconfig(qcdev->thc_hw); + + qcdev->state = QUICKI2C_DISABLED; +} + +/** + * 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 failed. + */ +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; + } + + return ret; +} + +/** + * 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); +} + +/** + * 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 failed. + */ +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 failed. + */ +static int quicki2c_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + 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); + 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) { + dev_err_once(&pdev->dev, + "Failed to request threaded IRQ, irq = %d.\n", pdev->irq); + 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_mark_last_busy(qcdev->dev); + 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: 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_dma_deinit(qcdev); + + pm_runtime_get_noresume(qcdev->dev); + + quicki2c_dev_deinit(qcdev); + + pci_clear_master(pdev); +} + +/** + * quicki2c_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 quicki2c_shutdown(struct pci_dev *pdev) +{ + struct quicki2c_device *qcdev; + + qcdev = pci_get_drvdata(pdev); + if (!qcdev) + return; + + /* Must stop DMA before reboot to avoid DMA entering into unknown state */ + quicki2c_dma_deinit(qcdev); + + quicki2c_dev_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; + + /* + * 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; + + 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; + + 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; + + 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; + + 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; + + 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_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_slave_addr, + qcdev->i2c_speed_mode, + qcdev->i2c_clock_hcnt, + qcdev->i2c_clock_lcnt); + 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 0; +} + +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_VDEVICE(INTEL, THC_LNL_DEVICE_ID_I2C_PORT1), }, + {PCI_VDEVICE(INTEL, THC_LNL_DEVICE_ID_I2C_PORT2), }, + {PCI_VDEVICE(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT1), }, + {PCI_VDEVICE(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT2), }, + {PCI_VDEVICE(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT1), }, + {PCI_VDEVICE(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT2), }, + {} +}; +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 <xinpeng.sun@intel.com>"); +MODULE_AUTHOR("Even Xu <even.xu@intel.com>"); + +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 000000000000..6ddb584bd611 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _QUICKI2C_DEV_H_ +#define _QUICKI2C_DEV_H_ + +#include <linux/hid-over-i2c.h> +#include <linux/workqueue.h> + +#define THC_LNL_DEVICE_ID_I2C_PORT1 0xA848 +#define THC_LNL_DEVICE_ID_I2C_PORT2 0xA84A +#define THC_PTL_H_DEVICE_ID_I2C_PORT1 0xE348 +#define THC_PTL_H_DEVICE_ID_I2C_PORT2 0xE34A +#define THC_PTL_U_DEVICE_ID_I2C_PORT1 0xE448 +#define THC_PTL_U_DEVICE_ID_I2C_PORT2 0xE44A + +/* 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 + +/* + * 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; +} __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 + * + * 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; +}; + +struct device; +struct pci_dev; +struct thc_device; +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 + * @driver_data: point to quicki2c specific driver data + * @state: THC I2C device state + * @mem_addr: MMIO memory address + * @dev_desc: device descriptor for HIDI2C protocol + * @i2c_slave_addr: HIDI2C device slave address + * @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 + * @i2c_speed_mode: 0 - standard mode, 1 - fast mode, 2 - fast mode plus + * @i2c_clock_hcnt: I2C CLK high period time (unit in cycle count) + * @i2c_clock_lcnt: I2C CLK low period time (unit in cycle count) + * @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 + */ +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; + enum quicki2c_dev_state state; + + void __iomem *mem_addr; + + struct hidi2c_dev_descriptor dev_desc; + u8 i2c_slave_addr; + u16 hid_desc_addr; + + u32 active_ltr_val; + u32 low_power_ltr_val; + + u32 i2c_speed_mode; + u32 i2c_clock_hcnt; + u32 i2c_clock_lcnt; + + u8 *report_descriptor; + u8 *input_buf; + u8 *report_buf; + u32 report_len; + + wait_queue_head_t reset_ack_wq; + bool reset_ack; +}; + +#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 000000000000..5c3ec95bb3fd --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/pm_runtime.h> + +#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_mark_last_busy(qcdev->dev); + pm_runtime_put_autosuspend(qcdev->dev); + + return ret; +} + +static int quicki2c_hid_power(struct hid_device *hid, int lvl) +{ + return 0; +} + +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, +}; + +/** + * 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); + + 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 000000000000..e80df5f339fe --- /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 000000000000..f493df0d5dc4 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2024 Intel Corporation */ + +#include <linux/bitfield.h> +#include <linux/hid.h> +#include <linux/hid-over-i2c.h> + +#include "intel-thc-dev.h" +#include "intel-thc-dma.h" + +#include "quicki2c-dev.h" +#include "quicki2c-hid.h" +#include "quicki2c-protocol.h" + +static int quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, int cmd_len, + bool append_data_reg, u8 *data, int data_len, + u8 *write_buf, int write_buf_len) +{ + int 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; + + 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; + } + + if (data && data_len) { + __le16 len = cpu_to_le16(data_len + HIDI2C_LENGTH_LEN); + + memcpy(write_buf + offset, &len, HIDI2C_LENGTH_LEN); + offset += HIDI2C_LENGTH_LEN; + memcpy(write_buf + offset, data, data_len); + } + + return buf_len; +} + +static int quicki2c_encode_cmd(struct quicki2c_device *qcdev, u32 *cmd_buf, + u8 opcode, u8 report_type, u8 report_id) +{ + int 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, int buf_len) +{ + size_t write_buf_len; + int cmd_len, ret; + u32 cmd; + + cmd_len = quicki2c_encode_cmd(qcdev, &cmd, opcode, report_type, report_id); + + ret = quicki2c_init_write_buf(qcdev, cmd, cmd_len, buf ? true : false, buf, + buf_len, qcdev->report_buf, qcdev->report_len); + if (ret < 0) + return ret; + + write_buf_len = ret; + + return thc_dma_write(qcdev->thc_hw, qcdev->report_buf, write_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, u32 buf_len) +{ + struct hidi2c_report_packet *rpt; + size_t write_buf_len, read_len = 0; + int cmd_len, rep_type; + u32 cmd; + int ret; + + 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); + + ret = quicki2c_init_write_buf(qcdev, cmd, cmd_len, true, NULL, 0, + qcdev->report_buf, qcdev->report_len); + if (ret < 0) + return ret; + + write_buf_len = ret; + + rpt = (struct hidi2c_report_packet *)qcdev->input_buf; + + ret = thc_swdma_read(qcdev->thc_hw, qcdev->report_buf, write_buf_len, + NULL, rpt, &read_len); + if (ret) { + dev_err_once(qcdev->dev, "Get report failed, ret %d, read len (%zu vs %d)\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 %d) 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, u32 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; +} + +#define HIDI2C_RESET_TIMEOUT 5 + +int quicki2c_reset(struct quicki2c_device *qcdev) +{ + 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 (ret <= 0 || !qcdev->reset_ack) { + dev_err_once(qcdev->dev, + "Wait reset response timed out ret:%d timeout:%ds\n", + ret, HIDI2C_RESET_TIMEOUT); + return -ETIMEDOUT; + } + + 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 000000000000..bf4908cce59c --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _QUICKI2C_PROTOCOL_H_ +#define _QUICKI2C_PROTOCOL_H_ + +#include <linux/hid-over-i2c.h> + +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, u32 buf_len); +int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type, + unsigned int reportnum, void *buf, u32 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_ */ |