From patchwork Sun Nov 22 22:39:07 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11924027 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,MSGID_FROM_MTA_HEADER,SPF_HELO_NONE,SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9AC76C64E69 for ; Sun, 22 Nov 2020 22:39:56 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 53CB52080A for ; Sun, 22 Nov 2020 22:39:56 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="Xb/MFr0o" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726964AbgKVWjm (ORCPT ); Sun, 22 Nov 2020 17:39:42 -0500 Received: from mail-bn8nam08on2058.outbound.protection.outlook.com ([40.107.100.58]:46161 "EHLO NAM04-BN8-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726810AbgKVWjm (ORCPT ); Sun, 22 Nov 2020 17:39:42 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=UbI+8Ix6NczQYIn2gcqF4TStG+Vad+IBMIAryV7Q17cZ3pzqS/TQWzNUTPlrSSfR3N1eIaLWwUa5V6BCfcFsIxXBqpXQtTSi6hESeY+ZJ6RWVZuFVknvYKuGKx1GIYxfczwYYm8ukCc09osGsH2kID6OYZ9IZRPa8GiaAYpvmu7UU//uCAagnAKwc2NiokZcGQnXV9xpzkaE7fgzmYpLGXOKMh2uzZWXlHM53gB8U4ZjBIQKpZ2Klr69pUBSgemGut0izvd4gW91lEURWvLmusUUrTE+XmqrXeU4G/xEv3FoPPPXUzWeYUhefC4WBpgj1Y6+8IYdptxu0qaE5JiDjg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=R7rPyazXVNSOjsHUuqmm2YTjrXcsCbDmjM2e8z3rv/Y=; b=JwfYwh4rH7FdvZhzVPElRvqynyZUfj0M+w32ZNaEW3yhmlAs0BcxNsxa4DT+nsWW7Hr3bbSYdj92ZjmWhUK+d/yIZGC/N40bX34KjLfYyDrhQKFcDggFpMZ9Do8qg76LVJzHP8TMH0PlT2Mx+DzfgL2QXhtXGc0Vx10g5QDAL4/TAqEUJWb7PbQz+EQ0T2nuAoxz/askGyzf9Ha+wTCo7WJzmlA/4kzkJ8OAFmwOGbnI4xio6ToiXP88OCrxYEjevN1usD49buefQQFiUM4GyVOx1LwzAzQuokyxf7IZAAbR0x7EiTZ1JtWR0iLsDOUj6J7q0zn6pXq+xTMxV5oUkQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=labundy.com; dmarc=pass action=none header.from=labundy.com; dkim=pass header.d=labundy.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=NETORG5796793.onmicrosoft.com; s=selector1-NETORG5796793-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=R7rPyazXVNSOjsHUuqmm2YTjrXcsCbDmjM2e8z3rv/Y=; b=Xb/MFr0op/70QeFDhemSw5Opm5SGFPB2Ks+VF2WW2esVJpTecjeU6K/OwJDvdof3yAT8Fnyj16F13bL/Qk0N3qUQiqaGzBgx4YHtTYG7xunzthvmheTv9eWCKQTRSZmiP63aE1BSi+YdR7VuJ3oZPHfkX1geFdcDX9qaslUT2LI= Authentication-Results: gmail.com; dkim=none (message not signed) header.d=none;gmail.com; dmarc=none action=none header.from=labundy.com; Received: from SN6PR08MB5517.namprd08.prod.outlook.com (2603:10b6:805:fb::32) by SA2PR08MB6604.namprd08.prod.outlook.com (2603:10b6:806:11c::20) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.3589.20; Sun, 22 Nov 2020 22:39:38 +0000 Received: from SN6PR08MB5517.namprd08.prod.outlook.com ([fe80::3cac:792d:fcf4:75a2]) by SN6PR08MB5517.namprd08.prod.outlook.com ([fe80::3cac:792d:fcf4:75a2%7]) with mapi id 15.20.3564.035; Sun, 22 Nov 2020 22:39:38 +0000 From: Jeff LaBundy To: dmitry.torokhov@gmail.com, robh+dt@kernel.org Cc: linux-input@vger.kernel.org, devicetree@vger.kernel.org, Jeff LaBundy Subject: [PATCH 1/2] dt-bindings: input: Add bindings for Azoteq IQS626A Date: Sun, 22 Nov 2020 16:39:07 -0600 Message-Id: <1606084748-4097-2-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1606084748-4097-1-git-send-email-jeff@labundy.com> References: <1606084748-4097-1-git-send-email-jeff@labundy.com> X-Originating-IP: [136.49.227.119] X-ClientProxiedBy: SN6PR01CA0017.prod.exchangelabs.com (2603:10b6:805:b6::30) To SN6PR08MB5517.namprd08.prod.outlook.com (2603:10b6:805:fb::32) MIME-Version: 1.0 X-MS-Exchange-MessageSentRepresentingType: 1 Received: from localhost.localdomain (136.49.227.119) by SN6PR01CA0017.prod.exchangelabs.com (2603:10b6:805:b6::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.3589.20 via Frontend Transport; Sun, 22 Nov 2020 22:39:37 +0000 X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: e7f1a6f1-cc69-4f89-691c-08d88f377e6c X-MS-TrafficTypeDiagnostic: SA2PR08MB6604: X-MS-Exchange-Transport-Forked: True X-Microsoft-Antispam-PRVS: X-MS-Oob-TLC-OOBClassifiers: OLM:4502; X-MS-Exchange-SenderADCheck: 1 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: sWSErqIoxCZ7XxB0LRPyHhFYLL9txMLBpaAwnM/v7L0dIDRmPLNq4jbJLEgR3dquis9+0HtQukIvHMuhX9a5DNhyTcAkLU4xG19AirWXDoEH9C8egvdaWO4iTk77AfAtcOxBGsDBReB3CMdbZShHnsS4oVbrqM2KJKt3vxgiA8+v9aniEAQqdowx2r0Mw+CoOxPGsOWUD949lfdLVEa1vqqi16dQ8pZOs8Kbw5iqytBuCdzUJgJWzasTPmkr+E3ruCeTanMtli1zA40N0KGpURRvTrwSNyol5VG8iR5mucpybAf0y1Rh8N1xztSZrysvZliv+WGGnZAvtjgI5qfkJPf0J2FW6S1eVU4JlvMcOBlZ8pgYdgvEfz7oYONQ55qVIFHXR8jOUlMl940p/ov8ADhdpZM8HQpWLH3Xz5NTKDKRhZJxd4Fca38aQ2Q9oCkFMAw3XeIP/h9dxIe/hRyxsg== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:SN6PR08MB5517.namprd08.prod.outlook.com;PTR:;CAT:NONE;SFS:(346002)(136003)(396003)(366004)(376002)(39830400003)(6512007)(316002)(6486002)(83380400001)(86362001)(69590400008)(36756003)(478600001)(8676002)(966005)(4326008)(5660300002)(107886003)(6506007)(66946007)(66556008)(66476007)(2906002)(26005)(6666004)(30864003)(8936002)(52116002)(2616005)(16526019)(186003)(956004);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData: TwLHPc2gHKCIljvLFAJ0XB1TPY6gb0BdCsmBUKetZBy2gVw0zJnoHXjzJE4ZdS1q1eyEaa7mC6QiZeGgatDvjnyX8EEaG62qYKyiiXKhyC9C/Dpu/48aSGZCapn8BuR+15RyP/Dk+bkxcseprx7v1A1EQ9lTJbl5iOEnRtTGdJFhslIv+RlGhOU6koMX6ToTLYePDgChoVCI9/wBBtZM/favIoxBzWZ199EfiMu+kRy01nHiSfffW5tQo+MCze15iAjeQCBHsTEncPATqM34ExGtnfDDyEUAfaisjg9g5vGDzDuDpwhcwHapO2IUwO+OOglypS1ZtpGEQTEVNm6AXAFDTUeJvw7jSXBn7O+iPkMlab9W4NVDPjoqYgYtRq5SzvP4iI3U4pAqNJGwSPx+Xv07GgU8Dp8pdvQ1iIrM6p2WZIbmC8z9ZzcEu6VcsbQpcXrlbYKwwu68WTlO/CFJR+WXlasvapoAwC8Itjot97iZV3FIbhGanuzb7n8tFkvcFIlVI2hCovIPsaPw+X1yyhdb/NzG7w33MaV/LZ9raVkrhx7wq+Vg/eohmfyLuDUcmT5jUy2Yb9uG+LaHvzsO+NVUipNM5KlrPKVyYiAE9JPqznYRNmyvakv4fnozOedJftl6mBy0+IfRqErnzBHl+Q== X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: e7f1a6f1-cc69-4f89-691c-08d88f377e6c X-MS-Exchange-CrossTenant-AuthSource: SN6PR08MB5517.namprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 22 Nov 2020 22:39:38.2301 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 00b69d09-acab-4585-aca7-8fb7c6323e6f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 2mOAcqBevqmfL10b/F9effnWNV+r6/vDTbGNc+JJnLd1LgoahKAbVua6Y4mPIQkzoZfYSy1mcEnJDXTfihFePg== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA2PR08MB6604 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org This patch adds device tree bindings for the Azoteq IQS626A capacitive touch controller. Signed-off-by: Jeff LaBundy --- .../devicetree/bindings/input/iqs626a.yaml | 840 +++++++++++++++++++++ 1 file changed, 840 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/iqs626a.yaml diff --git a/Documentation/devicetree/bindings/input/iqs626a.yaml b/Documentation/devicetree/bindings/input/iqs626a.yaml new file mode 100644 index 0000000..af2f88b --- /dev/null +++ b/Documentation/devicetree/bindings/input/iqs626a.yaml @@ -0,0 +1,840 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/iqs626a.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Azoteq IQS626A Capacitive Touch Controller + +maintainers: + - Jeff LaBundy + +description: | + The Azoteq IQS626A is a 14-channel capacitive touch controller that features + additional Hall-effect and inductive sensing capabilities. + + Link to datasheet: https://www.azoteq.com/ + +properties: + compatible: + const: azoteq,iqs626a + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + azoteq,suspend-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: | + Specifies the power mode during suspend as follows: + 0: Automatic (same as normal runtime, i.e. suspend/resume disabled) + 1: Low power (all sensing at a reduced reporting rate) + 2: Ultra-low power (ULP channel proximity sensing) + 3: Halt (no sensing) + + azoteq,clk-div: + type: boolean + description: Divides the device's core clock by a factor of 4. + + azoteq,ulp-enable: + type: boolean + description: + Permits the device to automatically enter ultra-low-power mode from low- + power mode. + + azoteq,ulp-update: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4, 5, 6, 7] + default: 3 + description: | + Specifies the rate at which the trackpad, generic and Hall channels are + updated during ultra-low-power mode as follows: + 0: 8 + 1: 13 + 2: 28 + 3: 54 + 4: 89 + 5: 135 + 6: 190 + 7: 256 + + azoteq,ati-band-disable: + type: boolean + description: Disables the ATI band check. + + azoteq,ati-lp-only: + type: boolean + description: Limits automatic ATI to low-power mode. + + azoteq,gpio3-select: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4, 5, 6, 7] + default: 1 + description: | + Selects the channel or group of channels for which the GPIO3 pin + represents touch state as follows: + 0: None + 1: ULP channel + 2: Trackpad + 3: Trackpad + 4: Generic channel 0 + 5: Generic channel 1 + 6: Generic channel 2 + 7: Hall channel + + azoteq,reseed-select: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: | + Specifies the event(s) that prompt the device to reseed (i.e. reset the + long-term average) of an associated channel as follows: + 0: None + 1: Proximity + 2: Proximity or touch + 3: Proximity, touch or deep touch + + azoteq,thresh-extend: + type: boolean + description: Multiplies all touch and deep-touch thresholds by 4. + + azoteq,tracking-enable: + type: boolean + description: + Enables all associated channels to track their respective reference + channels. + + azoteq,reseed-offset: + type: boolean + description: + Applies an 8-count offset to all long-term averages upon either ATI or + reseed events. + + azoteq,rate-np-ms: + minimum: 0 + maximum: 255 + default: 150 + description: Specifies the report rate (in ms) during normal-power mode. + + azoteq,rate-lp-ms: + minimum: 0 + maximum: 255 + default: 150 + description: Specifies the report rate (in ms) during low-power mode. + + azoteq,rate-ulp-ms: + multipleOf: 16 + minimum: 0 + maximum: 4080 + default: 0 + description: Specifies the report rate (in ms) during ultra-low-power mode. + + azoteq,timeout-pwr-ms: + multipleOf: 512 + minimum: 0 + maximum: 130560 + default: 2560 + description: + Specifies the length of time (in ms) to wait for an event before moving + from normal-power mode to low-power mode, or (if 'azoteq,ulp-enable' is + present) from low-power mode to ultra-low-power mode. + + azoteq,timeout-lta-ms: + multipleOf: 512 + minimum: 0 + maximum: 130560 + default: 40960 + description: + Specifies the length of time (in ms) to wait before resetting the long- + term average of all channels. Specify the maximum timeout to disable it + altogether. + + touchscreen-inverted-x: true + touchscreen-inverted-y: true + touchscreen-swapped-x-y: true + +patternProperties: + "^ulp-0|generic-[0-2]|hall$": + type: object + description: + Represents a single sensing channel. A channel is active if defined and + inactive otherwise. + + properties: + azoteq,ati-exclude: + type: boolean + description: + Prevents the channel from participating in an ATI event that is + manually triggered during initialization. + + azoteq,reseed-disable: + type: boolean + description: + Prevents the channel from being reseeded if the long-term average + timeout (defined in 'azoteq,timeout-lta') expires. + + azoteq,meas-cap-decrease: + type: boolean + description: + Decreases the internal measurement capacitance from 60 pF to 15 pF. + + azoteq,rx-inactive: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2] + default: 0 + description: | + Specifies how inactive CRX pins are to be terminated as follows: + 0: VSS + 1: Floating + 2: VREG (generic channels only) + + azoteq,linearize: + type: boolean + description: + Enables linearization of the channel's counts (generic and Hall + channels) or inverts the polarity of the channel's proximity or + touch states (ULP channel). + + azoteq,dual-direction: + type: boolean + description: + Specifies that the channel's long-term average is to freeze in the + presence of either increasing or decreasing counts, thereby permit- + ting events to be reported in either direction. + + azoteq,filt-disable: + type: boolean + description: Disables raw count filtering for the channel. + + azoteq,ati-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + description: | + Specifies the channel's ATI mode as follows: + 0: Disabled + 1: Semi-partial + 2: Partial + 3: Full + + The default value is a function of the channel and the device's reset + user interface (RUI); reference the datasheet for further information + about the available RUI options. + + azoteq,ati-base: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [75, 100, 150, 200] + description: + Specifies the channel's ATI base. The default value is a function + of the channel and the device's RUI. + + azoteq,ati-target: + $ref: /schemas/types.yaml#/definitions/uint32 + multipleOf: 32 + minimum: 0 + maximum: 2016 + description: + Specifies the channel's ATI target. The default value is a function + of the channel and the device's RUI. + + azoteq,cct-increase: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 16 + default: 0 + description: + Specifies the degree to which the channel's charge cycle time is to + be increased, with 0 representing no increase. The maximum value is + limited to 4 in the case of the ULP channel, and the property is un- + available entirely in the case of the Hall channel. + + azoteq,proj-bias: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: | + Specifies the bias current applied during projected-capacitance + sensing as follows: + 0: 2.5 uA + 1: 5 uA + 2: 10 uA + 3: 20 uA + + This property is unavailable in the case of the Hall channel. + + azoteq,sense-freq: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + description: | + Specifies the channel's sensing frequency as follows (parenthesized + numbers represent the frequency if 'azoteq,clk-div' is present): + 0: 4 MHz (1 MHz) + 1: 2 MHz (500 kHz) + 2: 1 MHz (250 kHz) + 3: 500 kHz (125 kHz) + + This property is unavailable in the case of the Hall channel. The + default value is a function of the channel and the device's RUI. + + azoteq,ati-band-tighten: + type: boolean + description: + Tightens the ATI band from 1/8 to 1/16 of the desired target (ULP and + generic channels only). + + azoteq,proj-enable: + type: boolean + description: Enables projected-capacitance sensing (ULP channel only). + + azoteq,filt-str-np-cnt: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: + Specifies the raw count filter strength during normal-power mode (ULP + and generic channels only). + + azoteq,filt-str-lp-cnt: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: + Specifies the raw count filter strength during low-power mode (ULP and + generic channels only). + + azoteq,filt-str-np-lta: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: + Specifies the long-term average filter strength during normal-power + mode (ULP and generic channels only). + + azoteq,filt-str-lp-lta: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: + Specifies the long-term average filter strength during low-power mode + (ULP and generic channels only). + + azoteq,rx-enable: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 8 + items: + minimum: 0 + maximum: 7 + description: + Specifies the CRX pin(s) associated with the channel. + + This property is unavailable in the case of the Hall channel. The + default value is a function of the channel and the device's RUI. + + azoteq,tx-enable: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 8 + items: + minimum: 0 + maximum: 7 + description: + Specifies the TX pin(s) associated with the channel. + + This property is unavailable in the case of the Hall channel. The + default value is a function of the channel and the device's RUI. + + azoteq,local-cap-size: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4] + default: 0 + description: | + Specifies the capacitance to be added to the channel as follows: + 0: 0 pF + 1: 0.5 pF + 2: 1.0 pF + 3: 1.5 pF + 4: 2.0 pF + + This property is unavailable in the case of the ULP or Hall channels. + + azoteq,sense-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 8, 9, 12, 14, 15] + description: | + Specifies the channel's sensing mode as follows: + 0: Self capacitance + 1: Projected capacitance + 8: Self inductance + 9: Mutual inductance + 12: External + 14: Hall effect + 15: Temperature + + This property is unavailable in the case of the ULP or Hall channels. + The default value is a function of the channel and the device's RUI. + + azoteq,tx-freq: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: | + Specifies the inductive sensing excitation frequency as follows + (parenthesized numbers represent the frequency if 'azoteq,clk-div' + is present): + 0: 16 MHz (4 MHz) + 1: 8 MHz (2 MHz) + 2: 4 MHz (1 MHz) + 3: 2 MHz (500 kHz) + + This property is unavailable in the case of the ULP or Hall channels. + + azoteq,invert-enable: + type: boolean + description: + Inverts the polarity of the states reported for proximity, touch and + deep-touch events relative to their respective thresholds (generic + channels only). + + azoteq,comp-disable: + type: boolean + description: + Disables compensation for the channel (generic channels only). + + azoteq,static-enable: + type: boolean + description: + Enables the static front-end for the channel (generic channels only). + + azoteq,assoc-select: + $ref: /schemas/types.yaml#/definitions/string-array + minItems: 1 + maxItems: 6 + items: + enum: + - ulp-0 + - trackpad-3x2 + - trackpad-3x3 + - generic-0 + - generic-1 + - generic-2 + - hall + description: + Specifies the associated channels for which the channel serves as a + reference channel. By default, no channels are selected. This prop- + erty is only available for the generic channels. + + azoteq,assoc-weight: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 255 + default: 0 + description: + Specifies the channel's impact weight if it acts as an associated + channel (0 = 0% impact, 255 = 200% impact). This property is only + available for the generic channels. + + patternProperties: + "^event-(prox|touch|deep)(-alt)?$": + type: object + description: + Represents a proximity, touch or deep-touch event reported by the + channel in response to a decrease in counts. Node names suffixed with + '-alt' instead correspond to an increase in counts. + + By default, the long-term average tracks an increase in counts such + that only events corresponding to a decrease in counts are reported + (refer to the datasheet for more information). + + Specify 'azoteq,dual-direction' to freeze the long-term average when + the counts increase or decrease such that events of either direction + can be reported. Alternatively, specify 'azoteq,invert-enable' to in- + vert the polarity of the states reported by the channel. + + Complementary events (e.g. event-touch and event-touch-alt) can both + be present and specify different key or switch codes, but not differ- + ent thresholds or hysteresis (if applicable). + + Proximity events are unavailable in the case of the Hall channel, and + deep-touch events are only available for the generic channels. Unless + otherwise specified, default values are a function of the channel and + the device's RUI. + + properties: + azoteq,thresh: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 255 + description: Specifies the threshold for the event. + + azoteq,hyst: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 15 + description: + Specifies the hysteresis for the event (touch and deep-touch + events only). + + linux,code: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Numeric key or switch code associated with the event. + + linux,input-type: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 5] + description: + Specifies whether the event is to be interpreted as a key (1) or + a switch (5). By default, Hall-channel events are interpreted as + switches and all others are interpreted as keys. + + dependencies: + linux,input-type: ["linux,code"] + + additionalProperties: false + + dependencies: + azoteq,assoc-weight: ["azoteq,assoc-select"] + + additionalProperties: false + + "^trackpad-3x[2-3]$": + type: object + description: + Represents all channels associated with the trackpad. The channels are + collectively active if the trackpad is defined and inactive otherwise. + + properties: + azoteq,ati-exclude: + type: boolean + description: + Prevents the trackpad channels from participating in an ATI event + that is manually triggered during initialization. + + azoteq,reseed-disable: + type: boolean + description: + Prevents the trackpad channels from being reseeded if the long-term + average timeout (defined in 'azoteq,timeout-lta') expires. + + azoteq,meas-cap-decrease: + type: boolean + description: + Decreases the internal measurement capacitance from 60 pF to 15 pF. + + azoteq,rx-inactive: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + default: 0 + description: | + Specifies how inactive CRX pins are to be terminated as follows: + 0: VSS + 1: Floating + + azoteq,linearize: + type: boolean + description: Inverts the polarity of the trackpad's touch state. + + azoteq,dual-direction: + type: boolean + description: + Specifies that the trackpad's long-term averages are to freeze in + the presence of either increasing or decreasing counts, thereby + permitting events to be reported in either direction. + + azoteq,filt-disable: + type: boolean + description: Disables raw count filtering for the trackpad channels. + + azoteq,ati-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: | + Specifies the trackpad's ATI mode as follows: + 0: Disabled + 1: Semi-partial + 2: Partial + 3: Full + + azoteq,ati-base: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 6 + maxItems: 9 + items: + minimum: 45 + maximum: 300 + default: [45, 45, 45, 45, 45, 45, 45, 45, 45] + description: Specifies each individual trackpad channel's ATI base. + + azoteq,ati-target: + $ref: /schemas/types.yaml#/definitions/uint32 + multipleOf: 32 + minimum: 0 + maximum: 2016 + default: 0 + description: Specifies the trackpad's ATI target. + + azoteq,cct-increase: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 4 + default: 0 + description: + Specifies the degree to which the trackpad's charge cycle time is to + be increased, with 0 representing no increase. + + azoteq,proj-bias: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: | + Specifies the bias current applied during projected-capacitance + sensing as follows: + 0: 2.5 uA + 1: 5 uA + 2: 10 uA + 3: 20 uA + + azoteq,sense-freq: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: | + Specifies the trackpad's sensing frequency as follows (parenthesized + numbers represent the frequency if 'azoteq,clk-div' is present): + 0: 4 MHz (1 MHz) + 1: 2 MHz (500 kHz) + 2: 1 MHz (250 kHz) + 3: 500 kHz (125 kHz) + + azoteq,ati-band-tighten: + type: boolean + description: + Tightens the ATI band from 1/8 to 1/16 of the desired target. + + azoteq,thresh: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 6 + maxItems: 9 + items: + minimum: 0 + maximum: 255 + default: [0, 0, 0, 0, 0, 0, 0, 0, 0] + description: + Specifies each individual trackpad channel's touch threshold. + + azoteq,hyst: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 15 + default: 0 + description: Specifies the trackpad's touch hysteresis. + + azoteq,lta-update: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4, 5, 6, 7] + default: 0 + description: | + Specifies the update rate of the trackpad's long-term average during + ultra-low-power mode as follows: + 0: 2 + 1: 4 + 2: 8 + 3: 16 + 4: 32 + 5: 64 + 6: 128 + 7: 255 + + azoteq,filt-str-trackpad: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: Specifies the trackpad coordinate filter strength. + + azoteq,filt-str-np-cnt: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: + Specifies the raw count filter strength during normal-power mode. + + azoteq,filt-str-lp-cnt: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 0 + description: + Specifies the raw count filter strength during low-power mode. + + linux,keycodes: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 6 + description: | + Specifies the numeric keycodes associated with each available gesture + in the following order (enter 0 for unused gestures): + 0: Positive flick or swipe in X direction + 1: Negative flick or swipe in X direction + 2: Positive flick or swipe in Y direction + 3: Negative flick or swipe in Y direction + 4: Tap + 5: Hold + + azoteq,gesture-swipe: + type: boolean + description: + Directs the device to interpret axial gestures as a swipe (finger + remains on trackpad) instead of a flick (finger leaves trackpad). + + azoteq,timeout-tap-ms: + multipleOf: 16 + minimum: 0 + maximum: 4080 + default: 0 + description: + Specifies the length of time (in ms) within which a trackpad touch + must be released in order to be interpreted as a tap. + + azoteq,timeout-swipe-ms: + multipleOf: 16 + minimum: 0 + maximum: 4080 + default: 0 + description: + Specifies the length of time (in ms) within which an axial gesture + must be completed in order to be interpreted as a flick or swipe. + + azoteq,thresh-swipe: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 255 + default: 0 + description: + Specifies the number of points across which an axial gesture must + travel in order to be interpreted as a flick or swipe. + + dependencies: + azoteq,gesture-swipe: ["linux,keycodes"] + azoteq,timeout-tap-ms: ["linux,keycodes"] + azoteq,timeout-swipe-ms: ["linux,keycodes"] + azoteq,thresh-swipe: ["linux,keycodes"] + + additionalProperties: false + +required: + - compatible + - reg + - interrupts + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + iqs626a@44 { + #address-cells = <1>; + #size-cells = <0>; + + compatible = "azoteq,iqs626a"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_LEVEL_LOW>; + + azoteq,rate-np-ms = <16>; + azoteq,rate-lp-ms = <160>; + + azoteq,timeout-pwr-ms = <2560>; + azoteq,timeout-lta-ms = <32768>; + + ulp-0 { + azoteq,meas-cap-decrease; + + azoteq,ati-base = <75>; + azoteq,ati-target = <1024>; + + azoteq,rx-enable = <2>, <3>, <4>, + <5>, <6>, <7>; + + event-prox { + linux,code = ; + }; + }; + + trackpad-3x3 { + azoteq,filt-str-np-cnt = <1>; + azoteq,filt-str-lp-cnt = <1>; + + azoteq,hyst = <4>; + azoteq,thresh = <35>, <40>, <40>, + <38>, <33>, <38>, + <35>, <35>, <35>; + + azoteq,ati-mode = <3>; + azoteq,ati-base = <195>, <195>, <195>, + <195>, <195>, <195>, + <195>, <195>, <195>; + azoteq,ati-target = <512>; + + azoteq,proj-bias = <1>; + azoteq,sense-freq = <2>; + + linux,keycodes = , + , + , + , + , + ; + + azoteq,gesture-swipe; + azoteq,timeout-swipe-ms = <800>; + azoteq,timeout-tap-ms = <400>; + azoteq,thresh-swipe = <40>; + }; + + /* + * Preserve the default register settings for + * the temperature-tracking channel leveraged + * by reset user interface (RUI) 1. + * + * Scalar properties (e.g. ATI mode) are left + * untouched by simply omitting them; boolean + * properties must be specified explicitly as + * needed. + */ + generic-2 { + azoteq,reseed-disable; + azoteq,meas-cap-decrease; + azoteq,dual-direction; + azoteq,comp-disable; + azoteq,static-enable; + }; + + hall { + azoteq,reseed-disable; + azoteq,meas-cap-decrease; + + event-touch { + linux,code = ; + }; + }; + }; + }; + +... From patchwork Sun Nov 22 22:39:08 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11924029 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,MSGID_FROM_MTA_HEADER,SPF_HELO_NONE,SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7F92AC63798 for ; Sun, 22 Nov 2020 22:40:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3449520782 for ; Sun, 22 Nov 2020 22:40:25 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="jVujKuw1" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726854AbgKVWkA (ORCPT ); Sun, 22 Nov 2020 17:40:00 -0500 Received: from mail-dm6nam12on2069.outbound.protection.outlook.com ([40.107.243.69]:26720 "EHLO NAM12-DM6-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726810AbgKVWj5 (ORCPT ); Sun, 22 Nov 2020 17:39:57 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=jqGXh9PujhQLQDiponhpRTY54INmtJVUH2p+lsjgvfqZTs3Y/TSQ6ReDSgs5tD4+76zgXzWcNK2hv3+ODnRlMzHaBZ/bdSR3eH993dUyzp2q64Vo9mjzQOPbpTx0qNEpMBg3aORpSP8pm6fPATCpb6ZUsJRe38nuor3cfhrS2rGkUBR7NBg/jwlM0S1DaGa/ovbEMl0z7CjAMCgaqi78yfKhLa6X0oPjst0WrnHFiVme+itT0bR4aqfkRL0NdAsRZN5upHH9CWpfapoC4bxIHztF5q5On+X2gmFk1PfEc4S7RP/ePs9pnWZqDWzUTf/QqyBkK+2IniRZu3wCZ3/boQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=k4E4JBrHqd124oTN69EKJQS2bxui42mv0QeeM28LyKE=; b=R8WOPF8ittHhtebmd/9z1oiv7kC6O4TmJhBTEQaUkSduGhHns2UOeE3DdNyNwpnkwCabgCiu4VoaFyq3DqCu+e/ManGwYqeE37xFh7Mq3IVNM89po6U8uJb8wOYjG5AF30GOGkKUebmu7cBC3uVTxXCAPLKKhYFIC7e6hnrd56UtNoY79hqQudMYLLVPgpPK3O0UW8wzR2XNcjvdrHx64cCXy89sJdMty6VPyOdygXzwfpe8gQT5oOW9bwt7aBRMXO4zoRDRJsal1pWCdplU+GGfPc0J5TEoGT9gkDKZvkohU9+Xh1zCu2cGey+1QmFfK2J89as9oOMcgL6CtMsGkg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=labundy.com; dmarc=pass action=none header.from=labundy.com; dkim=pass header.d=labundy.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=NETORG5796793.onmicrosoft.com; s=selector1-NETORG5796793-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=k4E4JBrHqd124oTN69EKJQS2bxui42mv0QeeM28LyKE=; b=jVujKuw17glPEMhX+O4wzpCaK0o3Inyg1KYffHuKcRnJzSSqoXj1T1w1fJ/yIigfLlj6h0nok0oW0gYy5UfposHOXCiOuZV9vqsk4nBHHzJVYN6l+sHA+avPLb3443tjh10tFB0zNfrp3CHy8nlXoWc8P0KN+zUhlyu4FpCXZ1A= Authentication-Results: gmail.com; dkim=none (message not signed) header.d=none;gmail.com; dmarc=none action=none header.from=labundy.com; Received: from SN6PR08MB5517.namprd08.prod.outlook.com (2603:10b6:805:fb::32) by SA2PR08MB6604.namprd08.prod.outlook.com (2603:10b6:806:11c::20) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.3589.20; Sun, 22 Nov 2020 22:39:46 +0000 Received: from SN6PR08MB5517.namprd08.prod.outlook.com ([fe80::3cac:792d:fcf4:75a2]) by SN6PR08MB5517.namprd08.prod.outlook.com ([fe80::3cac:792d:fcf4:75a2%7]) with mapi id 15.20.3564.035; Sun, 22 Nov 2020 22:39:46 +0000 From: Jeff LaBundy To: dmitry.torokhov@gmail.com, robh+dt@kernel.org Cc: linux-input@vger.kernel.org, devicetree@vger.kernel.org, Jeff LaBundy Subject: [PATCH 2/2] input: Add support for Azoteq IQS626A Date: Sun, 22 Nov 2020 16:39:08 -0600 Message-Id: <1606084748-4097-3-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1606084748-4097-1-git-send-email-jeff@labundy.com> References: <1606084748-4097-1-git-send-email-jeff@labundy.com> X-Originating-IP: [136.49.227.119] X-ClientProxiedBy: SN6PR01CA0017.prod.exchangelabs.com (2603:10b6:805:b6::30) To SN6PR08MB5517.namprd08.prod.outlook.com (2603:10b6:805:fb::32) MIME-Version: 1.0 X-MS-Exchange-MessageSentRepresentingType: 1 Received: from localhost.localdomain (136.49.227.119) by SN6PR01CA0017.prod.exchangelabs.com (2603:10b6:805:b6::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.3589.20 via Frontend Transport; Sun, 22 Nov 2020 22:39:45 +0000 X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: 073b1c25-17e4-48b9-be98-08d88f37831c X-MS-TrafficTypeDiagnostic: SA2PR08MB6604: X-MS-Exchange-Transport-Forked: True X-Microsoft-Antispam-PRVS: X-MS-Oob-TLC-OOBClassifiers: OLM:275; X-MS-Exchange-SenderADCheck: 1 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: PnDRBFGg002gZSMRhH+atM2ChKwTwiWXJNuoiEtF1AZF+XZ15WJa1GYb1ffUvRiO9Xzq9w4P5Y1j5H7WcteXdxvLhCjLLmF6sUVzDGzzEF48wofvjO7Pt7xNzD2tVHkw3kDJzwAc96isALxWVWRx+38nwtTCC0QlC0qv9c2d+NckAfcaV2k1sKJxN36g4sPoEcZZAbSOlTUMRRS4lWO1SrXYImp4ijOIMhCSrfylE4aoDVnKmC5+xk610qhIcSgMP9oubdhxzvjerPvu9JdXMehiaw8pa3y9Mip0K4KTV1LWNA8JQn7FREHMOs6RT2H2/73vImTQ+08Hum1KI30lhqiFn/J+6uNFLVst1ewk/CpI5NaMJ1ra/Dfz/QYvZyaMMHGhCS0X0/97PqbvYNEbg7xu9V9cF/LztVC+z7sH71U= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:SN6PR08MB5517.namprd08.prod.outlook.com;PTR:;CAT:NONE;SFS:(346002)(136003)(396003)(366004)(376002)(39830400003)(6512007)(316002)(6486002)(83380400001)(86362001)(69590400008)(36756003)(478600001)(8676002)(4326008)(5660300002)(107886003)(6506007)(66946007)(66556008)(66476007)(2906002)(26005)(6666004)(30864003)(8936002)(52116002)(2616005)(16526019)(186003)(956004)(579004)(559001)(309714004);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData: lOFppUr/cts+e7Eunbba6yINIobKl0ffqJM96HXJhPKcju1OSkLKfmXbSDJ7inbHXOLStiQOBvOlmm7ONf4WFMFZCLs9+/a7RXEyWhxupAWlWz81E34jQuh1vLIF4Kj3EuQyAnawg0SWHN9TRK/56m9f2Ul1oExq5vtp7I453Z0RybmAG9TMdn51dzGykM9LTjot7M67wcW7GdmL7b222XHUEpefJMtJZUr/uOM0HHuo9NfhIZv1o9aylBB41IAnWhSSbEQSR7ITBof1RR3tI+YE3IW4fY1jlyNRmElqjgvNrDBte/q/NxvlyuqdI5I3GEn7JKq8p+lxZ+7M++vvem0fKQq5Eke0oG/qRAlJnCeoQ9UzRaWiMUDDxPmMYBJ+iYJI/L/lir+/CWThQyIpGLCUCL1Ghwn2ELycIXN84YGCI1wwGogfNSQXCaHINqM2byJIQHeE/zLSqHyiM4ifY5nCcVkglcxTJpQx8z3fr+A+6QxrfspNww/fj6VaTBAyvi1j+D6+IwAHdxa4qBZLLrf5TACND1rc0kK1gOW1fKG60J7z8n7V6Nv+/lDBTRk+ENUXyeeC/T95K+iNd+qxTGP1OVBVMDlHuvNJuvGdIV/MxWKm2cPZEgbM9qLcMSMEYWHKghXlTkB2cV1cmanI3A== X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: 073b1c25-17e4-48b9-be98-08d88f37831c X-MS-Exchange-CrossTenant-AuthSource: SN6PR08MB5517.namprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 22 Nov 2020 22:39:46.1216 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 00b69d09-acab-4585-aca7-8fb7c6323e6f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: vggAWDNZ4ef+AXQKYqQOdjrl03K2ozpERhPIJZ7xIa4dl0ctBmfu3J0k0rqkl6XmgSNnqjCK+RIPXbVvMdgRrQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA2PR08MB6604 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org This patch adds support for the Azoteq IQS626A capacitive touch controller. Signed-off-by: Jeff LaBundy --- drivers/input/misc/Kconfig | 11 + drivers/input/misc/Makefile | 1 + drivers/input/misc/iqs626a.c | 1846 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1858 insertions(+) create mode 100644 drivers/input/misc/iqs626a.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 362e8a0..7b6d568 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -729,6 +729,17 @@ config INPUT_IQS269A To compile this driver as a module, choose M here: the module will be called iqs269a. +config INPUT_IQS626A + tristate "Azoteq IQS626A capacitive touch controller" + depends on I2C + select REGMAP_I2C + help + Say Y to enable support for the Azoteq IQS626A capacitive + touch controller. + + To compile this driver as a module, choose M here: the + module will be called iqs626a. + config INPUT_CMA3000 tristate "VTI CMA3000 Tri-axis accelerometer" help diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index a48e5f2..f965122 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_INPUT_HISI_POWERKEY) += hisi_powerkey.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o obj-$(CONFIG_INPUT_IQS269A) += iqs269a.o +obj-$(CONFIG_INPUT_IQS626A) += iqs626a.o obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o diff --git a/drivers/input/misc/iqs626a.c b/drivers/input/misc/iqs626a.c new file mode 100644 index 0000000..5b067cc --- /dev/null +++ b/drivers/input/misc/iqs626a.c @@ -0,0 +1,1846 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS626A Capacitive Touch Controller + * + * Copyright (C) 2020 Jeff LaBundy + * + * This driver registers up to 2 input devices: one representing capacitive or + * inductive keys as well as Hall-effect switches, and one for a trackpad that + * can express various gestures. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS626_VER_INFO 0x00 +#define IQS626_VER_INFO_PROD_NUM 0x51 + +#define IQS626_SYS_FLAGS 0x02 +#define IQS626_SYS_FLAGS_SHOW_RESET BIT(15) +#define IQS626_SYS_FLAGS_IN_ATI BIT(12) +#define IQS626_SYS_FLAGS_PWR_MODE_MASK GENMASK(9, 8) +#define IQS626_SYS_FLAGS_PWR_MODE_SHIFT 8 + +#define IQS626_HALL_OUTPUT 0x23 + +#define IQS626_SYS_SETTINGS 0x80 +#define IQS626_SYS_SETTINGS_CLK_DIV BIT(15) +#define IQS626_SYS_SETTINGS_ULP_AUTO BIT(14) +#define IQS626_SYS_SETTINGS_DIS_AUTO BIT(13) +#define IQS626_SYS_SETTINGS_PWR_MODE_MASK GENMASK(12, 11) +#define IQS626_SYS_SETTINGS_PWR_MODE_SHIFT 11 +#define IQS626_SYS_SETTINGS_PWR_MODE_MAX 3 +#define IQS626_SYS_SETTINGS_ULP_UPDATE_MASK GENMASK(10, 8) +#define IQS626_SYS_SETTINGS_ULP_UPDATE_SHIFT 8 +#define IQS626_SYS_SETTINGS_ULP_UPDATE_MAX 7 +#define IQS626_SYS_SETTINGS_EVENT_MODE BIT(5) +#define IQS626_SYS_SETTINGS_EVENT_MODE_LP BIT(4) +#define IQS626_SYS_SETTINGS_REDO_ATI BIT(2) +#define IQS626_SYS_SETTINGS_ACK_RESET BIT(0) + +#define IQS626_MISC_A_ATI_BAND_DISABLE BIT(7) +#define IQS626_MISC_A_TPx_LTA_UPDATE_MASK GENMASK(6, 4) +#define IQS626_MISC_A_TPx_LTA_UPDATE_SHIFT 4 +#define IQS626_MISC_A_TPx_LTA_UPDATE_MAX 7 +#define IQS626_MISC_A_ATI_LP_ONLY BIT(3) +#define IQS626_MISC_A_GPIO3_SELECT_MASK GENMASK(2, 0) +#define IQS626_MISC_A_GPIO3_SELECT_MAX 7 + +#define IQS626_EVENT_MASK_SYS BIT(6) +#define IQS626_EVENT_MASK_GESTURE BIT(3) +#define IQS626_EVENT_MASK_DEEP BIT(2) +#define IQS626_EVENT_MASK_TOUCH BIT(1) +#define IQS626_EVENT_MASK_PROX BIT(0) + +#define IQS626_RATE_NP_MS_MAX 255 +#define IQS626_RATE_LP_MS_MAX 255 +#define IQS626_RATE_ULP_MS_MAX 4080 +#define IQS626_TIMEOUT_PWR_MS_MAX 130560 +#define IQS626_TIMEOUT_LTA_MS_MAX 130560 + +#define IQS626_MISC_B_RESEED_UI_SEL_MASK GENMASK(7, 6) +#define IQS626_MISC_B_RESEED_UI_SEL_SHIFT 6 +#define IQS626_MISC_B_RESEED_UI_SEL_MAX 3 +#define IQS626_MISC_B_THRESH_EXTEND BIT(5) +#define IQS626_MISC_B_TRACKING_UI_ENABLE BIT(4) +#define IQS626_MISC_B_TPx_SWIPE BIT(3) +#define IQS626_MISC_B_RESEED_OFFSET BIT(2) +#define IQS626_MISC_B_FILT_STR_TPx GENMASK(1, 0) + +#define IQS626_THRESH_SWIPE_MAX 255 +#define IQS626_TIMEOUT_TAP_MS_MAX 4080 +#define IQS626_TIMEOUT_SWIPE_MS_MAX 4080 + +#define IQS626_CHx_ENG_0_MEAS_CAP_SIZE BIT(7) +#define IQS626_CHx_ENG_0_RX_TERM_VSS BIT(5) +#define IQS626_CHx_ENG_0_LINEARIZE BIT(4) +#define IQS626_CHx_ENG_0_DUAL_DIR BIT(3) +#define IQS626_CHx_ENG_0_FILT_DISABLE BIT(2) +#define IQS626_CHx_ENG_0_ATI_MODE_MASK GENMASK(1, 0) +#define IQS626_CHx_ENG_0_ATI_MODE_MAX 3 + +#define IQS626_CHx_ENG_1_CCT_HIGH_1 BIT(7) +#define IQS626_CHx_ENG_1_CCT_HIGH_0 BIT(6) +#define IQS626_CHx_ENG_1_PROJ_BIAS_MASK GENMASK(5, 4) +#define IQS626_CHx_ENG_1_PROJ_BIAS_SHIFT 4 +#define IQS626_CHx_ENG_1_PROJ_BIAS_MAX 3 +#define IQS626_CHx_ENG_1_CCT_ENABLE BIT(3) +#define IQS626_CHx_ENG_1_SENSE_FREQ_MASK GENMASK(2, 1) +#define IQS626_CHx_ENG_1_SENSE_FREQ_SHIFT 1 +#define IQS626_CHx_ENG_1_SENSE_FREQ_MAX 3 +#define IQS626_CHx_ENG_1_ATI_BAND_TIGHTEN BIT(0) + +#define IQS626_CHx_ENG_2_LOCAL_CAP_MASK GENMASK(7, 6) +#define IQS626_CHx_ENG_2_LOCAL_CAP_SHIFT 6 +#define IQS626_CHx_ENG_2_LOCAL_CAP_MAX 3 +#define IQS626_CHx_ENG_2_LOCAL_CAP_ENABLE BIT(5) +#define IQS626_CHx_ENG_2_SENSE_MODE_MASK GENMASK(3, 0) +#define IQS626_CHx_ENG_2_SENSE_MODE_MAX 15 + +#define IQS626_CHx_ENG_3_TX_FREQ_MASK GENMASK(5, 4) +#define IQS626_CHx_ENG_3_TX_FREQ_SHIFT 4 +#define IQS626_CHx_ENG_3_TX_FREQ_MAX 3 +#define IQS626_CHx_ENG_3_INV_LOGIC BIT(0) + +#define IQS626_CHx_ENG_4_RX_TERM_VREG BIT(6) +#define IQS626_CHx_ENG_4_CCT_LOW_1 BIT(5) +#define IQS626_CHx_ENG_4_CCT_LOW_0 BIT(4) +#define IQS626_CHx_ENG_4_COMP_DISABLE BIT(1) +#define IQS626_CHx_ENG_4_STATIC_ENABLE BIT(0) + +#define IQS626_TPx_ATI_BASE_MIN 45 +#define IQS626_TPx_ATI_BASE_MAX 300 +#define IQS626_CHx_ATI_BASE_MASK GENMASK(7, 6) +#define IQS626_CHx_ATI_BASE_75 0x00 +#define IQS626_CHx_ATI_BASE_100 0x40 +#define IQS626_CHx_ATI_BASE_150 0x80 +#define IQS626_CHx_ATI_BASE_200 0xC0 +#define IQS626_CHx_ATI_TARGET_MASK GENMASK(5, 0) +#define IQS626_CHx_ATI_TARGET_MAX 2016 + +#define IQS626_CHx_THRESH_MAX 255 +#define IQS626_CHx_HYST_DEEP_MASK GENMASK(7, 4) +#define IQS626_CHx_HYST_DEEP_SHIFT 4 +#define IQS626_CHx_HYST_TOUCH_MASK GENMASK(3, 0) +#define IQS626_CHx_HYST_MAX 15 + +#define IQS626_FILT_STR_NP_TPx_MASK GENMASK(7, 6) +#define IQS626_FILT_STR_NP_TPx_SHIFT 6 +#define IQS626_FILT_STR_LP_TPx_MASK GENMASK(5, 4) +#define IQS626_FILT_STR_LP_TPx_SHIFT 4 + +#define IQS626_FILT_STR_NP_CNT_MASK GENMASK(7, 6) +#define IQS626_FILT_STR_NP_CNT_SHIFT 6 +#define IQS626_FILT_STR_LP_CNT_MASK GENMASK(5, 4) +#define IQS626_FILT_STR_LP_CNT_SHIFT 4 +#define IQS626_FILT_STR_NP_LTA_MASK GENMASK(3, 2) +#define IQS626_FILT_STR_NP_LTA_SHIFT 2 +#define IQS626_FILT_STR_LP_LTA_MASK GENMASK(1, 0) +#define IQS626_FILT_STR_MAX 3 + +#define IQS626_ULP_PROJ_ENABLE BIT(4) +#define IQS626_GEN_WEIGHT_MAX 255 + +#define IQS626_MAX_REG 0xFF + +#define IQS626_NUM_CH_TP_3 9 +#define IQS626_NUM_CH_TP_2 6 +#define IQS626_NUM_CH_GEN 3 +#define IQS626_NUM_CRx_TX 8 + +#define IQS626_PWR_MODE_POLL_SLEEP_US 50000 +#define IQS626_PWR_MODE_POLL_TIMEOUT_US 500000 + +#define iqs626_irq_wait() usleep_range(350, 400) + +enum iqs626_ch_id { + IQS626_CH_ULP_0, + IQS626_CH_TP_2, + IQS626_CH_TP_3, + IQS626_CH_GEN_0, + IQS626_CH_GEN_1, + IQS626_CH_GEN_2, + IQS626_CH_HALL, +}; + +enum iqs626_rx_inactive { + IQS626_RX_INACTIVE_VSS, + IQS626_RX_INACTIVE_FLOAT, + IQS626_RX_INACTIVE_VREG, +}; + +enum iqs626_st_offs { + IQS626_ST_OFFS_PROX, + IQS626_ST_OFFS_DIR, + IQS626_ST_OFFS_TOUCH, + IQS626_ST_OFFS_DEEP, +}; + +enum iqs626_th_offs { + IQS626_TH_OFFS_PROX, + IQS626_TH_OFFS_TOUCH, + IQS626_TH_OFFS_DEEP, +}; + +enum iqs626_event_id { + IQS626_EVENT_PROX_DN, + IQS626_EVENT_PROX_UP, + IQS626_EVENT_TOUCH_DN, + IQS626_EVENT_TOUCH_UP, + IQS626_EVENT_DEEP_DN, + IQS626_EVENT_DEEP_UP, +}; + +enum iqs626_gesture_id { + IQS626_GESTURE_FLICK_X_POS, + IQS626_GESTURE_FLICK_X_NEG, + IQS626_GESTURE_FLICK_Y_POS, + IQS626_GESTURE_FLICK_Y_NEG, + IQS626_GESTURE_TAP, + IQS626_GESTURE_HOLD, + IQS626_NUM_GESTURES, +}; + +struct iqs626_event_desc { + const char *name; + enum iqs626_st_offs st_offs; + enum iqs626_th_offs th_offs; + bool dir_up; + u8 mask; +}; + +static const struct iqs626_event_desc iqs626_events[] = { + [IQS626_EVENT_PROX_DN] = { + .name = "event-prox", + .st_offs = IQS626_ST_OFFS_PROX, + .th_offs = IQS626_TH_OFFS_PROX, + .mask = IQS626_EVENT_MASK_PROX, + }, + [IQS626_EVENT_PROX_UP] = { + .name = "event-prox-alt", + .st_offs = IQS626_ST_OFFS_PROX, + .th_offs = IQS626_TH_OFFS_PROX, + .dir_up = true, + .mask = IQS626_EVENT_MASK_PROX, + }, + [IQS626_EVENT_TOUCH_DN] = { + .name = "event-touch", + .st_offs = IQS626_ST_OFFS_TOUCH, + .th_offs = IQS626_TH_OFFS_TOUCH, + .mask = IQS626_EVENT_MASK_TOUCH, + }, + [IQS626_EVENT_TOUCH_UP] = { + .name = "event-touch-alt", + .st_offs = IQS626_ST_OFFS_TOUCH, + .th_offs = IQS626_TH_OFFS_TOUCH, + .dir_up = true, + .mask = IQS626_EVENT_MASK_TOUCH, + }, + [IQS626_EVENT_DEEP_DN] = { + .name = "event-deep", + .st_offs = IQS626_ST_OFFS_DEEP, + .th_offs = IQS626_TH_OFFS_DEEP, + .mask = IQS626_EVENT_MASK_DEEP, + }, + [IQS626_EVENT_DEEP_UP] = { + .name = "event-deep-alt", + .st_offs = IQS626_ST_OFFS_DEEP, + .th_offs = IQS626_TH_OFFS_DEEP, + .dir_up = true, + .mask = IQS626_EVENT_MASK_DEEP, + }, +}; + +struct iqs626_ver_info { + u8 prod_num; + u8 sw_num; + u8 hw_num; + u8 padding; +} __packed; + +struct iqs626_flags { + __be16 system; + u8 gesture; + u8 padding_a; + u8 states[4]; + u8 ref_active; + u8 padding_b; + u8 comp_min; + u8 comp_max; + u8 trackpad_x; + u8 trackpad_y; +} __packed; + +struct iqs626_ch_reg_ulp { + u8 thresh[2]; + u8 hyst; + u8 filter; + u8 engine[2]; + u8 ati_target; + u8 padding; + __be16 ati_comp; + u8 rx_enable; + u8 tx_enable; +} __packed; + +struct iqs626_ch_reg_tp { + u8 thresh; + u8 ati_base; + __be16 ati_comp; +} __packed; + +struct iqs626_tp_grp_reg { + u8 hyst; + u8 ati_target; + u8 engine[2]; + struct iqs626_ch_reg_tp ch_reg_tp[IQS626_NUM_CH_TP_3]; +} __packed; + +struct iqs626_ch_reg_gen { + u8 thresh[3]; + u8 padding; + u8 hyst; + u8 ati_target; + __be16 ati_comp; + u8 engine[5]; + u8 filter; + u8 rx_enable; + u8 tx_enable; + u8 assoc_select; + u8 assoc_weight; +} __packed; + +struct iqs626_ch_reg_hall { + u8 engine; + u8 thresh; + u8 hyst; + u8 ati_target; + __be16 ati_comp; +} __packed; + +struct iqs626_sys_reg { + __be16 general; + u8 misc_a; + u8 event_mask; + u8 active; + u8 reseed; + u8 rate_np; + u8 rate_lp; + u8 rate_ulp; + u8 timeout_pwr; + u8 timeout_rdy; + u8 timeout_lta; + u8 misc_b; + u8 thresh_swipe; + u8 timeout_tap; + u8 timeout_swipe; + u8 redo_ati; + u8 padding; + struct iqs626_ch_reg_ulp ch_reg_ulp; + struct iqs626_tp_grp_reg tp_grp_reg; + struct iqs626_ch_reg_gen ch_reg_gen[IQS626_NUM_CH_GEN]; + struct iqs626_ch_reg_hall ch_reg_hall; +} __packed; + +struct iqs626_channel_desc { + const char *name; + int num_ch; + u8 active; + bool events[ARRAY_SIZE(iqs626_events)]; +}; + +static const struct iqs626_channel_desc iqs626_channels[] = { + [IQS626_CH_ULP_0] = { + .name = "ulp-0", + .num_ch = 1, + .active = BIT(0), + .events = { + [IQS626_EVENT_PROX_DN] = true, + [IQS626_EVENT_PROX_UP] = true, + [IQS626_EVENT_TOUCH_DN] = true, + [IQS626_EVENT_TOUCH_UP] = true, + }, + }, + [IQS626_CH_TP_2] = { + .name = "trackpad-3x2", + .num_ch = IQS626_NUM_CH_TP_2, + .active = BIT(1), + .events = { + [IQS626_EVENT_TOUCH_DN] = true, + }, + }, + [IQS626_CH_TP_3] = { + .name = "trackpad-3x3", + .num_ch = IQS626_NUM_CH_TP_3, + .active = BIT(2) | BIT(1), + .events = { + [IQS626_EVENT_TOUCH_DN] = true, + }, + }, + [IQS626_CH_GEN_0] = { + .name = "generic-0", + .num_ch = 1, + .active = BIT(4), + .events = { + [IQS626_EVENT_PROX_DN] = true, + [IQS626_EVENT_PROX_UP] = true, + [IQS626_EVENT_TOUCH_DN] = true, + [IQS626_EVENT_TOUCH_UP] = true, + [IQS626_EVENT_DEEP_DN] = true, + [IQS626_EVENT_DEEP_UP] = true, + }, + }, + [IQS626_CH_GEN_1] = { + .name = "generic-1", + .num_ch = 1, + .active = BIT(5), + .events = { + [IQS626_EVENT_PROX_DN] = true, + [IQS626_EVENT_PROX_UP] = true, + [IQS626_EVENT_TOUCH_DN] = true, + [IQS626_EVENT_TOUCH_UP] = true, + [IQS626_EVENT_DEEP_DN] = true, + [IQS626_EVENT_DEEP_UP] = true, + }, + }, + [IQS626_CH_GEN_2] = { + .name = "generic-2", + .num_ch = 1, + .active = BIT(6), + .events = { + [IQS626_EVENT_PROX_DN] = true, + [IQS626_EVENT_PROX_UP] = true, + [IQS626_EVENT_TOUCH_DN] = true, + [IQS626_EVENT_TOUCH_UP] = true, + [IQS626_EVENT_DEEP_DN] = true, + [IQS626_EVENT_DEEP_UP] = true, + }, + }, + [IQS626_CH_HALL] = { + .name = "hall", + .num_ch = 1, + .active = BIT(7), + .events = { + [IQS626_EVENT_TOUCH_DN] = true, + [IQS626_EVENT_TOUCH_UP] = true, + }, + }, +}; + +struct iqs626_private { + struct i2c_client *client; + struct regmap *regmap; + struct iqs626_sys_reg sys_reg; + struct completion ati_done; + struct input_dev *keypad; + struct input_dev *trackpad; + struct touchscreen_properties prop; + unsigned int kp_type[ARRAY_SIZE(iqs626_channels)] + [ARRAY_SIZE(iqs626_events)]; + unsigned int kp_code[ARRAY_SIZE(iqs626_channels)] + [ARRAY_SIZE(iqs626_events)]; + unsigned int tp_code[IQS626_NUM_GESTURES]; + unsigned int suspend_mode; +}; + +static int iqs626_parse_events(struct iqs626_private *iqs626, + const struct fwnode_handle *ch_node, + enum iqs626_ch_id ch_id) +{ + struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg; + struct i2c_client *client = iqs626->client; + const struct fwnode_handle *ev_node; + const char *ev_name; + u8 *thresh, *hyst; + unsigned int thresh_tp[IQS626_NUM_CH_TP_3]; + unsigned int val; + int num_ch = iqs626_channels[ch_id].num_ch; + int error, i, j; + + switch (ch_id) { + case IQS626_CH_ULP_0: + thresh = sys_reg->ch_reg_ulp.thresh; + hyst = &sys_reg->ch_reg_ulp.hyst; + break; + + case IQS626_CH_TP_2: + case IQS626_CH_TP_3: + thresh = &sys_reg->tp_grp_reg.ch_reg_tp[0].thresh; + hyst = &sys_reg->tp_grp_reg.hyst; + break; + + case IQS626_CH_GEN_0: + case IQS626_CH_GEN_1: + case IQS626_CH_GEN_2: + i = ch_id - IQS626_CH_GEN_0; + thresh = sys_reg->ch_reg_gen[i].thresh; + hyst = &sys_reg->ch_reg_gen[i].hyst; + break; + + case IQS626_CH_HALL: + thresh = &sys_reg->ch_reg_hall.thresh; + hyst = &sys_reg->ch_reg_hall.hyst; + break; + + default: + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(iqs626_events); i++) { + if (!iqs626_channels[ch_id].events[i]) + continue; + + if (ch_id == IQS626_CH_TP_2 || ch_id == IQS626_CH_TP_3) { + /* + * Trackpad touch events are simply described under the + * trackpad child node. + */ + ev_node = ch_node; + } else { + ev_name = iqs626_events[i].name; + ev_node = fwnode_get_named_child_node(ch_node, ev_name); + if (!ev_node) + continue; + + if (!fwnode_property_read_u32(ev_node, "linux,code", + &val)) { + iqs626->kp_code[ch_id][i] = val; + + if (fwnode_property_read_u32(ev_node, + "linux,input-type", + &val)) { + if (ch_id == IQS626_CH_HALL) + val = EV_SW; + else + val = EV_KEY; + } + + if (val != EV_KEY && val != EV_SW) { + dev_err(&client->dev, + "Invalid input type: %u\n", + val); + return -EINVAL; + } + + iqs626->kp_type[ch_id][i] = val; + + sys_reg->event_mask &= ~iqs626_events[i].mask; + } + } + + if (!fwnode_property_read_u32(ev_node, "azoteq,hyst", &val)) { + if (val > IQS626_CHx_HYST_MAX) { + dev_err(&client->dev, + "Invalid %s channel hysteresis: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + if (i == IQS626_EVENT_DEEP_DN || + i == IQS626_EVENT_DEEP_UP) { + *hyst &= ~IQS626_CHx_HYST_DEEP_MASK; + *hyst |= (val << IQS626_CHx_HYST_DEEP_SHIFT); + } else if (i == IQS626_EVENT_TOUCH_DN || + i == IQS626_EVENT_TOUCH_UP) { + *hyst &= ~IQS626_CHx_HYST_TOUCH_MASK; + *hyst |= val; + } + } + + if (ch_id != IQS626_CH_TP_2 && ch_id != IQS626_CH_TP_3 && + !fwnode_property_read_u32(ev_node, "azoteq,thresh", &val)) { + if (val > IQS626_CHx_THRESH_MAX) { + dev_err(&client->dev, + "Invalid %s channel threshold: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + if (ch_id == IQS626_CH_HALL) + *thresh = val; + else + *(thresh + iqs626_events[i].th_offs) = val; + + continue; + } + + if (!fwnode_property_present(ev_node, "azoteq,thresh")) + continue; + + error = fwnode_property_read_u32_array(ev_node, "azoteq,thresh", + thresh_tp, num_ch); + if (error) { + dev_err(&client->dev, + "Failed to read %s channel thresholds: %d\n", + fwnode_get_name(ch_node), error); + return error; + } + + for (j = 0; j < num_ch; j++) { + if (thresh_tp[j] > IQS626_CHx_THRESH_MAX) { + dev_err(&client->dev, + "Invalid %s channel threshold: %u\n", + fwnode_get_name(ch_node), thresh_tp[j]); + return -EINVAL; + } + + sys_reg->tp_grp_reg.ch_reg_tp[j].thresh = thresh_tp[j]; + } + } + + return 0; +} + +static int iqs626_parse_ati_target(struct iqs626_private *iqs626, + const struct fwnode_handle *ch_node, + enum iqs626_ch_id ch_id) +{ + struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg; + struct i2c_client *client = iqs626->client; + unsigned int ati_base[IQS626_NUM_CH_TP_3]; + unsigned int val; + u8 *ati_target; + int num_ch = iqs626_channels[ch_id].num_ch; + int error, i; + + switch (ch_id) { + case IQS626_CH_ULP_0: + ati_target = &sys_reg->ch_reg_ulp.ati_target; + break; + + case IQS626_CH_TP_2: + case IQS626_CH_TP_3: + ati_target = &sys_reg->tp_grp_reg.ati_target; + break; + + case IQS626_CH_GEN_0: + case IQS626_CH_GEN_1: + case IQS626_CH_GEN_2: + i = ch_id - IQS626_CH_GEN_0; + ati_target = &sys_reg->ch_reg_gen[i].ati_target; + break; + + case IQS626_CH_HALL: + ati_target = &sys_reg->ch_reg_hall.ati_target; + break; + + default: + return -EINVAL; + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,ati-target", &val)) { + if (val > IQS626_CHx_ATI_TARGET_MAX) { + dev_err(&client->dev, + "Invalid %s channel ATI target: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *ati_target &= ~IQS626_CHx_ATI_TARGET_MASK; + *ati_target |= (val / 32); + } + + if (ch_id != IQS626_CH_TP_2 && ch_id != IQS626_CH_TP_3 && + !fwnode_property_read_u32(ch_node, "azoteq,ati-base", &val)) { + switch (val) { + case 75: + val = IQS626_CHx_ATI_BASE_75; + break; + + case 100: + val = IQS626_CHx_ATI_BASE_100; + break; + + case 150: + val = IQS626_CHx_ATI_BASE_150; + break; + + case 200: + val = IQS626_CHx_ATI_BASE_200; + break; + + default: + dev_err(&client->dev, + "Invalid %s channel ATI base: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *ati_target &= ~IQS626_CHx_ATI_BASE_MASK; + *ati_target |= val; + + return 0; + } + + if (!fwnode_property_present(ch_node, "azoteq,ati-base")) + return 0; + + error = fwnode_property_read_u32_array(ch_node, "azoteq,ati-base", + ati_base, num_ch); + if (error) { + dev_err(&client->dev, + "Failed to read %s channel ATI bases: %d\n", + fwnode_get_name(ch_node), error); + return error; + } + + for (i = 0; i < num_ch; i++) { + if (ati_base[i] < IQS626_TPx_ATI_BASE_MIN || + ati_base[i] > IQS626_TPx_ATI_BASE_MAX) { + dev_err(&client->dev, + "Invalid %s channel ATI base: %u\n", + fwnode_get_name(ch_node), ati_base[i]); + return -EINVAL; + } + + ati_base[i] -= IQS626_TPx_ATI_BASE_MIN; + sys_reg->tp_grp_reg.ch_reg_tp[i].ati_base = ati_base[i]; + } + + return 0; +} + +static int iqs626_parse_pins(struct iqs626_private *iqs626, + const struct fwnode_handle *ch_node, + const char *propname, u8 *enable) +{ + struct i2c_client *client = iqs626->client; + unsigned int val[IQS626_NUM_CRx_TX]; + int error, count, i; + + if (!fwnode_property_present(ch_node, propname)) + return 0; + + count = fwnode_property_count_u32(ch_node, propname); + if (count > IQS626_NUM_CRx_TX) { + dev_err(&client->dev, + "Too many %s channel CRX/TX pins present\n", + fwnode_get_name(ch_node)); + return -EINVAL; + } else if (count < 0) { + dev_err(&client->dev, + "Failed to count %s channel CRX/TX pins: %d\n", + fwnode_get_name(ch_node), count); + return count; + } + + error = fwnode_property_read_u32_array(ch_node, propname, val, count); + if (error) { + dev_err(&client->dev, + "Failed to read %s channel CRX/TX pins: %d\n", + fwnode_get_name(ch_node), error); + return error; + } + + *enable = 0; + + for (i = 0; i < count; i++) { + if (val[i] >= IQS626_NUM_CRx_TX) { + dev_err(&client->dev, + "Invalid %s channel CRX/TX pin: %u\n", + fwnode_get_name(ch_node), val[i]); + return -EINVAL; + } + + *enable |= BIT(val[i]); + } + + return 0; +} + +static int iqs626_parse_trackpad(struct iqs626_private *iqs626, + const struct fwnode_handle *ch_node) +{ + struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg; + struct i2c_client *client = iqs626->client; + u8 *hyst = &sys_reg->tp_grp_reg.hyst; + unsigned int val; + int error, count; + + if (!fwnode_property_read_u32(ch_node, "azoteq,lta-update", &val)) { + if (val > IQS626_MISC_A_TPx_LTA_UPDATE_MAX) { + dev_err(&client->dev, + "Invalid %s channel update rate: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + sys_reg->misc_a &= ~IQS626_MISC_A_TPx_LTA_UPDATE_MASK; + sys_reg->misc_a |= (val << IQS626_MISC_A_TPx_LTA_UPDATE_SHIFT); + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-trackpad", + &val)) { + if (val > IQS626_FILT_STR_MAX) { + dev_err(&client->dev, + "Invalid %s channel filter strength: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + sys_reg->misc_b &= ~IQS626_MISC_B_FILT_STR_TPx; + sys_reg->misc_b |= val; + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-np-cnt", + &val)) { + if (val > IQS626_FILT_STR_MAX) { + dev_err(&client->dev, + "Invalid %s channel filter strength: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *hyst &= ~IQS626_FILT_STR_NP_TPx_MASK; + *hyst |= (val << IQS626_FILT_STR_NP_TPx_SHIFT); + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-lp-cnt", + &val)) { + if (val > IQS626_FILT_STR_MAX) { + dev_err(&client->dev, + "Invalid %s channel filter strength: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *hyst &= ~IQS626_FILT_STR_LP_TPx_MASK; + *hyst |= (val << IQS626_FILT_STR_LP_TPx_SHIFT); + } + + if (!fwnode_property_present(ch_node, "linux,keycodes")) + return 0; + + count = fwnode_property_count_u32(ch_node, "linux,keycodes"); + if (count > IQS626_NUM_GESTURES) { + dev_err(&client->dev, "Too many keycodes present\n"); + return -EINVAL; + } else if (count < 0) { + dev_err(&client->dev, "Failed to count keycodes: %d\n", count); + return count; + } + + error = fwnode_property_read_u32_array(ch_node, "linux,keycodes", + iqs626->tp_code, count); + if (error) { + dev_err(&client->dev, "Failed to read keycodes: %d\n", error); + return error; + } + + sys_reg->misc_b &= ~IQS626_MISC_B_TPx_SWIPE; + if (fwnode_property_present(ch_node, "azoteq,gesture-swipe")) + sys_reg->misc_b |= IQS626_MISC_B_TPx_SWIPE; + + if (!fwnode_property_read_u32(ch_node, "azoteq,timeout-tap-ms", + &val)) { + if (val > IQS626_TIMEOUT_TAP_MS_MAX) { + dev_err(&client->dev, + "Invalid %s channel timeout: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + sys_reg->timeout_tap = val / 16; + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,timeout-swipe-ms", + &val)) { + if (val > IQS626_TIMEOUT_SWIPE_MS_MAX) { + dev_err(&client->dev, + "Invalid %s channel timeout: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + sys_reg->timeout_swipe = val / 16; + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,thresh-swipe", + &val)) { + if (val > IQS626_THRESH_SWIPE_MAX) { + dev_err(&client->dev, + "Invalid %s channel threshold: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + sys_reg->thresh_swipe = val; + } + + sys_reg->event_mask &= ~IQS626_EVENT_MASK_GESTURE; + + return 0; +} + +static int iqs626_parse_channel(struct iqs626_private *iqs626, + const struct fwnode_handle *ch_node, + enum iqs626_ch_id ch_id) +{ + struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg; + struct i2c_client *client = iqs626->client; + u8 *engine, *filter, *rx_enable, *tx_enable; + u8 *assoc_select, *assoc_weight; + unsigned int val; + int error, i; + + switch (ch_id) { + case IQS626_CH_ULP_0: + engine = sys_reg->ch_reg_ulp.engine; + break; + + case IQS626_CH_TP_2: + case IQS626_CH_TP_3: + engine = sys_reg->tp_grp_reg.engine; + break; + + case IQS626_CH_GEN_0: + case IQS626_CH_GEN_1: + case IQS626_CH_GEN_2: + i = ch_id - IQS626_CH_GEN_0; + engine = sys_reg->ch_reg_gen[i].engine; + break; + + case IQS626_CH_HALL: + engine = &sys_reg->ch_reg_hall.engine; + break; + + default: + return -EINVAL; + } + + *engine |= IQS626_CHx_ENG_0_MEAS_CAP_SIZE; + if (fwnode_property_present(ch_node, "azoteq,meas-cap-decrease")) + *engine &= ~IQS626_CHx_ENG_0_MEAS_CAP_SIZE; + + *engine |= IQS626_CHx_ENG_0_RX_TERM_VSS; + if (!fwnode_property_read_u32(ch_node, "azoteq,rx-inactive", &val)) { + switch (val) { + case IQS626_RX_INACTIVE_VSS: + break; + + case IQS626_RX_INACTIVE_FLOAT: + *engine &= ~IQS626_CHx_ENG_0_RX_TERM_VSS; + if (ch_id == IQS626_CH_GEN_0 || + ch_id == IQS626_CH_GEN_1 || + ch_id == IQS626_CH_GEN_2) + *(engine + 4) &= ~IQS626_CHx_ENG_4_RX_TERM_VREG; + break; + + case IQS626_RX_INACTIVE_VREG: + if (ch_id == IQS626_CH_GEN_0 || + ch_id == IQS626_CH_GEN_1 || + ch_id == IQS626_CH_GEN_2) { + *engine &= ~IQS626_CHx_ENG_0_RX_TERM_VSS; + *(engine + 4) |= IQS626_CHx_ENG_4_RX_TERM_VREG; + break; + } + fallthrough; + + default: + dev_err(&client->dev, + "Invalid %s channel CRX pin termination: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + } + + *engine &= ~IQS626_CHx_ENG_0_LINEARIZE; + if (fwnode_property_present(ch_node, "azoteq,linearize")) + *engine |= IQS626_CHx_ENG_0_LINEARIZE; + + *engine &= ~IQS626_CHx_ENG_0_DUAL_DIR; + if (fwnode_property_present(ch_node, "azoteq,dual-direction")) + *engine |= IQS626_CHx_ENG_0_DUAL_DIR; + + *engine &= ~IQS626_CHx_ENG_0_FILT_DISABLE; + if (fwnode_property_present(ch_node, "azoteq,filt-disable")) + *engine |= IQS626_CHx_ENG_0_FILT_DISABLE; + + if (!fwnode_property_read_u32(ch_node, "azoteq,ati-mode", &val)) { + if (val > IQS626_CHx_ENG_0_ATI_MODE_MAX) { + dev_err(&client->dev, + "Invalid %s channel ATI mode: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *engine &= ~IQS626_CHx_ENG_0_ATI_MODE_MASK; + *engine |= val; + } + + if (ch_id == IQS626_CH_HALL) + return 0; + + *(engine + 1) &= ~IQS626_CHx_ENG_1_CCT_ENABLE; + if (!fwnode_property_read_u32(ch_node, "azoteq,cct-increase", + &val) && val) { + unsigned int orig_val = val--; + + /* + * In the case of the generic channels, the charge cycle time + * field doubles in size and straddles two separate registers. + */ + if (ch_id == IQS626_CH_GEN_0 || + ch_id == IQS626_CH_GEN_1 || + ch_id == IQS626_CH_GEN_2) { + *(engine + 4) &= ~IQS626_CHx_ENG_4_CCT_LOW_1; + if (val & BIT(1)) + *(engine + 4) |= IQS626_CHx_ENG_4_CCT_LOW_1; + + *(engine + 4) &= ~IQS626_CHx_ENG_4_CCT_LOW_0; + if (val & BIT(0)) + *(engine + 4) |= IQS626_CHx_ENG_4_CCT_LOW_0; + + val >>= 2; + } + + if (val & ~GENMASK(1, 0)) { + dev_err(&client->dev, + "Invalid %s channel charge cycle time: %u\n", + fwnode_get_name(ch_node), orig_val); + return -EINVAL; + } + + *(engine + 1) &= ~IQS626_CHx_ENG_1_CCT_HIGH_1; + if (val & BIT(1)) + *(engine + 1) |= IQS626_CHx_ENG_1_CCT_HIGH_1; + + *(engine + 1) &= ~IQS626_CHx_ENG_1_CCT_HIGH_0; + if (val & BIT(0)) + *(engine + 1) |= IQS626_CHx_ENG_1_CCT_HIGH_0; + + *(engine + 1) |= IQS626_CHx_ENG_1_CCT_ENABLE; + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,proj-bias", &val)) { + if (val > IQS626_CHx_ENG_1_PROJ_BIAS_MAX) { + dev_err(&client->dev, + "Invalid %s channel bias current: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *(engine + 1) &= ~IQS626_CHx_ENG_1_PROJ_BIAS_MASK; + *(engine + 1) |= (val << IQS626_CHx_ENG_1_PROJ_BIAS_SHIFT); + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,sense-freq", &val)) { + if (val > IQS626_CHx_ENG_1_SENSE_FREQ_MAX) { + dev_err(&client->dev, + "Invalid %s channel sensing frequency: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *(engine + 1) &= ~IQS626_CHx_ENG_1_SENSE_FREQ_MASK; + *(engine + 1) |= (val << IQS626_CHx_ENG_1_SENSE_FREQ_SHIFT); + } + + *(engine + 1) &= ~IQS626_CHx_ENG_1_ATI_BAND_TIGHTEN; + if (fwnode_property_present(ch_node, "azoteq,ati-band-tighten")) + *(engine + 1) |= IQS626_CHx_ENG_1_ATI_BAND_TIGHTEN; + + if (ch_id == IQS626_CH_TP_2 || ch_id == IQS626_CH_TP_3) + return iqs626_parse_trackpad(iqs626, ch_node); + + if (ch_id == IQS626_CH_ULP_0) { + sys_reg->ch_reg_ulp.hyst &= ~IQS626_ULP_PROJ_ENABLE; + if (fwnode_property_present(ch_node, "azoteq,proj-enable")) + sys_reg->ch_reg_ulp.hyst |= IQS626_ULP_PROJ_ENABLE; + + filter = &sys_reg->ch_reg_ulp.filter; + + rx_enable = &sys_reg->ch_reg_ulp.rx_enable; + tx_enable = &sys_reg->ch_reg_ulp.tx_enable; + } else { + i = ch_id - IQS626_CH_GEN_0; + filter = &sys_reg->ch_reg_gen[i].filter; + + rx_enable = &sys_reg->ch_reg_gen[i].rx_enable; + tx_enable = &sys_reg->ch_reg_gen[i].tx_enable; + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-np-cnt", + &val)) { + if (val > IQS626_FILT_STR_MAX) { + dev_err(&client->dev, + "Invalid %s channel filter strength: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *filter &= ~IQS626_FILT_STR_NP_CNT_MASK; + *filter |= (val << IQS626_FILT_STR_NP_CNT_SHIFT); + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-lp-cnt", + &val)) { + if (val > IQS626_FILT_STR_MAX) { + dev_err(&client->dev, + "Invalid %s channel filter strength: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *filter &= ~IQS626_FILT_STR_LP_CNT_MASK; + *filter |= (val << IQS626_FILT_STR_LP_CNT_SHIFT); + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-np-lta", + &val)) { + if (val > IQS626_FILT_STR_MAX) { + dev_err(&client->dev, + "Invalid %s channel filter strength: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *filter &= ~IQS626_FILT_STR_NP_LTA_MASK; + *filter |= (val << IQS626_FILT_STR_NP_LTA_SHIFT); + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-lp-lta", + &val)) { + if (val > IQS626_FILT_STR_MAX) { + dev_err(&client->dev, + "Invalid %s channel filter strength: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *filter &= ~IQS626_FILT_STR_LP_LTA_MASK; + *filter |= val; + } + + error = iqs626_parse_pins(iqs626, ch_node, "azoteq,rx-enable", + rx_enable); + if (error) + return error; + + error = iqs626_parse_pins(iqs626, ch_node, "azoteq,tx-enable", + tx_enable); + if (error) + return error; + + if (ch_id == IQS626_CH_ULP_0) + return 0; + + *(engine + 2) &= ~IQS626_CHx_ENG_2_LOCAL_CAP_ENABLE; + if (!fwnode_property_read_u32(ch_node, "azoteq,local-cap-size", + &val) && val) { + unsigned int orig_val = val--; + + if (val > IQS626_CHx_ENG_2_LOCAL_CAP_MAX) { + dev_err(&client->dev, + "Invalid %s channel local cap. size: %u\n", + fwnode_get_name(ch_node), orig_val); + return -EINVAL; + } + + *(engine + 2) &= ~IQS626_CHx_ENG_2_LOCAL_CAP_MASK; + *(engine + 2) |= (val << IQS626_CHx_ENG_2_LOCAL_CAP_SHIFT); + + *(engine + 2) |= IQS626_CHx_ENG_2_LOCAL_CAP_ENABLE; + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,sense-mode", &val)) { + if (val > IQS626_CHx_ENG_2_SENSE_MODE_MAX) { + dev_err(&client->dev, + "Invalid %s channel sensing mode: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *(engine + 2) &= ~IQS626_CHx_ENG_2_SENSE_MODE_MASK; + *(engine + 2) |= val; + } + + if (!fwnode_property_read_u32(ch_node, "azoteq,tx-freq", &val)) { + if (val > IQS626_CHx_ENG_3_TX_FREQ_MAX) { + dev_err(&client->dev, + "Invalid %s channel excitation frequency: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *(engine + 3) &= ~IQS626_CHx_ENG_3_TX_FREQ_MASK; + *(engine + 3) |= (val << IQS626_CHx_ENG_3_TX_FREQ_SHIFT); + } + + *(engine + 3) &= ~IQS626_CHx_ENG_3_INV_LOGIC; + if (fwnode_property_present(ch_node, "azoteq,invert-enable")) + *(engine + 3) |= IQS626_CHx_ENG_3_INV_LOGIC; + + *(engine + 4) &= ~IQS626_CHx_ENG_4_COMP_DISABLE; + if (fwnode_property_present(ch_node, "azoteq,comp-disable")) + *(engine + 4) |= IQS626_CHx_ENG_4_COMP_DISABLE; + + *(engine + 4) &= ~IQS626_CHx_ENG_4_STATIC_ENABLE; + if (fwnode_property_present(ch_node, "azoteq,static-enable")) + *(engine + 4) |= IQS626_CHx_ENG_4_STATIC_ENABLE; + + i = ch_id - IQS626_CH_GEN_0; + assoc_select = &sys_reg->ch_reg_gen[i].assoc_select; + assoc_weight = &sys_reg->ch_reg_gen[i].assoc_weight; + + *assoc_select = 0; + if (!fwnode_property_present(ch_node, "azoteq,assoc-select")) + return 0; + + for (i = 0; i < ARRAY_SIZE(iqs626_channels); i++) { + if (fwnode_property_match_string(ch_node, "azoteq,assoc-select", + iqs626_channels[i].name) < 0) + continue; + + *assoc_select |= iqs626_channels[i].active; + } + + if (fwnode_property_read_u32(ch_node, "azoteq,assoc-weight", &val)) + return 0; + + if (val > IQS626_GEN_WEIGHT_MAX) { + dev_err(&client->dev, + "Invalid %s channel associated weight: %u\n", + fwnode_get_name(ch_node), val); + return -EINVAL; + } + + *assoc_weight = val; + + return 0; +} + +static int iqs626_parse_prop(struct iqs626_private *iqs626) +{ + struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg; + struct i2c_client *client = iqs626->client; + struct fwnode_handle *ch_node; + unsigned int val; + int error, i; + u16 general; + + if (!device_property_read_u32(&client->dev, "azoteq,suspend-mode", + &val)) { + if (val > IQS626_SYS_SETTINGS_PWR_MODE_MAX) { + dev_err(&client->dev, "Invalid suspend mode: %u\n", + val); + return -EINVAL; + } + + iqs626->suspend_mode = val; + } + + error = regmap_raw_read(iqs626->regmap, IQS626_SYS_SETTINGS, sys_reg, + sizeof(*sys_reg)); + if (error) + return error; + + general = be16_to_cpu(sys_reg->general); + general &= IQS626_SYS_SETTINGS_ULP_UPDATE_MASK; + + if (device_property_present(&client->dev, "azoteq,clk-div")) + general |= IQS626_SYS_SETTINGS_CLK_DIV; + + if (device_property_present(&client->dev, "azoteq,ulp-enable")) + general |= IQS626_SYS_SETTINGS_ULP_AUTO; + + if (!device_property_read_u32(&client->dev, "azoteq,ulp-update", + &val)) { + if (val > IQS626_SYS_SETTINGS_ULP_UPDATE_MAX) { + dev_err(&client->dev, "Invalid update rate: %u\n", val); + return -EINVAL; + } + + general &= ~IQS626_SYS_SETTINGS_ULP_UPDATE_MASK; + general |= (val << IQS626_SYS_SETTINGS_ULP_UPDATE_SHIFT); + } + + sys_reg->misc_a &= ~IQS626_MISC_A_ATI_BAND_DISABLE; + if (device_property_present(&client->dev, "azoteq,ati-band-disable")) + sys_reg->misc_a |= IQS626_MISC_A_ATI_BAND_DISABLE; + + sys_reg->misc_a &= ~IQS626_MISC_A_ATI_LP_ONLY; + if (device_property_present(&client->dev, "azoteq,ati-lp-only")) + sys_reg->misc_a |= IQS626_MISC_A_ATI_LP_ONLY; + + if (!device_property_read_u32(&client->dev, "azoteq,gpio3-select", + &val)) { + if (val > IQS626_MISC_A_GPIO3_SELECT_MAX) { + dev_err(&client->dev, "Invalid GPIO3 selection: %u\n", + val); + return -EINVAL; + } + + sys_reg->misc_a &= ~IQS626_MISC_A_GPIO3_SELECT_MASK; + sys_reg->misc_a |= val; + } + + if (!device_property_read_u32(&client->dev, "azoteq,reseed-select", + &val)) { + if (val > IQS626_MISC_B_RESEED_UI_SEL_MAX) { + dev_err(&client->dev, "Invalid reseed selection: %u\n", + val); + return -EINVAL; + } + + sys_reg->misc_b &= ~IQS626_MISC_B_RESEED_UI_SEL_MASK; + sys_reg->misc_b |= (val << IQS626_MISC_B_RESEED_UI_SEL_SHIFT); + } + + sys_reg->misc_b &= ~IQS626_MISC_B_THRESH_EXTEND; + if (device_property_present(&client->dev, "azoteq,thresh-extend")) + sys_reg->misc_b |= IQS626_MISC_B_THRESH_EXTEND; + + sys_reg->misc_b &= ~IQS626_MISC_B_TRACKING_UI_ENABLE; + if (device_property_present(&client->dev, "azoteq,tracking-enable")) + sys_reg->misc_b |= IQS626_MISC_B_TRACKING_UI_ENABLE; + + sys_reg->misc_b &= ~IQS626_MISC_B_RESEED_OFFSET; + if (device_property_present(&client->dev, "azoteq,reseed-offset")) + sys_reg->misc_b |= IQS626_MISC_B_RESEED_OFFSET; + + if (!device_property_read_u32(&client->dev, "azoteq,rate-np-ms", + &val)) { + if (val > IQS626_RATE_NP_MS_MAX) { + dev_err(&client->dev, "Invalid report rate: %u\n", val); + return -EINVAL; + } + + sys_reg->rate_np = val; + } + + if (!device_property_read_u32(&client->dev, "azoteq,rate-lp-ms", + &val)) { + if (val > IQS626_RATE_LP_MS_MAX) { + dev_err(&client->dev, "Invalid report rate: %u\n", val); + return -EINVAL; + } + + sys_reg->rate_lp = val; + } + + if (!device_property_read_u32(&client->dev, "azoteq,rate-ulp-ms", + &val)) { + if (val > IQS626_RATE_ULP_MS_MAX) { + dev_err(&client->dev, "Invalid report rate: %u\n", val); + return -EINVAL; + } + + sys_reg->rate_ulp = val / 16; + } + + if (!device_property_read_u32(&client->dev, "azoteq,timeout-pwr-ms", + &val)) { + if (val > IQS626_TIMEOUT_PWR_MS_MAX) { + dev_err(&client->dev, "Invalid timeout: %u\n", val); + return -EINVAL; + } + + sys_reg->timeout_pwr = val / 512; + } + + if (!device_property_read_u32(&client->dev, "azoteq,timeout-lta-ms", + &val)) { + if (val > IQS626_TIMEOUT_LTA_MS_MAX) { + dev_err(&client->dev, "Invalid timeout: %u\n", val); + return -EINVAL; + } + + sys_reg->timeout_lta = val / 512; + } + + sys_reg->event_mask = ~((u8)IQS626_EVENT_MASK_SYS); + sys_reg->redo_ati = 0; + + sys_reg->reseed = 0; + sys_reg->active = 0; + + for (i = 0; i < ARRAY_SIZE(iqs626_channels); i++) { + ch_node = device_get_named_child_node(&client->dev, + iqs626_channels[i].name); + if (!ch_node) + continue; + + error = iqs626_parse_channel(iqs626, ch_node, i); + if (error) + return error; + + error = iqs626_parse_ati_target(iqs626, ch_node, i); + if (error) + return error; + + error = iqs626_parse_events(iqs626, ch_node, i); + if (error) + return error; + + if (!fwnode_property_present(ch_node, "azoteq,ati-exclude")) + sys_reg->redo_ati |= iqs626_channels[i].active; + + if (!fwnode_property_present(ch_node, "azoteq,reseed-disable")) + sys_reg->reseed |= iqs626_channels[i].active; + + sys_reg->active |= iqs626_channels[i].active; + } + + general |= IQS626_SYS_SETTINGS_EVENT_MODE; + + /* + * Enable streaming during normal-power mode if the trackpad is used to + * report raw coordinates instead of gestures. In that case, the device + * returns to event mode during low-power mode. + */ + if (sys_reg->active & iqs626_channels[IQS626_CH_TP_2].active && + sys_reg->event_mask & IQS626_EVENT_MASK_GESTURE) + general |= IQS626_SYS_SETTINGS_EVENT_MODE_LP; + + general |= IQS626_SYS_SETTINGS_REDO_ATI; + general |= IQS626_SYS_SETTINGS_ACK_RESET; + + sys_reg->general = cpu_to_be16(general); + + error = regmap_raw_write(iqs626->regmap, IQS626_SYS_SETTINGS, + &iqs626->sys_reg, sizeof(iqs626->sys_reg)); + if (error) + return error; + + iqs626_irq_wait(); + + return 0; +} + +static int iqs626_input_init(struct iqs626_private *iqs626) +{ + struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg; + struct i2c_client *client = iqs626->client; + int error, i, j; + + iqs626->keypad = devm_input_allocate_device(&client->dev); + if (!iqs626->keypad) + return -ENOMEM; + + iqs626->keypad->keycodemax = ARRAY_SIZE(iqs626->kp_code); + iqs626->keypad->keycode = iqs626->kp_code; + iqs626->keypad->keycodesize = sizeof(**iqs626->kp_code); + + iqs626->keypad->name = "iqs626a_keypad"; + iqs626->keypad->id.bustype = BUS_I2C; + + for (i = 0; i < ARRAY_SIZE(iqs626_channels); i++) { + if (!(sys_reg->active & iqs626_channels[i].active)) + continue; + + for (j = 0; j < ARRAY_SIZE(iqs626_events); j++) { + if (!iqs626->kp_type[i][j]) + continue; + + input_set_capability(iqs626->keypad, + iqs626->kp_type[i][j], + iqs626->kp_code[i][j]); + } + } + + if (!(sys_reg->active & iqs626_channels[IQS626_CH_TP_2].active)) + return 0; + + iqs626->trackpad = devm_input_allocate_device(&client->dev); + if (!iqs626->trackpad) + return -ENOMEM; + + iqs626->trackpad->keycodemax = ARRAY_SIZE(iqs626->tp_code); + iqs626->trackpad->keycode = iqs626->tp_code; + iqs626->trackpad->keycodesize = sizeof(*iqs626->tp_code); + + iqs626->trackpad->name = "iqs626a_trackpad"; + iqs626->trackpad->id.bustype = BUS_I2C; + + /* + * Present the trackpad as a traditional pointing device if no gestures + * have been mapped to a keycode. + */ + if (sys_reg->event_mask & IQS626_EVENT_MASK_GESTURE) { + u8 tp_mask = iqs626_channels[IQS626_CH_TP_3].active; + + input_set_capability(iqs626->trackpad, EV_KEY, BTN_TOUCH); + input_set_abs_params(iqs626->trackpad, ABS_Y, 0, 255, 0, 0); + + if ((sys_reg->active & tp_mask) == tp_mask) + input_set_abs_params(iqs626->trackpad, + ABS_X, 0, 255, 0, 0); + else + input_set_abs_params(iqs626->trackpad, + ABS_X, 0, 128, 0, 0); +#ifdef CONFIG_TOUCHSCREEN_PROPERTIES + touchscreen_parse_properties(iqs626->trackpad, false, + &iqs626->prop); +#endif /* CONFIG_TOUCHSCREEN_PROPERTIES */ + } else { + for (i = 0; i < IQS626_NUM_GESTURES; i++) + if (iqs626->tp_code[i] != KEY_RESERVED) + input_set_capability(iqs626->trackpad, EV_KEY, + iqs626->tp_code[i]); + } + + error = input_register_device(iqs626->trackpad); + if (error) + dev_err(&client->dev, "Failed to register trackpad: %d\n", + error); + + return error; +} + +static int iqs626_report(struct iqs626_private *iqs626) +{ + struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg; + struct i2c_client *client = iqs626->client; + struct iqs626_flags flags; + __le16 hall_output; + int error, i, j; + u8 state; + u8 *dir_mask = &flags.states[IQS626_ST_OFFS_DIR]; + + error = regmap_raw_read(iqs626->regmap, IQS626_SYS_FLAGS, &flags, + sizeof(flags)); + if (error) { + dev_err(&client->dev, "Failed to read device status: %d\n", + error); + return error; + } + + /* + * The device resets itself if its own watchdog bites, which can happen + * in the event of an I2C communication error. In this case, the device + * asserts a SHOW_RESET interrupt and all registers must be restored. + */ + if (be16_to_cpu(flags.system) & IQS626_SYS_FLAGS_SHOW_RESET) { + dev_err(&client->dev, "Unexpected device reset\n"); + + error = regmap_raw_write(iqs626->regmap, IQS626_SYS_SETTINGS, + sys_reg, sizeof(*sys_reg)); + if (error) + dev_err(&client->dev, + "Failed to re-initialize device: %d\n", error); + + return error; + } + + if (be16_to_cpu(flags.system) & IQS626_SYS_FLAGS_IN_ATI) + return 0; + + /* + * Unlike the ULP or generic channels, the Hall channel does not have a + * direction flag. Instead, the direction (i.e. magnet polarity) can be + * derived based on the sign of the 2's complement differential output. + */ + if (sys_reg->active & iqs626_channels[IQS626_CH_HALL].active) { + error = regmap_raw_read(iqs626->regmap, IQS626_HALL_OUTPUT, + &hall_output, sizeof(hall_output)); + if (error) { + dev_err(&client->dev, + "Failed to read Hall output: %d\n", error); + return error; + } + + *dir_mask &= ~iqs626_channels[IQS626_CH_HALL].active; + if (le16_to_cpu(hall_output) < 0x8000) + *dir_mask |= iqs626_channels[IQS626_CH_HALL].active; + } + + for (i = 0; i < ARRAY_SIZE(iqs626_channels); i++) { + if (!(sys_reg->active & iqs626_channels[i].active)) + continue; + + for (j = 0; j < ARRAY_SIZE(iqs626_events); j++) { + if (!iqs626->kp_type[i][j]) + continue; + + state = flags.states[iqs626_events[j].st_offs]; + state &= iqs626_events[j].dir_up ? *dir_mask + : ~(*dir_mask); + state &= iqs626_channels[i].active; + + input_event(iqs626->keypad, iqs626->kp_type[i][j], + iqs626->kp_code[i][j], !!state); + } + } + + input_sync(iqs626->keypad); + + /* + * The following completion signals that ATI has finished, any initial + * switch states have been reported and the keypad can be registered. + */ + complete_all(&iqs626->ati_done); + + if (!(sys_reg->active & iqs626_channels[IQS626_CH_TP_2].active)) + return 0; + + if (sys_reg->event_mask & IQS626_EVENT_MASK_GESTURE) { + state = flags.states[IQS626_ST_OFFS_TOUCH]; + state &= iqs626_channels[IQS626_CH_TP_2].active; + + input_report_key(iqs626->trackpad, BTN_TOUCH, state); + + if (state) { +#ifdef CONFIG_TOUCHSCREEN_PROPERTIES + touchscreen_report_pos(iqs626->trackpad, &iqs626->prop, + flags.trackpad_x, + flags.trackpad_y, false); +#else + input_report_abs(iqs626->trackpad, ABS_X, + flags.trackpad_x); + input_report_abs(iqs626->trackpad, ABS_Y, + flags.trackpad_y); +#endif /* CONFIG_TOUCHSCREEN_PROPERTIES */ + } + } else { + for (i = 0; i < IQS626_NUM_GESTURES; i++) + input_report_key(iqs626->trackpad, iqs626->tp_code[i], + flags.gesture & BIT(i)); + + if (flags.gesture & GENMASK(IQS626_GESTURE_TAP, 0)) { + input_sync(iqs626->trackpad); + + /* + * Momentary gestures are followed by a complementary + * release cycle so as to emulate a full keystroke. + */ + for (i = 0; i < IQS626_GESTURE_HOLD; i++) + input_report_key(iqs626->trackpad, + iqs626->tp_code[i], 0); + } + } + + input_sync(iqs626->trackpad); + + return 0; +} + +static irqreturn_t iqs626_irq(int irq, void *context) +{ + struct iqs626_private *iqs626 = context; + + if (iqs626_report(iqs626)) + return IRQ_NONE; + + /* + * The device does not deassert its interrupt (RDY) pin until shortly + * after receiving an I2C stop condition; the following delay ensures + * the interrupt handler does not return before this time. + */ + iqs626_irq_wait(); + + return IRQ_HANDLED; +} + +static const struct regmap_config iqs626_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = IQS626_MAX_REG, +}; + +static int iqs626_probe(struct i2c_client *client) +{ + struct iqs626_ver_info ver_info; + struct iqs626_private *iqs626; + int error; + + iqs626 = devm_kzalloc(&client->dev, sizeof(*iqs626), GFP_KERNEL); + if (!iqs626) + return -ENOMEM; + + i2c_set_clientdata(client, iqs626); + iqs626->client = client; + + iqs626->regmap = devm_regmap_init_i2c(client, &iqs626_regmap_config); + if (IS_ERR(iqs626->regmap)) { + error = PTR_ERR(iqs626->regmap); + dev_err(&client->dev, "Failed to initialize register map: %d\n", + error); + return error; + } + + init_completion(&iqs626->ati_done); + + error = regmap_raw_read(iqs626->regmap, IQS626_VER_INFO, &ver_info, + sizeof(ver_info)); + if (error) + return error; + + if (ver_info.prod_num != IQS626_VER_INFO_PROD_NUM) { + dev_err(&client->dev, "Unrecognized product number: 0x%02X\n", + ver_info.prod_num); + return -EINVAL; + } + + error = iqs626_parse_prop(iqs626); + if (error) + return error; + + error = iqs626_input_init(iqs626); + if (error) + return error; + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, iqs626_irq, IRQF_ONESHOT, + client->name, iqs626); + if (error) { + dev_err(&client->dev, "Failed to request IRQ: %d\n", error); + return error; + } + + if (!wait_for_completion_timeout(&iqs626->ati_done, + msecs_to_jiffies(2000))) { + dev_err(&client->dev, "Failed to complete ATI\n"); + return -ETIMEDOUT; + } + + /* + * The keypad may include one or more switches and is not registered + * until ATI is complete and the initial switch states are read. + */ + error = input_register_device(iqs626->keypad); + if (error) + dev_err(&client->dev, "Failed to register keypad: %d\n", error); + + return error; +} + +static int __maybe_unused iqs626_suspend(struct device *dev) +{ + struct iqs626_private *iqs626 = dev_get_drvdata(dev); + struct i2c_client *client = iqs626->client; + unsigned int val; + int error; + + if (!iqs626->suspend_mode) + return 0; + + disable_irq(client->irq); + + /* + * Automatic power mode switching must be disabled before the device is + * forced into any particular power mode. In this case, the device will + * transition into normal-power mode. + */ + error = regmap_update_bits(iqs626->regmap, IQS626_SYS_SETTINGS, + IQS626_SYS_SETTINGS_DIS_AUTO, ~0); + if (error) + goto err_irq; + + /* + * The following check ensures the device has completed its transition + * into normal-power mode before a manual mode switch is performed. + */ + error = regmap_read_poll_timeout(iqs626->regmap, IQS626_SYS_FLAGS, val, + !(val & IQS626_SYS_FLAGS_PWR_MODE_MASK), + IQS626_PWR_MODE_POLL_SLEEP_US, + IQS626_PWR_MODE_POLL_TIMEOUT_US); + if (error) + goto err_irq; + + error = regmap_update_bits(iqs626->regmap, IQS626_SYS_SETTINGS, + IQS626_SYS_SETTINGS_PWR_MODE_MASK, + iqs626->suspend_mode << + IQS626_SYS_SETTINGS_PWR_MODE_SHIFT); + if (error) + goto err_irq; + + /* + * This last check ensures the device has completed its transition into + * the desired power mode to prevent any spurious interrupts from being + * triggered after iqs626_suspend has already returned. + */ + error = regmap_read_poll_timeout(iqs626->regmap, IQS626_SYS_FLAGS, val, + (val & IQS626_SYS_FLAGS_PWR_MODE_MASK) + == (iqs626->suspend_mode << + IQS626_SYS_FLAGS_PWR_MODE_SHIFT), + IQS626_PWR_MODE_POLL_SLEEP_US, + IQS626_PWR_MODE_POLL_TIMEOUT_US); + +err_irq: + iqs626_irq_wait(); + enable_irq(client->irq); + + return error; +} + +static int __maybe_unused iqs626_resume(struct device *dev) +{ + struct iqs626_private *iqs626 = dev_get_drvdata(dev); + struct i2c_client *client = iqs626->client; + unsigned int val; + int error; + + if (!iqs626->suspend_mode) + return 0; + + disable_irq(client->irq); + + error = regmap_update_bits(iqs626->regmap, IQS626_SYS_SETTINGS, + IQS626_SYS_SETTINGS_PWR_MODE_MASK, 0); + if (error) + goto err_irq; + + /* + * This check ensures the device has returned to normal-power mode + * before automatic power mode switching is re-enabled. + */ + error = regmap_read_poll_timeout(iqs626->regmap, IQS626_SYS_FLAGS, val, + !(val & IQS626_SYS_FLAGS_PWR_MODE_MASK), + IQS626_PWR_MODE_POLL_SLEEP_US, + IQS626_PWR_MODE_POLL_TIMEOUT_US); + if (error) + goto err_irq; + + error = regmap_update_bits(iqs626->regmap, IQS626_SYS_SETTINGS, + IQS626_SYS_SETTINGS_DIS_AUTO, 0); + if (error) + goto err_irq; + + /* + * This step reports any events that may have been "swallowed" as a + * result of polling PWR_MODE (which automatically acknowledges any + * pending interrupts). + */ + error = iqs626_report(iqs626); + +err_irq: + iqs626_irq_wait(); + enable_irq(client->irq); + + return error; +} + +static SIMPLE_DEV_PM_OPS(iqs626_pm, iqs626_suspend, iqs626_resume); + +static const struct of_device_id iqs626_of_match[] = { + { .compatible = "azoteq,iqs626a" }, + { } +}; +MODULE_DEVICE_TABLE(of, iqs626_of_match); + +static struct i2c_driver iqs626_i2c_driver = { + .driver = { + .name = "iqs626a", + .of_match_table = iqs626_of_match, + .pm = &iqs626_pm, + }, + .probe_new = iqs626_probe, +}; +module_i2c_driver(iqs626_i2c_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS626A Capacitive Touch Controller"); +MODULE_LICENSE("GPL");