From patchwork Thu Nov 14 05:33:56 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843291 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 156D91EABCF; Thu, 14 Nov 2024 05:34:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562471; cv=none; b=efH593uTFvE5T7Z3Eg62P3uzkcYCiEUtlpJ2jymxsqv+A/TKv+gUuWeUd4o1bGKX0zbSmcjuQuMyZDuxZ897G1nC82Yhlsx+lwfQ+rHvmCDNF39smhxqs9PoWpbqRDpnPEj4lkPdHeiuX18YRjceCunQt8hNkVkc7Iw5F838vSs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562471; c=relaxed/simple; bh=3FLKq+RX4sFgvKHrVb2rJzGoRuSMBDAsjsN/GR6gelE=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=hVKOq63eQtOU0XKQCuA+Po/ejYlvd6lO2mTWOcqXW4QvLYWU7YtForCfHlwHp0MOZhlFxKWqtD/b/huK4v/nCm4wXdD9VPX55sBOb7t7EcJ408iGXQa+ssNGx3ORvH50HARDgPi+sBBtR3s5zFmq9xdTHkJ6+mxXWitMsv6wXSA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=D8p7JqGw; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="D8p7JqGw" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562469; x=1763098469; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=3FLKq+RX4sFgvKHrVb2rJzGoRuSMBDAsjsN/GR6gelE=; b=D8p7JqGwsgSjLch/Pl1Binylc9PSjnIlEmUIXY8gMMY0vE1iCfXHAx/V qXor6rvUbKUuDzM84gycFGX1En9T9Le5suHNHhNDTdhhTdKRWmYWGz9ES cscAT78QZD/z7Z0ehkn4GoNVH7qMv6LMzlQpAU5CchLd4yc+34ptzoaHk +4/nq4H7CYk2xY6b829N9Tls9d53I3bJK0yXrh4PUV4ynhxkZ44nKorgy IvbwCaTscXwHOHYVt7KmCGrr8DOeuBWTRG+fbzfUt/QF4pd0BAWYtnuf5 ryfL1Xa2280sw5+vgLYn2peJxM/9GHCWelMe5058aXVw0GkzGROV8DWnX A==; X-CSE-ConnectionGUID: LREOzdQzQQap1eKYf5YjTw== X-CSE-MsgGUID: ClwcVoaaTZ6cAOafbo4EsQ== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869011" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869011" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:34:28 -0800 X-CSE-ConnectionGUID: sUGSUknTReC6Pifwl7gM2Q== X-CSE-MsgGUID: 14G8NDNGSY6p4uIhoLo5AQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891439" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:34:25 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Xinpeng Sun , Even Xu , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 02/22] HID: intel-thc-hid: Add basic THC driver skeleton Date: Thu, 14 Nov 2024 13:33:56 +0800 Message-Id: <20241114053416.4085715-3-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Xinpeng Sun Create intel-thc-hid folder and add Kconfig and Makefile for THC drivers. Add basic THC device context structure, definitions and related initialization APIs for THC Hardware layer driver. Also initialize regmap struct for future THC registers access. Co-developed-by: Even Xu Signed-off-by: Even Xu Signed-off-by: Xinpeng Sun Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- MAINTAINERS | 6 ++ drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/intel-thc-hid/Kconfig | 20 +++++ drivers/hid/intel-thc-hid/Makefile | 11 +++ .../intel-thc-hid/intel-thc/intel-thc-dev.c | 87 +++++++++++++++++++ .../intel-thc-hid/intel-thc/intel-thc-dev.h | 25 ++++++ 7 files changed, 153 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-thc/intel-thc-dev.c create mode 100644 drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h diff --git a/MAINTAINERS b/MAINTAINERS index cc40a9d9b8cd..48cb8b0c8acb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11597,6 +11597,12 @@ S: Maintained F: arch/x86/include/asm/intel_telemetry.h F: drivers/platform/x86/intel/telemetry/ +INTEL TOUCH HOST CONTROLLER (THC) DRIVER +M: Even Xu +M: Xinpeng Sun +S: Maintained +F: drivers/hid/intel-thc-hid/ + INTEL TPMI DRIVER M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 08446c89eff6..ebe3fe349cad 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1367,4 +1367,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" source "drivers/hid/surface-hid/Kconfig" +source "drivers/hid/intel-thc-hid/Kconfig" + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index e40f1ddebbb7..fae42e2f75cf 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -169,3 +169,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + +obj-$(CONFIG_INTEL_THC_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 000000000000..e7e8201287f8 --- /dev/null +++ b/drivers/hid/intel-thc-hid/Kconfig @@ -0,0 +1,20 @@ +# 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" + select HID + help + THC (Touch Host Controller) is the name of the IP block in PCH that + interface 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 HW sequencer with RW DMA capability to + system memory. + + Say Y/M here if you want to support Intel THC. 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 000000000000..bebb60bedfeb --- /dev/null +++ b/drivers/hid/intel-thc-hid/Makefile @@ -0,0 +1,11 @@ +# 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 + +ccflags-y += -I $(src)/intel-thc 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 000000000000..d77603e4c01e --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include + +#include "intel-thc-dev.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_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); + } + + return thc_dev; +} +EXPORT_SYMBOL_NS_GPL(thc_dev_init, 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 000000000000..7060f0a36cbd --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#ifndef _INTEL_THC_DEV_H_ +#define _INTEL_THC_DEV_H_ + +#include + +#define THC_REGMAP_COMMON_OFFSET 0x10 +#define THC_REGMAP_MMIO_OFFSET 0x1000 + +/** + * struct thc_device - THC private device struct + * @thc_regmap: MMIO regmap structure for accessing THC registers + * @mmio_addr: MMIO registers address + */ +struct thc_device { + struct device *dev; + struct regmap *thc_regmap; + void __iomem *mmio_addr; +}; + +struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr); + +#endif /* _INTEL_THC_DEV_H_ */ From patchwork Thu Nov 14 05:33:58 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843290 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C28BB1EE021; Thu, 14 Nov 2024 05:34:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562477; cv=none; b=Jk/znIKeEqvV9n3ZlHlcteejRMErAhDdbj+f7xM/xXJdNs82/QXwSP3wW2kYs+XyTuV9jx3UFEBL7q9G9PNOurRIB+1MnGVRIiSIAsEQOPhV6e3vK4sAQ1IFwwYVv2CgCpa78I94tGQS5YwmP75DE0sUBxf/uDy8hF8KXweplTI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562477; c=relaxed/simple; bh=1XJsBS1LT3ck3XELtE6Cfnyf3BM6hYOMjaKwmJkmQFw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=b+dBNHMOkWfMZXZ8pjqcl0yMDYMZ/YDKGWUL3MQrD3rnbaYQQ0dILY1hqmfhq8YmHDGg6r9dcngCnOrjZnId/X1X6uEN25tmVKyEdQ19MPPH9XRvA8qxpCjPrA9/zhk6Vart9mlgQUn6vCgbg0aDjUJ6u4LVchQ2dQYHdf5etS8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=C2uDhvq9; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="C2uDhvq9" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562475; x=1763098475; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=1XJsBS1LT3ck3XELtE6Cfnyf3BM6hYOMjaKwmJkmQFw=; b=C2uDhvq9g+NmTmIfdETlb3UaGB/1NVlSLJn7qW7SxAnYbVFsXystmY/4 f0C5gzXkVHQYA+t86JtZ02kXpFeXihuPE1NRmUCiHucPRaXeA4K7dGwXa kaOB1Rbx1/PPDfQM737R7PZky488bNlsDYd8fVhyCqgezZTrMKacIrQut UfPmLQj1qXbeCDE9kx/teVTnDD45fEbODoUp31p+pGYuA+n8Kjp6rtChj 5THkYXlmXXpD9nT4aUnptwAY9d1BD1z7bsgSCfV3NJLOOU8UWRnnHFVnv Sge7MdWbqqwpkXYnpHdo8zqa2WzVYJtV8pxFGUBFi0o6oU2x9hCdCnUJO A==; X-CSE-ConnectionGUID: D3rB9KQDRDibGq05Vh3qdQ== X-CSE-MsgGUID: qs4fIHk4TOukqS+0pb8r3Q== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869029" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869029" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:34:35 -0800 X-CSE-ConnectionGUID: 6IFlZn+TRAi8fkoVOkxF6A== X-CSE-MsgGUID: cefJEbzSSNydsPVX122YMw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891475" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:34:32 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Xinpeng Sun , Even Xu , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 04/22] HID: intel-thc-hid: intel-thc: Add THC PIO operation APIs Date: Thu, 14 Nov 2024 13:33:58 +0800 Message-Id: <20241114053416.4085715-5-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Xinpeng Sun THC PIO (programmed I/O) operations are very similar with general SPI/I2C read/write operation to access external slave device on the bus through internal FIFO. THC PIO operations are split into 4 steps: 1. prepare: configure hardware with correct opcode, slave address, and fill the PIO FIFO 2. start: set start bit to issue a bus send/receive 3. wait: wait for bus sending/receiving completion 4. complete: check send/receive data in FIFO and return Co-developed-by: Even Xu Signed-off-by: Even Xu Signed-off-by: Xinpeng Sun Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- .../intel-thc-hid/intel-thc/intel-thc-dev.c | 245 ++++++++++++++++++ .../intel-thc-hid/intel-thc/intel-thc-dev.h | 24 ++ .../intel-thc-hid/intel-thc/intel-thc-hw.h | 23 ++ 3 files changed, 292 insertions(+) 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 index d77603e4c01e..0571b8270716 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -1,9 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (c) 2024 Intel Corporation */ +#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) @@ -76,10 +78,253 @@ struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr) return ERR_PTR(ret); } + mutex_init(&thc_dev->thc_bus_lock); + 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); + MODULE_AUTHOR("Xinpeng Sun "); MODULE_AUTHOR("Even Xu "); 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 index 7060f0a36cbd..88a9f606a6a9 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h @@ -5,21 +5,45 @@ #define _INTEL_THC_DEV_H_ #include +#include #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, +}; + /** * 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 */ 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_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); #endif /* _INTEL_THC_DEV_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 index 77b275a400d5..9f5f39dcedcd 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h @@ -633,4 +633,27 @@ #define THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN GENMASK(23, 0) +#define THC_REGMAP_POLLING_INTERVAL_US 10 /* 10us */ +#define THC_PIO_DONE_TIMEOUT_US USEC_PER_SEC /* 1s */ + +/* + * 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, +}; + #endif /* _INTEL_THC_HW_H_ */ From patchwork Thu Nov 14 05:34:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843289 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 30ECB1F4FB7; Thu, 14 Nov 2024 05:34:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562485; cv=none; b=ZpztnUN36ejihMB5OjXAQ1KivPWctbXyU2vCxKzuGc2AW/KlRZnmFk5hcm+aj5TeGukJQ2QjuggGrOItbovnb3v9JZPYmzrnTpUQ8WCKSsbHRvYc1hBst9D1Ba6MYOEX69qxAzhuT2BeiLrLmjqhDTgiwOmS6sm4G1eeu84JqAQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562485; c=relaxed/simple; bh=LCX6pqld3GpFcZpbuiFqeTaW7bjzEEDW2PKPkD9m0Kc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=PnKBNrJpjKfcD5x4O6YiEGHm6G2MVTBRuc7Rvw8ZAl2ZfJx5qsUqbWYxYISow9a/EEpOhxvF/hGt3u30ZVQR9WKSD9bH+4cyisrWgx2XTYintkBOmrFk2Ogu2YTsdkRvqYy5qHBHPh2axKNVdP9oAVABOnbWKGWnTmGJuSvpJ9E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=PdW4Qkev; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="PdW4Qkev" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562482; x=1763098482; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=LCX6pqld3GpFcZpbuiFqeTaW7bjzEEDW2PKPkD9m0Kc=; b=PdW4QkevfQudw5sZdPgFuDjpct/XKe+19j/xTvAShL1N9lyHN30iMUWa DhlCt+dS/pXjSQecdsgv74uGHcS73WZQcPTgvTq+wM+oWFGy2TxI2PNz0 yMKzb5/4Kbog1eqPmAgAvwEhtzANIDJZXuIWBovwkNRmZyxiIvKVOjljC zsczdQUT1QlXdRg6m5s9fQe3Sj1VKZnV2bJEYH9umWpcATKvbhnss7J5A Hww/M4MaFuY1ahPyjm9lfyouxYS/j9uAmyEiFpIOi90ASUTmVfrt7Fxta uQwocIQlkimeC2L2tVqQsQ9vlzJOZdB3I292w6hpu/OFbMX1dTNhgQ7Dr g==; X-CSE-ConnectionGUID: QdZCUh+FRNeTbAqull/h4g== X-CSE-MsgGUID: 71H1osMxTJSPObU8flooDA== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869041" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869041" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:34:41 -0800 X-CSE-ConnectionGUID: 1aDFWa+OT1S5w/eF5DXSxw== X-CSE-MsgGUID: tsZpIjD7Q6yI7uvt8M5sAQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891521" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:34:38 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Even Xu , Xinpeng Sun , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 06/22] HID: intel-thc-hid: intel-thc: Add THC DMA interfaces Date: Thu, 14 Nov 2024 13:34:00 +0800 Message-Id: <20241114053416.4085715-7-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 As THC PIO only has 64 bytes FIFO length, THC DMAs are introduced to send/recevie large data packets. THC has three types of DMA channels: Read DMA channel (RxDMA), Write DMA channel (TxDMA) and Software DMA (SWDMA). In addition to basic DMA functions, THC RxDMA also includes an auto hardware sequence which can handle external touch device's interrupt automatically without software involved. THC RxDMA channel usually is used for handling touch input reports. THC TxDMA is very similar with general IO TxDMA, and usually is used for sending command/request to exteranl touch device. THC SWDMA can perform read, write followed by read operation according to different configurations. Unlike RxDMA triggered by bus activity, SWDMA can be triggered by SW driver at any time, for example: - Retrieving an input report without interrupt - Sending command followed by reading response THC DMA operation flow includes 4 steps: 1. Allocate DMA buffers 2. Configure opcode, fill PRD table with DMA buffers, enable DMA channel 3. Wait for completion, read out DMA buffers and update buffer pointers 4. Stop DMA and release DMA buffers THC Hardware layer driver provides APIs for all above DMA Steps. Co-developed-by: Xinpeng Sun Signed-off-by: Xinpeng Sun Signed-off-by: Even Xu Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- drivers/hid/intel-thc-hid/Makefile | 1 + .../intel-thc-hid/intel-thc/intel-thc-dev.c | 8 + .../intel-thc-hid/intel-thc/intel-thc-dev.h | 18 + .../intel-thc-hid/intel-thc/intel-thc-dma.c | 969 ++++++++++++++++++ .../intel-thc-hid/intel-thc/intel-thc-dma.h | 146 +++ 5 files changed, 1142 insertions(+) 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 diff --git a/drivers/hid/intel-thc-hid/Makefile b/drivers/hid/intel-thc-hid/Makefile index bebb60bedfeb..b9709cc5e9eb 100644 --- a/drivers/hid/intel-thc-hid/Makefile +++ b/drivers/hid/intel-thc-hid/Makefile @@ -7,5 +7,6 @@ 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 ccflags-y += -I $(src)/intel-thc 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 index c898cd78c37b..06ca0e5ba7f0 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -79,6 +79,14 @@ struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr) } 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; } 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 index bef48c25c195..f775b972b4f5 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h @@ -6,6 +6,9 @@ #include #include +#include + +#include "intel-thc-dma.h" #define THC_REGMAP_COMMON_OFFSET 0x10 #define THC_REGMAP_MMIO_OFFSET 0x1000 @@ -27,6 +30,12 @@ enum thc_port_type { * @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 + * @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 swquence is done + * @perf_limit: the delay between read operation and write operation */ struct thc_device { struct device *dev; @@ -35,6 +44,15 @@ struct thc_device { struct mutex thc_bus_lock; enum thc_port_type port_type; bool pio_int_supported; + + struct thc_dma_context *dma_ctx; + + wait_queue_head_t write_complete_wait; + wait_queue_head_t swdma_complete_wait; + bool write_done; + bool swdma_done; + + u32 perf_limit; }; struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr); 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 000000000000..d9a3e6f5c47f --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c @@ -0,0 +1,969 @@ +/* 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, 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[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[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; +} + +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; + } + + 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); + + 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; + + 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 000000000000..ca923ff2bef9 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h @@ -0,0 +1,146 @@ +/* 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], hw 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: actual number of entries per sg 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[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; +}; + +/* + * THC DMA context + * Store all THC Channel configures + */ +struct thc_dma_context { + struct thc_dma_configuration dma_config[MAX_THC_DMA_CHANNEL]; + u8 use_write_interrupts; +}; + +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_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_ */ From patchwork Thu Nov 14 05:34:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843288 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 371801F6668; Thu, 14 Nov 2024 05:34:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562491; cv=none; b=DTlIL+eR15lLuwK+7Ai0S9A7gm9xWiPxkuvW/uwcV2qmh4WDZR7zbPVyT5ngmeVy58ieepZV6xkDnxeGZ8Z6kTwtOpHrpQBL9Kjam7x21JFqLr97S8upzg3cA6ISBFrKbLtIeYat+1SHk88n1p2AXFHS+mV991q6xXd6ZHjtTdg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562491; c=relaxed/simple; bh=jvezbM463w+twncyXBrYx2h5OuYd+2uQroULOWToELA=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=HlS+kxMoHJvNp8tiFHD1H0hOZPxsb7X8qE6LMVTvecJfPMVFhnHc3559IFUo9v73MbDAbP3/swevvtC920Hf3JcBz1Kw6F7pYHXt2J8nmwoT7FB1CK/3TKaOu6PKI/gX4L+ki/jH63gibFnnX1Bm5GsVFX65KhSWvwrvK855SEg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=ESQFQZQK; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="ESQFQZQK" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562489; x=1763098489; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=jvezbM463w+twncyXBrYx2h5OuYd+2uQroULOWToELA=; b=ESQFQZQKPd+dLr3CNqK6B4AGpzyG0zFfI2WsY2PEmU/tNE/EAmC9ayCc GDKH+gwXqqw+ZVkIAgOPluC/JJYwW5GORidkUEEEDPG9Uw4RE0wwn8mxM YjsghDMoOa4ECJAxYAA+WB6W5YyMEalviARDzx1+/e2AiJgnJx5RES2w5 FWERkTB5+KWyWIkaodmyxBsTX1hQ5GcePZpEI1WKvWrIXQYfwQzH5Izn5 iqt8qx8qpHs7DqS/Lpb82cAhUDyH8+SIgrklec7LbeTjba2lgIiINW/l3 ijXf8AMfaK1hJvMoKYwYRdewRer18N66NgZBELUXiwaAUKnPPZaPyq2GG w==; X-CSE-ConnectionGUID: xvXkzL25S6aWJB5TGs3yGA== X-CSE-MsgGUID: /BN3eZ3PSvKE3nT7O01IRQ== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869058" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869058" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:34:48 -0800 X-CSE-ConnectionGUID: nONI9vjtSVKFQBuukXtEXw== X-CSE-MsgGUID: Zs2HMxwwSte6yLpRvLPbUw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891548" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:34:45 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Xinpeng Sun , Even Xu , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 08/22] HID: intel-thc-hid: intel-thc: Add THC interrupt handler Date: Thu, 14 Nov 2024 13:34:02 +0800 Message-Id: <20241114053416.4085715-9-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Xinpeng Sun Add common interrupt handler to clear interrupt status and return interrupt type to caller for future handling. Co-developed-by: Even Xu Signed-off-by: Even Xu Signed-off-by: Xinpeng Sun Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- .../intel-thc-hid/intel-thc/intel-thc-dev.c | 259 ++++++++++++++++++ .../intel-thc-hid/intel-thc/intel-thc-dev.h | 27 ++ 2 files changed, 286 insertions(+) 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 index f2fb07f4092d..fb5d42cb1d44 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -670,6 +670,265 @@ void thc_ltr_unconfig(struct thc_device *dev) } 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); + MODULE_AUTHOR("Xinpeng Sun "); MODULE_AUTHOR("Even Xu "); 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 index c25f2fd57c76..d34fab243fdc 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h @@ -23,6 +23,31 @@ enum thc_port_type { 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_device - THC private device struct * @thc_regmap: MMIO regmap structure for accessing THC registers @@ -71,5 +96,7 @@ 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); #endif /* _INTEL_THC_DEV_H_ */ From patchwork Thu Nov 14 05:34:04 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843287 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 49EB61F7091; Thu, 14 Nov 2024 05:34:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562497; cv=none; b=SWAaCwpfdJpG065lMZTph2rZZfQBKhbvvD4hcS+GVfUzbQIHslASr+hCT6d8FpmIpKmMaGOZs55AqDvbU03uXCj9x86BxprFpNpfk0mpyOk1nhrSMm7d8oeZkEVGfqGyoj/Ah5bEg+jjOV17YKuYY0acS8LpSvY2iZgSvr3n9tc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562497; c=relaxed/simple; bh=RMz760zO9B3yH8o3G0EDF6gAV62euj26wxHD38EV8Rg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=m95p5pNf15wxWJe+1JDlUiE2H5tyFQBT+x0Sxg3G71x1Y01Ced6r9Rg/33R8QaXebW/N+DUdQ0seAvSRRWRoIb3l5wju0ZC4CJbLRK+urXzYXWobpuknAiwRwLkOfpLUiLokt+uXNJRoZymlBO7IAN2w4A2i1cjYG5CyZGRzOR0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=dlkE1AxO; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="dlkE1AxO" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562495; x=1763098495; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=RMz760zO9B3yH8o3G0EDF6gAV62euj26wxHD38EV8Rg=; b=dlkE1AxOVHJyU9XHX/+uCE5OuOiXu9UdpYjr6XjDe8skv0lEMsFyoymH +PKeixmG5zg+NafWR01rCL0t+F2wp4nsFIatNTgcVw3h1Usfzdt3s/YWT UJZ0UI01bugTL0Fnc6Zq4ewYQjUZ7fV234DIUgR14G9VWIODrDjd1udHu 2evUsXFkrIsXyQVDjULAbDFTQHK4JnzbCyPIogi/4dyoeWdojfO+7nb2C ASD4XFPiyjXXSoCZlh6Gz9/tl7HoSpXryCKxlyy9KWAno6znaDXOatSS3 gyufd95OGrnNATF4BWAUjNMpuwm4h02N8gyFfxTX2cTKFiRNG8D2dX/Jb w==; X-CSE-ConnectionGUID: 9KNPz/ulThSCEzjVBgzLHw== X-CSE-MsgGUID: oY488J+6S+qi44KmXV7DqQ== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869078" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869078" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:34:55 -0800 X-CSE-ConnectionGUID: fimF4EihTO+RUF+WozZw1Q== X-CSE-MsgGUID: jldhNYqfSFil28JzXWQuhA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891592" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:34:52 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Even Xu , Xinpeng Sun , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 10/22] HID: intel-thc-hid: intel-thc: Add THC I2C config interfaces Date: Thu, 14 Nov 2024 13:34:04 +0800 Message-Id: <20241114053416.4085715-11-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add I2C bus related APIs to initialize I2C sub-system parameters, such as bus speed, slave address, address bit. As I2C sub-system hasn't auto register save and restore support, provide APIs to do it manually. Co-developed-by: Xinpeng Sun Signed-off-by: Xinpeng Sun Signed-off-by: Even Xu Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- .../intel-thc-hid/intel-thc/intel-thc-dev.c | 281 ++++++++++++++++++ .../intel-thc-hid/intel-thc/intel-thc-dev.h | 7 + .../intel-thc-hid/intel-thc/intel-thc-hw.h | 129 ++++++++ 3 files changed, 417 insertions(+) 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 index 4f1f86b45513..51c2699edbb1 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -1158,6 +1158,287 @@ void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_a } 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_set_speed(struct thc_device *dev, const u32 speed, + const u32 hcnt, const u32 lcnt) +{ + u32 hcnt_offset, lcnt_offset; + u32 val; + int ret; + + switch (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", speed); + ret = -EINVAL; + return ret; + } + + ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &hcnt); + if (ret < 0) + return ret; + + ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &lcnt); + if (ret < 0) + return ret; + + val = I2C_SUBIP_CON_DEFAULT & ~THC_I2C_IC_CON_SPEED; + val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, speed); + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_CON_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_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 + * @target_address: Slave address of touch device (TIC) + * @speed: I2C bus frequency speed mode + * @hcnt: I2C clock SCL high count + * @lcnt: I2C clock SCL low count + * + * Return: 0 on success, other error codes on failed. + */ +int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address, + const u32 speed, const u32 hcnt, const u32 lcnt) +{ + u32 read_size = sizeof(u32); + u32 val; + int 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; + + 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; + val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, target_address); + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + ret = thc_i2c_subip_set_speed(dev, speed, hcnt, lcnt); + 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 PTR_ERR(dev->i2c_subip_regs); + + 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, (u32 *)&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, (u32 *)&dev->i2c_subip_regs + i); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_restore, INTEL_THC); + MODULE_AUTHOR("Xinpeng Sun "); MODULE_AUTHOR("Even Xu "); 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 index 925355f19bae..0517fee2c668 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h @@ -61,6 +61,7 @@ enum thc_int_type { * @write_done: bool value that indicates if DMA write is done * @swdma_done: bool value that indicates if SWDMA swquence 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 */ struct thc_device { struct device *dev; @@ -78,6 +79,8 @@ struct thc_device { bool swdma_done; u32 perf_limit; + + u32 *i2c_subip_regs; }; struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr); @@ -105,5 +108,9 @@ 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 u32 target_address, + const u32 speed, const u32 hcnt, const u32 lcnt); +int thc_i2c_subip_regs_save(struct thc_device *dev); +int thc_i2c_subip_regs_restore(struct thc_device *dev); #endif /* _INTEL_THC_DEV_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 index 985cad0cbe87..09a56e6852d2 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h @@ -746,4 +746,133 @@ enum thc_spi_frq_div { 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) + #endif /* _INTEL_THC_HW_H_ */ From patchwork Thu Nov 14 05:34:06 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843286 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 97EB01F76A3; Thu, 14 Nov 2024 05:35:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562503; cv=none; b=GHIGe7nWDOazzfKFpPZ/s2LeXecqDumX6wZMWFcU09o6M4RRlqqgR+pwl6IdA2wrQeoO9e9JyMNCSpCih47Jf2xXkNp9p/8V1fOcOHFwiTldgj2KUctGVE+pGPVG6TJcvOoYHENz3xjN0HOxmtE9pjuNPiqS5ZzEMts/rFgczGM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562503; c=relaxed/simple; bh=+/NltVkSFTTtiUQhqn0W1cki0vYuB7TObr/3B60AxuY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=WuUUk2J7Qshz6wJvsbxvr2dWaLS7+5H72SxYX7HNj2vAEtnyKm/KXyiA6OwbsxUFCrwevF2tDrixUQ1Sn709btMABX038peUvZHDv2NZpi+OIr4GybvTIMUvz0trtGidmH9TqAyduyK1RqFuCpxvg4VWpiwjLcEeZI8bRS6A3HA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=DplDN7Oz; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="DplDN7Oz" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562501; x=1763098501; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=+/NltVkSFTTtiUQhqn0W1cki0vYuB7TObr/3B60AxuY=; b=DplDN7OzskuMoYwxVqDfsDbB24zhm4cH8H1P7pnd8Ws9aaCyC7tWHWXA EGrFifUwxXA+5BJ6mjjlUZo9A0MpBy6NluhdaMHR93DL2X80vkCFtnYiQ dyP5JQKF72E/FmKfm3OmSqSpsbSxyJawR/Z50nUDqUxPD6PE9Mhs9QMDQ xV3+rMYZsO0AF8S/0dDm5dg9fBx/dT/7dyvof6Zz1Z8N5c+FV6fkQV9wl 8SSF2mCiAw4ySee4f9rjyhsFr5GKpj34zY+X0JvN8Vo5L//ZyfOjZ9GZc mTpjbb7QYTwJda5KW2vFrK996sa2RXdGCao6wxC7IqSY/k1+kBXFbxXim g==; X-CSE-ConnectionGUID: gT2RKV1ERZKmo9xGoVyVRg== X-CSE-MsgGUID: qLa6c5jFStamuwJMEzmDjA== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869094" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869094" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:35:01 -0800 X-CSE-ConnectionGUID: dwQ6wMoNRKq9DDHC5k1W6Q== X-CSE-MsgGUID: 68BLhlrXRu6ql2uSswGNIg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891615" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:34:58 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Even Xu , Xinpeng Sun , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 12/22] HID: intel-thc-hid: intel-quickspi: Add THC QuickSPI driver hid layer Date: Thu, 14 Nov 2024 13:34:06 +0800 Message-Id: <20241114053416.4085715-13-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add HID Low level driver callbacks and hid probe function to register QucikSPI as a HID driver, and external touch device as a HID device. Co-developed-by: Xinpeng Sun Signed-off-by: Xinpeng Sun Signed-off-by: Even Xu Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- drivers/hid/intel-thc-hid/Makefile | 1 + .../intel-quickspi/quickspi-dev.h | 11 ++ .../intel-quickspi/quickspi-hid.c | 142 ++++++++++++++++++ .../intel-quickspi/quickspi-hid.h | 14 ++ include/linux/hid-over-spi.h | 37 +++++ 5 files changed, 205 insertions(+) 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 include/linux/hid-over-spi.h diff --git a/drivers/hid/intel-thc-hid/Makefile b/drivers/hid/intel-thc-hid/Makefile index 7669739a39b7..4dd9e815021d 100644 --- a/drivers/hid/intel-thc-hid/Makefile +++ b/drivers/hid/intel-thc-hid/Makefile @@ -11,5 +11,6 @@ intel-thc-objs += intel-thc/intel-thc-dma.o obj-$(CONFIG_INTEL_QUICKSPI) += intel-quickspi.o intel-quickspi-objs += intel-quickspi/pci-quickspi.o +intel-quickspi-objs += intel-quickspi/quickspi-hid.o ccflags-y += -I $(src)/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 index cffc859efa19..335b32363d7f 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h @@ -4,6 +4,8 @@ #ifndef _QUICKSPI_DEV_H_ #define _QUICKSPI_DEV_H_ +#include + #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 @@ -38,24 +40,33 @@ struct quickspi_driver_data { struct device; struct pci_dev; struct thc_device; +struct hid_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 * @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 + * @report_descriptor: store a copy of device report descriptor */ struct quickspi_device { struct device *dev; struct pci_dev *pdev; struct thc_device *thc_hw; + struct hid_device *hid_dev; struct quickspi_driver_data *driver_data; enum quickspi_dev_state state; void __iomem *mem_addr; + + struct hidspi_dev_descriptor dev_desc; + + u8 *report_descriptor; }; #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 000000000000..eae53a749a38 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#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) +{ + return 0; +} + +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); + + 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 000000000000..f640fa876a40 --- /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/include/linux/hid-over-spi.h b/include/linux/hid-over-spi.h new file mode 100644 index 000000000000..ddbe41c5d8fd --- /dev/null +++ b/include/linux/hid-over-spi.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2024 Intel Corporation */ + +#ifndef _HID_OVER_SPI_H_ +#define _HID_OVER_SPI_H_ + +/** + * struct hidspi_dev_descriptor - HIDSPI device descriptor definition + * @dev_desc_len: The length of the complete device descriptor, fixed to 0x18 (24). + * @bcd_ver: The version number of the HIDSPI protocol supported. + * In binary coded decimal (BCD) format. Must be fixed to 0x0300. + * @rep_desc_len: The length of the report descriptor + * @max_input_len: The length of the largest possible HID input (or feature) report + * @max_output_len: The length of the largest output (or feature) report + * @max_frag_len: The length of the largest fragment, where a fragment represents + * the body of an input report. + * @vendor_id: Device manufacturers vendor ID + * @product_id: Device unique model/product ID + * @version_id: Device’s unique version + * @flags: Specify flags for the device’s operation + * @reserved: Reserved and should be 0 + */ +struct hidspi_dev_descriptor { + __le16 dev_desc_len; + __le16 bcd_ver; + __le16 rep_desc_len; + __le16 max_input_len; + __le16 max_output_len; + __le16 max_frag_len; + __le16 vendor_id; + __le16 product_id; + __le16 version_id; + __le16 flags; + __le32 reserved; +}; + +#endif /* _HID_OVER_SPI_H_ */ From patchwork Thu Nov 14 05:34:08 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843285 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 521431F77B3; Thu, 14 Nov 2024 05:35:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562510; cv=none; b=YRxA8y1QfIYLmuZVNmGYRJ5Ow2qaClVf9xfhXMFDCZwnYKXAqUJZWuKsb1O4zGNgLkrFNQVQGU4AU8NnSLGdBTcq5OLCx3TzubSCKQjhm07iwjViN3uiZctGLJFKjMFovOT3IiHsssB70X+G4AUa2qCnTr/O1Rq/iYSjVtHuyYo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562510; c=relaxed/simple; bh=1cJv5ad+rAF+o/Uz2Bf4qtnD1G1viDL9bsxSI3g18Q8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=i2N8glzHl97HqkNkg77LTYtO4uDxcBTNMJ5pMCqj8j9HVEBU25cQ/rV1IqPp+epVpkUWhHbO3vCsL5Tb+PtA4pTVGf3XPJd8Sowx4eCOKw29cil3qt5vU4oB5EPmoRgT11/+n73NGaRcMy3ol31Evg00kRI5/X96PC+gRPxK0bI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=GzgyYfBo; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="GzgyYfBo" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562508; x=1763098508; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=1cJv5ad+rAF+o/Uz2Bf4qtnD1G1viDL9bsxSI3g18Q8=; b=GzgyYfBoS3PUNm59kf3FwAkh8v2PHAM2nETxEbSoukiZcDXVJvWuEvz0 3hDruuR/aH4apDp+V+nzde2yHLhFlT8Xcs5P/XGq5ok+ZcM/fwF5qXEqJ z1Y/Fq5lDuYNz7Ru5S+DsqqOu8o1RXmRQ5FE3m4iJUPz8l4KG5VG9wyE7 vjdZsCfArnYCbGIf4BAgU1Bc8D7SSteQB3jk7MpzvLgufF6epyjvlY98G RjS4Gpa/sfwheMT7vP1mVGTuMPw50Ns2HD4Sx2IHuqxnVVJlQPLzisJRG CYhWu30Xb5haQ3mmHS5ESy9n4y3IBbFtrXD7iaDPCUWmA9kPticK8MyfC g==; X-CSE-ConnectionGUID: evb1PVRDRUeXl/EuXm38pg== X-CSE-MsgGUID: rWLdezGLQjycEkbTHj6M5w== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869112" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869112" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:35:08 -0800 X-CSE-ConnectionGUID: JM04VgV/Siq6vJB64QT3Lg== X-CSE-MsgGUID: BzWLcT+1SvOSJk85Ewf/yg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891635" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:35:04 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Even Xu , Xinpeng Sun , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 14/22] HID: intel-thc-hid: intel-quickspi: Add HIDSPI protocol implementation Date: Thu, 14 Nov 2024 13:34:08 +0800 Message-Id: <20241114053416.4085715-15-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Intel QuickSPI driver uses THC hardware to accelerate HID over SPI (HIDSPI) protocol flow. This patch implements all data flows described in HID over SPI protocol SPEC by using THC hardware layer APIs. HID over SPI SPEC: https://www.microsoft.com/download/details.aspx?id=103325 Co-developed-by: Xinpeng Sun Signed-off-by: Xinpeng Sun Signed-off-by: Even Xu Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- drivers/hid/intel-thc-hid/Makefile | 1 + .../intel-quickspi/quickspi-dev.h | 38 ++ .../intel-quickspi/quickspi-hid.c | 17 +- .../intel-quickspi/quickspi-protocol.c | 410 ++++++++++++++++++ .../intel-quickspi/quickspi-protocol.h | 25 ++ include/linux/hid-over-spi.h | 118 +++++ 6 files changed, 608 insertions(+), 1 deletion(-) 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 diff --git a/drivers/hid/intel-thc-hid/Makefile b/drivers/hid/intel-thc-hid/Makefile index 4dd9e815021d..2dba4db70c33 100644 --- a/drivers/hid/intel-thc-hid/Makefile +++ b/drivers/hid/intel-thc-hid/Makefile @@ -12,5 +12,6 @@ intel-thc-objs += intel-thc/intel-thc-dma.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 ccflags-y += -I $(src)/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 index deb24dd1a0fa..49b0322b9ebb 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h @@ -4,7 +4,12 @@ #ifndef _QUICKSPI_DEV_H_ #define _QUICKSPI_DEV_H_ +#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 @@ -92,6 +97,21 @@ struct acpi_device; * @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 descritpor + * @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 */ struct quickspi_device { struct device *dev; @@ -121,6 +141,24 @@ struct quickspi_device { u32 low_power_ltr_val; u8 *report_descriptor; + u8 *input_buf; + u8 *report_buf; + 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; }; #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 index eae53a749a38..2553d4b42b51 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c @@ -51,7 +51,22 @@ static int quickspi_hid_raw_request(struct hid_device *hid, __u8 *buf, size_t len, unsigned char rtype, int reqtype) { - return 0; + struct quickspi_device *qsdev = hid->driver_data; + int ret = 0; + + switch (reqtype) { + case HID_REQ_GET_REPORT: + ret = quickspi_get_report(qsdev, rtype, reportnum, buf); + 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; + } + + return ret; } static int quickspi_hid_power(struct hid_device *hid, int lvl) 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 000000000000..7d8ef603b949 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c @@ -0,0 +1,410 @@ +/* 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; + + 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; + } + + 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 intput 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: + dev_dbg(qsdev->dev, "Receive DIR reset response\n"); + /* TODO: to add device initiated reset flow */ + 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 (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; + + /* First interrupt uses level trigger to avoid missing interrupt */ + thc_int_trigger_type_select(qsdev->thc_hw, false); + + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); + if (ret) + return ret; + + 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; + } + + 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_RESETED; + + 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) +{ + int rep_type; + int ret; + + 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; + + memcpy(buf, qsdev->report_buf, qsdev->report_len); + + return qsdev->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 000000000000..775e29c1ed13 --- /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); +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/include/linux/hid-over-spi.h b/include/linux/hid-over-spi.h index ddbe41c5d8fd..da5a14b5e89b 100644 --- a/include/linux/hid-over-spi.h +++ b/include/linux/hid-over-spi.h @@ -4,6 +4,120 @@ #ifndef _HID_OVER_SPI_H_ #define _HID_OVER_SPI_H_ +#include +#include + +/* Input report type definition in HIDSPI protocol */ +enum input_report_type { + INVALID_INPUT_REPORT_TYPE_0 = 0, + DATA = 1, + INVALID_TYPE_2 = 2, + RESET_RESPONSE = 3, + COMMAND_RESPONSE = 4, + GET_FEATURE_RESPONSE = 5, + INVALID_TYPE_6 = 6, + DEVICE_DESCRIPTOR_RESPONSE = 7, + REPORT_DESCRIPTOR_RESPONSE = 8, + SET_FEATURE_RESPONSE = 9, + OUTPUT_REPORT_RESPONSE = 10, + GET_INPUT_REPORT_RESPONSE = 11, + INVALID_INPUT_REPORT_TYPE = 0xF, +}; + +/* Output report type definition in HIDSPI protocol */ +enum output_report_type { + INVALID_OUTPUT_REPORT_TYPE_0 = 0, + DEVICE_DESCRIPTOR = 1, + REPORT_DESCRIPTOR = 2, + SET_FEATURE = 3, + GET_FEATURE = 4, + OUTPUT_REPORT = 5, + GET_INPUT_REPORT = 6, + COMMAND_CONTENT = 7, +}; + +/* Set power command ID for output report */ +#define HIDSPI_SET_POWER_CMD_ID 1 + +/* Power state definition in HIDSPI protocol */ +enum hidspi_power_state { + HIDSPI_ON = 1, + HIDSPI_SLEEP = 2, + HIDSPI_OFF = 3, +}; + +/** + * Input report header definition in HIDSPI protocol + * Report header size is 32bits, it includes: + * protocol_ver: [0:3] Current supported HIDSPI protocol version, must be 0x3 + * reserved0: [4:7] Reserved bits + * input_report_len: [8:21] Input report length in number bytes divided by 4 + * last_frag_flag: [22]Indicate if this packet is last fragment. + * 1 - indicates last fragment + * 0 - indicates additional fragments + * reserved1: [23] Reserved bits + * @sync_const: [24:31] Used to validate input report header, must be 0x5A + */ +#define HIDSPI_INPUT_HEADER_SIZE sizeof(u32) +#define HIDSPI_INPUT_HEADER_VER GENMASK(3, 0) +#define HIDSPI_INPUT_HEADER_REPORT_LEN GENMASK(21, 8) +#define HIDSPI_INPUT_HEADER_LAST_FLAG BIT(22) +#define HIDSPI_INPUT_HEADER_SYNC GENMASK(31, 24) + +/** + * struct input_report_body_header - Input report body header definition in HIDSPI protocol + * @input_report_type: indicate input report type, reference to enum input_report_type + * @content_len: this input report body packet length + * @content_id: indicate this input report's report id + */ +struct input_report_body_header { + u8 input_report_type; + __le16 content_len; + u8 content_id; +} __packed; + +#define HIDSPI_INPUT_BODY_HEADER_SIZE sizeof(struct input_report_body_header) + +/** + * struct input_report_body - Input report body definition in HIDSPI protocol + * @body_hdr: input report body header + * @content: input report body content + */ +struct input_report_body { + struct input_report_body_header body_hdr; + u8 content[]; +} __packed; + +#define HIDSPI_INPUT_BODY_SIZE(content_len) ((content_len) + HIDSPI_INPUT_BODY_HEADER_SIZE) + +/** + * struct output_report_header - Output report header definition in HIDSPI protocol + * @report_type: output report type, reference to enum output_report_type + * @content_len: length of content + * @content_id: 0x00 - descriptors + * report id - Set/Feature feature or Input/Output Reports + * command opcode - for commands + */ +struct output_report_header { + u8 report_type; + __le16 content_len; + u8 content_id; +} __packed; + +#define HIDSPI_OUTPUT_REPORT_HEADER_SIZE sizeof(struct output_report_header) + +/** + * struct output_report - Output report definition in HIDSPI protocol + * @output_hdr: output report header + * @content: output report content + */ +struct output_report { + struct output_report_header output_hdr; + u8 content[]; +} __packed; + +#define HIDSPI_OUTPUT_REPORT_SIZE(content_len) ((content_len) + HIDSPI_OUTPUT_REPORT_HEADER_SIZE) + /** * struct hidspi_dev_descriptor - HIDSPI device descriptor definition * @dev_desc_len: The length of the complete device descriptor, fixed to 0x18 (24). @@ -34,4 +148,8 @@ struct hidspi_dev_descriptor { __le32 reserved; }; +#define HIDSPI_DEVICE_DESCRIPTOR_SIZE sizeof(struct hidspi_dev_descriptor) +#define HIDSPI_INPUT_DEVICE_DESCRIPTOR_SIZE \ + (HIDSPI_INPUT_BODY_HEADER_SIZE + HIDSPI_DEVICE_DESCRIPTOR_SIZE) + #endif /* _HID_OVER_SPI_H_ */ From patchwork Thu Nov 14 05:34:10 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843284 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C7C421F8189; Thu, 14 Nov 2024 05:35:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562516; cv=none; b=HSY6hzZuRM4VqQOb36/2ztQ3DkyANokmswsQ2yGrEv5kPaHqcI2AmkEraVE+MJGJHFO4eztn2XQHyhFXMSTL5AicdY4su8z2/1n25IemNvuiAsIXWU18A1VLHmP48RoBv9hhuuz/FqpJzwnXZHFyO0rUXHy66X4I6g8YMByXfVk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562516; c=relaxed/simple; bh=5yCUFzs3zN1FfZluvljTmuMj+BFmI1yA7vXRGfz2/eY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=kpbA/ZM5dQnLymQfl5wnEgHuo6TONKxinbPl7H+EmwFZYc8q1KW+g5qTXdhD+oWElbGpxmYl6Fvqo1dQ+tqLYS6Lcezsoc5zLTFVfs64UEIrgOIUOH5S8aGi368PapaOAvCwsHXxXH+Db7WjG08belHZvJLlX0QCFvPLM2KMWBM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=Kkcsaa8d; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="Kkcsaa8d" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562514; x=1763098514; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=5yCUFzs3zN1FfZluvljTmuMj+BFmI1yA7vXRGfz2/eY=; b=Kkcsaa8dC5+IQK5l1CL30DR0XVMUVFP1rl5vvZbRcih26iSLHyqWgJBo a1cGqmHtxLtVARog6xA25U5k2+GRc4Hbj9mhaNuXwvaS0NxR5nOdPn+Nx iffEWXfbh1cU91wpnk2y0dQ83F7jWWqjTWSxT7pYu09Y8R2r8/JkUhkVs ZXl6O7BIDlwmedSv2es3wJf+Mh9bEj/f24hh5hcWKkLEYuTNuF+e/V0Q+ woi/0psMZfU9sYM7D6oo+YR8/f3Pi26ALYdiNbWaJSl037037v1YWvGSj DKu10nmZmHMoSN/7Di8OOxNyP+cgOHLNYtHLr0CQ6MNXDpT4gCZlaD5ns A==; X-CSE-ConnectionGUID: omHAWzEsQC+xCtPlXrlYrw== X-CSE-MsgGUID: D5zBv/OERs+Sjk086OE21Q== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869127" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869127" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:35:14 -0800 X-CSE-ConnectionGUID: k8dRpgDHSTGTPUD0SKFv+g== X-CSE-MsgGUID: 1GuLbWFpSQOmbyDbNC3fPQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891682" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:35:11 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Even Xu , Xinpeng Sun , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 16/22] HID: intel-thc-hid: intel-quickspi: Add PM implementation Date: Thu, 14 Nov 2024 13:34:10 +0800 Message-Id: <20241114053416.4085715-17-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Implement THC QuickSPI driver power management callbacks. Co-developed-by: Xinpeng Sun Signed-off-by: Xinpeng Sun Signed-off-by: Even Xu Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- .../intel-quickspi/pci-quickspi.c | 246 ++++++++++++++++++ .../intel-quickspi/quickspi-dev.h | 8 + .../intel-quickspi/quickspi-hid.c | 8 + 3 files changed, 262 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c index 73be89933f1e..9c0df1163bcd 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "intel-thc-dev.h" #include "intel-thc-hw.h" @@ -281,6 +282,10 @@ static irqreturn_t quickspi_irq_thread_handler(int irq, void *dev_id) 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)) { @@ -318,6 +323,9 @@ static irqreturn_t quickspi_irq_thread_handler(int irq, void *dev_id) if (try_recover(qsdev)) qsdev->state = QUICKSPI_DISABLED; + pm_runtime_mark_last_busy(qsdev->dev); + pm_runtime_put_autosuspend(qsdev->dev); + return IRQ_HANDLED; } @@ -641,6 +649,13 @@ static int quickspi_probe(struct pci_dev *pdev, 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_mark_last_busy(qsdev->dev); + pm_runtime_put_noidle(qsdev->dev); + pm_runtime_put_autosuspend(qsdev->dev); + dev_dbg(&pdev->dev, "QuickSPI probe success\n"); return 0; @@ -677,6 +692,8 @@ static void quickspi_remove(struct pci_dev *pdev) quickspi_hid_remove(qsdev); quickspi_dma_deinit(qsdev); + pm_runtime_get_noresume(qsdev->dev); + quickspi_dev_deinit(qsdev); pcim_iounmap_regions(pdev, BIT(0)); @@ -707,6 +724,234 @@ static void quickspi_shutdown(struct pci_dev *pdev) quickspi_dev_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; + + ret = quickspi_set_power(qsdev, HIDSPI_SLEEP); + if (ret) + return ret; + + 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; + + thc_interrupt_config(qsdev->thc_hw); + + thc_interrupt_enable(qsdev->thc_hw, true); + + ret = thc_dma_configure(qsdev->thc_hw); + if (ret) + return ret; + + ret = thc_interrupt_quiesce(qsdev->thc_hw, false); + if (ret) + return ret; + + ret = quickspi_set_power(qsdev, HIDSPI_ON); + if (ret) + return ret; + + 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; + + 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; + + 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; + + 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_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; + + /* 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); + + ret = thc_interrupt_quiesce(qsdev->thc_hw, false); + if (ret) + return ret; + + /* 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); + + 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), }, @@ -726,6 +971,7 @@ static struct pci_driver quickspi_driver = { .probe = quickspi_probe, .remove = quickspi_remove, .shutdown = quickspi_shutdown, + .driver.pm = &quickspi_pm_ops, .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, }; diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h index 49b0322b9ebb..c7400667cd70 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h @@ -47,6 +47,14 @@ #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_RESETING, diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c index 2553d4b42b51..ad52e402c28a 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c @@ -3,6 +3,7 @@ #include #include +#include #include "quickspi-dev.h" #include "quickspi-hid.h" @@ -54,6 +55,10 @@ static int quickspi_hid_raw_request(struct hid_device *hid, 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); @@ -66,6 +71,9 @@ static int quickspi_hid_raw_request(struct hid_device *hid, break; } + pm_runtime_mark_last_busy(qsdev->dev); + pm_runtime_put_autosuspend(qsdev->dev); + return ret; } From patchwork Thu Nov 14 05:34:12 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843283 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2ECE11F891B; Thu, 14 Nov 2024 05:35:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562522; cv=none; b=O43TXL+CQOgQMOGZY5smTw2re7mPVL6uRZuJvZBOE6KzrRC7671vy5czdGAWklC/7tTuFE+ppr+vI7AzeiYsODx2U3LblYa1gP1CCHHEjAujXEFmnI+70OQ3amycuS9qGLcfV5s6cCm8WQBTEwubBwd1B6iSqcz/CejmxfW99sQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562522; c=relaxed/simple; bh=S2VGnoN8x+4uuiFuz+X+fHxqFUmuiVGq32WOlZ7OJdM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=opWL4M6Qt9JIW7Iu2W8LiXFTpE11k4V4j7a2rhy9q0KrCGMFt2Rkz1DkzuP4k/JMKDuIDKk9ruMn5dwFpIsEcG6Tlh/PSvzFnxB41P4vH/MWHRV67GPYhuDpQ0AHbl0uIIApzPlY07UCPbSMV6wSZUoDwHt87FbmRXQs2fBtVTU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=cfvlrZVk; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="cfvlrZVk" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562521; x=1763098521; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=S2VGnoN8x+4uuiFuz+X+fHxqFUmuiVGq32WOlZ7OJdM=; b=cfvlrZVk6joNktrRrFEL+b20TT+/6SK5cO2HYNf6NRYeXMcRn7yIqiPe BKA5eVRMoBLUUT2OGb/h3Q30XaDvkoexzicINni3Rj0TmeZXing9H0Vo7 ftVQwJbak2d5yOPdPBjnWfHgcXBTmvLFPvZEZNArdat/17Dedh5Lip4se pDpBNG/dOKpLD+U5SWYcvbZjaoUAtInWpJHyXGjo1qaKMZLj94fWQgeL8 dyqAn8A/0oIOelPILDQ+up8Ukm0xcnYvT8YvzsApKW1oEyVbI0LnCo4jy pZASnr8P5Hfigf5eR1tTER3PQMUzZn9Dy9YmZ68ZvBINMXX1KFrfhTgva w==; X-CSE-ConnectionGUID: ja5ifZqfTnKkXQ6R670Nhg== X-CSE-MsgGUID: jwuEwoeuTNeVv/74rrvJ9Q== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869139" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869139" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:35:21 -0800 X-CSE-ConnectionGUID: /8JyXSwnQ+S0LrgSCtDY+A== X-CSE-MsgGUID: js82RsSxSvGLB+Rg+TGMWw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891711" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:35:18 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Even Xu , Xinpeng Sun , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 18/22] HID: intel-thc-hid: intel-quicki2c: Add THC QuickI2C driver hid layer Date: Thu, 14 Nov 2024 13:34:12 +0800 Message-Id: <20241114053416.4085715-19-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add HID Low level driver callbacks and hid probe function to register QucikI2C as a HID driver, and external touch device as a HID device. Co-developed-by: Xinpeng Sun Signed-off-by: Xinpeng Sun Signed-off-by: Even Xu Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- drivers/hid/intel-thc-hid/Makefile | 1 + .../intel-quicki2c/quicki2c-dev.h | 11 ++ .../intel-quicki2c/quicki2c-hid.c | 142 ++++++++++++++++++ .../intel-quicki2c/quicki2c-hid.h | 14 ++ include/linux/hid-over-i2c.h | 44 ++++++ 5 files changed, 212 insertions(+) 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 include/linux/hid-over-i2c.h diff --git a/drivers/hid/intel-thc-hid/Makefile b/drivers/hid/intel-thc-hid/Makefile index 4d4f02e1c415..91bde6d6aa14 100644 --- a/drivers/hid/intel-thc-hid/Makefile +++ b/drivers/hid/intel-thc-hid/Makefile @@ -16,5 +16,6 @@ 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 ccflags-y += -I $(src)/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 index 90c4ffe5ccfa..9e2a863d8135 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -4,6 +4,8 @@ #ifndef _QUICKI2C_DEV_H_ #define _QUICKI2C_DEV_H_ +#include + #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 @@ -26,23 +28,32 @@ enum quicki2c_dev_state { struct device; struct pci_dev; struct thc_device; +struct hid_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 * @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 + * @report_descriptor: store a copy of device report descriptor */ struct quicki2c_device { struct device *dev; struct pci_dev *pdev; struct thc_device *thc_hw; + struct hid_device *hid_dev; enum quicki2c_dev_state state; void __iomem *mem_addr; + + struct hidi2c_dev_descriptor dev_desc; + + u8 *report_descriptor; }; #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..4236603f3f76 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Intel Corporation */ + +#include +#include + +#include "quicki2c-dev.h" +#include "quicki2c-hid.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) +{ + return 0; +} + +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/include/linux/hid-over-i2c.h b/include/linux/hid-over-i2c.h new file mode 100644 index 000000000000..b70626723a38 --- /dev/null +++ b/include/linux/hid-over-i2c.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2024 Intel Corporation */ + +#ifndef _HID_OVER_I2C_H_ +#define _HID_OVER_I2C_H_ + +/** + * struct hidi2c_dev_descriptor - HIDI2C device descriptor definition + * @dev_desc_len: The length of the complete device descriptor, fixed to 0x1E (30). + * @bcd_ver: The version number of the HIDI2C protocol supported. + * In binary coded decimal (BCD) format. + * @report_desc_len: The length of the report descriptor + * @report_desc_reg: The register address to retrieve report descriptor + * @input_reg: the register address to retrieve input report + * @max_input_len: The length of the largest possible HID input (or feature) report + * @output_reg: the register address to send output report + * @max_output_len: The length of the largest output (or feature) report + * @cmd_reg: the register address to send command + * @data_reg: the register address to send command data + * @vendor_id: Device manufacturers vendor ID + * @product_id: Device unique model/product ID + * @version_id: Device’s unique version + * @reserved0: Reserved and should be 0 + * @reserved1: Reserved and should be 0 + */ +struct hidi2c_dev_descriptor { + __le16 dev_desc_len; + __le16 bcd_ver; + __le16 report_desc_len; + __le16 report_desc_reg; + __le16 input_reg; + __le16 max_input_len; + __le16 output_reg; + __le16 max_output_len; + __le16 cmd_reg; + __le16 data_reg; + __le16 vendor_id; + __le16 product_id; + __le16 version_id; + __le16 reserved0; + __le16 reserved1; +} __packed; + +#endif /* _HID_OVER_I2C_H_ */ From patchwork Thu Nov 14 05:34:14 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843282 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CF9C11F891B; Thu, 14 Nov 2024 05:35:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562529; cv=none; b=kgFQFvV0tO6seIG4yRL2ZUG0AgjUdRhGOrVzB8kWBScFqR7W4U84xa48NNvr3F+P9rITj8A6ZgsUnXWgob/guGZOZxAR/CD0AEG0J1n5lpVfsG5NKjwbb3UWeUWldrx2sQHm2XvMxxnhb9Q/9nfNO0xN3VJnxzp1JsZyu+ylTi4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562529; c=relaxed/simple; bh=ceMPxRK8KRn99au/OLCkMlcU7DwV77mplej/hHuRU1E=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=fKyOLRUmZHq+LmerMMBtdJth6n6Ok6JJfS56i/7wZMNutSJpMwLouq8JqF4Qs+aTyj0x+OgVLRjeirQctsG0iJCXVORtrJHvTrR5sEIuEwUgOB7AiodKAv5BL3bvKrg6Yar2rGtnxS0L+NxNqOMIKiHx+g/uVhiD/6t2JoUPBbs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=Xgcwiw8e; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="Xgcwiw8e" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562528; x=1763098528; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=ceMPxRK8KRn99au/OLCkMlcU7DwV77mplej/hHuRU1E=; b=Xgcwiw8ePqlabw9E1sqwOYd2vjPml+GkjJjzETZcyZtGYbdVCTfCrrfk c3FzCCX13Ve9fxQl2hQM2efFC/K+GUm8vGolCq7aIXwWZlGgD//bivwNz bXafwUazPNAQWEMsQ3vlS/SrRdKJR7OBmlyMYHyTtiPtt2XsLptbZ12oz Ds0FW1KJ7cIgYhGa2Efmgz5TcIBRXMZ46wjjYi7oWRJa4r00cvGhZyhvl yuYukz5eqiY4XXzQEW7C/hqxAUg6V5bLE8JbrhwnPB2NXUr6/pzQsO/F2 3pRXbKlVp4KrX3UqAqd2oc095qGzhFGifA8pl3DCEfaTAtaflJnZxVjn2 w==; X-CSE-ConnectionGUID: sz26JHduSvKJHax1rznPQw== X-CSE-MsgGUID: FaYOSHscRsSFXaO4hzpkDg== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869153" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869153" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:35:27 -0800 X-CSE-ConnectionGUID: v4wtcDPETsyeNSBTtH4qLA== X-CSE-MsgGUID: 91C+D4aYQCSe2M/FlHC/kw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891741" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:35:24 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Even Xu , Xinpeng Sun , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 20/22] HID: intel-thc-hid: intel-quicki2c: Add HIDI2C protocol implementation Date: Thu, 14 Nov 2024 13:34:14 +0800 Message-Id: <20241114053416.4085715-21-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Intel QuickI2C driver uses THC hardware to accelerate HID over I2C (HIDI2C) protocol flow. This patch implements all data flows described in HID over I2C protocol SPEC by using THC hardware layer APIs. HID over I2C SPEC: https://learn.microsoft.com/en-us/previous-versions/windows/hardware/design/dn642101(v=vs.85) Co-developed-by: Xinpeng Sun Signed-off-by: Xinpeng Sun Signed-off-by: Even Xu Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- drivers/hid/intel-thc-hid/Makefile | 1 + .../intel-quicki2c/quicki2c-dev.h | 6 + .../intel-quicki2c/quicki2c-hid.c | 18 +- .../intel-quicki2c/quicki2c-protocol.c | 192 ++++++++++++++++++ .../intel-quicki2c/quicki2c-protocol.h | 19 ++ include/linux/hid-over-i2c.h | 73 +++++++ 6 files changed, 308 insertions(+), 1 deletion(-) 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 diff --git a/drivers/hid/intel-thc-hid/Makefile b/drivers/hid/intel-thc-hid/Makefile index 91bde6d6aa14..6f762d87af07 100644 --- a/drivers/hid/intel-thc-hid/Makefile +++ b/drivers/hid/intel-thc-hid/Makefile @@ -17,5 +17,6 @@ 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/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h index 60cb736bd5e5..d6ad731120ce 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -138,6 +138,9 @@ struct acpi_device; * @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 */ struct quicki2c_device { struct device *dev; @@ -161,6 +164,9 @@ struct quicki2c_device { u32 i2c_clock_lcnt; u8 *report_descriptor; + u8 *input_buf; + u8 *report_buf; + u32 report_len; }; #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 index 4236603f3f76..e8e6f10b7952 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c @@ -6,6 +6,7 @@ #include "quicki2c-dev.h" #include "quicki2c-hid.h" +#include "quicki2c-protocol.h" /** * quicki2c_hid_parse() - HID core parse() callback @@ -51,7 +52,22 @@ static int quicki2c_hid_raw_request(struct hid_device *hid, __u8 *buf, size_t len, unsigned char rtype, int reqtype) { - return 0; + struct quicki2c_device *qcdev = hid->driver_data; + int ret = 0; + + 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; + } + + return ret; } static int quicki2c_hid_power(struct hid_device *hid, int lvl) 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..6e6463abe894 --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2024 Intel Corporation */ + +#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 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; + + 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) + 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; +} 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..3a0d66c7d9ef --- /dev/null +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h @@ -0,0 +1,19 @@ +/* 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, 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); + +#endif /* _QUICKI2C_PROTOCOL_H_ */ diff --git a/include/linux/hid-over-i2c.h b/include/linux/hid-over-i2c.h index b70626723a38..3b1a0208a6b8 100644 --- a/include/linux/hid-over-i2c.h +++ b/include/linux/hid-over-i2c.h @@ -1,9 +1,80 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2024 Intel Corporation */ +#include + #ifndef _HID_OVER_I2C_H_ #define _HID_OVER_I2C_H_ +#define HIDI2C_REG_LEN sizeof(__le16) + +/* Input report type definition in HIDI2C protocol */ +enum hidi2c_report_type { + HIDI2C_RESERVED = 0, + HIDI2C_INPUT, + HIDI2C_OUTPUT, + HIDI2C_FEATURE, +}; + +/* Power state type definition in HIDI2C protocol */ +enum hidi2c_power_state { + HIDI2C_ON, + HIDI2C_SLEEP, +}; + +/* Opcode type definition in HIDI2C protocol */ +enum hidi2c_opcode { + HIDI2C_RESET = 1, + HIDI2C_GET_REPORT, + HIDI2C_SET_REPORT, + HIDI2C_GET_IDLE, + HIDI2C_SET_IDLE, + HIDI2C_GET_PROTOCOL, + HIDI2C_SET_PROTOCOL, + HIDI2C_SET_POWER, +}; + +/** + * struct hidi2c_report_packet - Report packet definition in HIDI2C protocol + * @len: data field length + * @data: HIDI2C report packet data + */ +struct hidi2c_report_packet { + __le16 len; + u8 data[]; +} __packed; + +#define HIDI2C_LENGTH_LEN sizeof(__le16) + +#define HIDI2C_PACKET_LEN(data_len) ((data_len) + HIDI2C_LENGTH_LEN) +#define HIDI2C_DATA_LEN(pkt_len) ((pkt_len) - HIDI2C_LENGTH_LEN) + +#define HIDI2C_CMD_MAX_RI 0x0F + +/** + * HIDI2C command data packet - Command packet definition in HIDI2C protocol + * @report_id: [0:3] report id (<15) for features or output reports + * @report_type: [4:5] indicate report type, reference to hidi2c_report_type + * @reserved0: [6:7] reserved bits + * @opcode: [8:11] command operation code, reference to hidi2c_opcode + * @reserved1: [12:15] reserved bits + * @report_id_optional: [23:16] appended 3rd byte. + * If the report_id in the low byte is set to the + * sentinel value (HIDI2C_CMD_MAX_RI), then this + * optional third byte represents the report id (>=15) + * Otherwise, not this 3rd byte. + */ + +#define HIDI2C_CMD_LEN sizeof(__le16) +#define HIDI2C_CMD_LEN_OPT (sizeof(__le16) + 1) +#define HIDI2C_CMD_REPORT_ID GENMASK(3, 0) +#define HIDI2C_CMD_REPORT_TYPE GENMASK(5, 4) +#define HIDI2C_CMD_OPCODE GENMASK(11, 8) +#define HIDI2C_CMD_OPCODE GENMASK(11, 8) +#define HIDI2C_CMD_3RD_BYTE GENMASK(23, 16) + +#define HIDI2C_HID_DESC_BCDVERSION 0x100 + /** * struct hidi2c_dev_descriptor - HIDI2C device descriptor definition * @dev_desc_len: The length of the complete device descriptor, fixed to 0x1E (30). @@ -41,4 +112,6 @@ struct hidi2c_dev_descriptor { __le16 reserved1; } __packed; +#define HIDI2C_DEV_DESC_LEN sizeof(struct hidi2c_dev_descriptor) + #endif /* _HID_OVER_I2C_H_ */ From patchwork Thu Nov 14 05:34:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Even Xu X-Patchwork-Id: 843281 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7C47C1F9432; Thu, 14 Nov 2024 05:35:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562536; cv=none; b=ZFWN7zpsHDDJPEixXuPHcabu/OH6wCt6in9cb5SYfdfGGiVyMBqpQAGdXNNJZ4p6W4M4H8SJCXZ+G9TKyxivn/ry0MlEVZ/6DO2i+p93GlfB35k8ERjMKDA2ecGddHzum9pokvp0+dgzmQXzF9r/FaafwZucbcy1UppA5YDH7yE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1731562536; c=relaxed/simple; bh=I9UdPq7xVbQXTsvqIT/0pW3bgh+EpZb1v+ic5y/WJvk=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=cKd6477mFbMA/mFCrCcTjTRbr8K1uQDZ3vSnuJfhzFayOhAxhyHoitvaQATRrwUlrlbHrPSBpjWgZTKvHn6Jgwcwt1K+5+Z1I5vSpOdgDEOAA3N1vVcsZ33BjMP8AtXvSs3/tA/p/2PtWrH4zKtLn+VahQxCJA1BKQnBCfHwA6c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=czMe4zBe; arc=none smtp.client-ip=192.198.163.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="czMe4zBe" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1731562534; x=1763098534; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=I9UdPq7xVbQXTsvqIT/0pW3bgh+EpZb1v+ic5y/WJvk=; b=czMe4zBel0hnWlA1GzHTosbTTk4Nndo2PDCwgTdVg/Z6xi8VhOeG/zIw x3yzVRBMnMxHxqB6eh1tcE+CQMN0MLxkmkIQcpiFvyHvXzXPDJ3hUjNYd BkZvB7/qb5jaW8z+0rWiYoJtWbkQsb/rF9UevsG3YZV8jD2ugEiif/Y+L uAaszu1DaW5I+aT4gYSJqjG9cgIHHmKPojRtjSCfgI1v7FZB+ZwAdbawR fXEazjnW3ZMhPo3eI00PeWMHmRDJnD4+lta6BDkWj/xsnlrMix9k2C98I SHIp3uFZySQ/5s4AJIFVBmvVCYkcN2SMg/URiIoiEhcviG/aQKlVL+Tfz A==; X-CSE-ConnectionGUID: dkkuz+uIRFCwK84BVX2MQw== X-CSE-MsgGUID: 7O7TodNKRdyXdnthbwIUrQ== X-IronPort-AV: E=McAfee;i="6700,10204,11255"; a="30869165" X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="30869165" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by fmvoesa112.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 13 Nov 2024 21:35:34 -0800 X-CSE-ConnectionGUID: TEkWmKDhQN2MN/en535oqw== X-CSE-MsgGUID: sHSgXHtBRc26T2bv6eU22w== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.12,153,1728975600"; d="scan'208";a="92891812" Received: from shsensorbuild.sh.intel.com ([10.239.133.18]) by fmviesa004.fm.intel.com with ESMTP; 13 Nov 2024 21:35:31 -0800 From: Even Xu To: jikos@kernel.org, bentiss@kernel.org, corbet@lwn.net, bagasdotme@gmail.com, aaron.ma@canonical.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Even Xu , Xinpeng Sun , Rui Zhang , Srinivas Pandruvada Subject: [PATCH v2 22/22] HID: intel-thc-hid: intel-quicki2c: Add PM implementation Date: Thu, 14 Nov 2024 13:34:16 +0800 Message-Id: <20241114053416.4085715-23-even.xu@intel.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20241114053416.4085715-1-even.xu@intel.com> References: <20241114053416.4085715-1-even.xu@intel.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Implement THC QuickI2C driver power management callbacks. Co-developed-by: Xinpeng Sun Signed-off-by: Xinpeng Sun Signed-off-by: Even Xu Tested-by: Rui Zhang Reviewed-by: Srinivas Pandruvada --- .../intel-quicki2c/pci-quicki2c.c | 233 ++++++++++++++++++ .../intel-quicki2c/quicki2c-dev.h | 8 + .../intel-quicki2c/quicki2c-hid.c | 8 + 3 files changed, 249 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index 7045b211a5c9..1300a97978d8 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "intel-thc-dev.h" #include "intel-thc-hw.h" @@ -289,10 +290,15 @@ 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) || @@ -314,6 +320,9 @@ static irqreturn_t quicki2c_irq_thread_handler(int irq, void *dev_id) if (try_recover(qcdev)) qcdev->state = QUICKI2C_DISABLED; + pm_runtime_mark_last_busy(qcdev->dev); + pm_runtime_put_autosuspend(qcdev->dev); + return IRQ_HANDLED; } @@ -637,6 +646,13 @@ static int quicki2c_probe(struct pci_dev *pdev, 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; @@ -673,6 +689,8 @@ static void quicki2c_remove(struct pci_dev *pdev) quicki2c_hid_remove(qcdev); quicki2c_dma_deinit(qcdev); + pm_runtime_get_noresume(qcdev->dev); + quicki2c_dev_deinit(qcdev); pcim_iounmap_regions(pdev, BIT(0)); @@ -703,6 +721,220 @@ static void quicki2c_shutdown(struct pci_dev *pdev) 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), }, @@ -720,6 +952,7 @@ static struct pci_driver quicki2c_driver = { .probe = quicki2c_probe, .remove = quicki2c_remove, .shutdown = quicki2c_shutdown, + .driver.pm = &quicki2c_pm_ops, .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, }; diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h index 0fdac6ba1b04..6ddb584bd611 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -36,6 +36,14 @@ #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, diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c index e8e6f10b7952..5c3ec95bb3fd 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c @@ -3,6 +3,7 @@ #include #include +#include #include "quicki2c-dev.h" #include "quicki2c-hid.h" @@ -55,6 +56,10 @@ static int quicki2c_hid_raw_request(struct hid_device *hid, 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); @@ -67,6 +72,9 @@ static int quicki2c_hid_raw_request(struct hid_device *hid, break; } + pm_runtime_mark_last_busy(qcdev->dev); + pm_runtime_put_autosuspend(qcdev->dev); + return ret; }