From patchwork Mon Jan 6 00:48:00 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11318563 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id AC2B41580 for ; Mon, 6 Jan 2020 00:48:06 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 8A7602072C for ; Mon, 6 Jan 2020 00:48:06 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="pQhm2jnZ" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727219AbgAFAsF (ORCPT ); Sun, 5 Jan 2020 19:48:05 -0500 Received: from mail-bn7nam10on2049.outbound.protection.outlook.com ([40.107.92.49]:37536 "EHLO NAM10-BN7-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726526AbgAFAsF (ORCPT ); Sun, 5 Jan 2020 19:48:05 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=SICotShTzgOVVz1o2DNePI4mJF/X0Z9+xUDikkCoXrf28Estqdm01/2eIJL3H9ny4HFgFg9rTBgDRsVgnuTCGuJLf0ZhdXY0SZbeLrKRgF/cna01d7UKEYB70bqqlWvXLiDxRjTDgiUS8W2ibwqWPBv0P7wiq8wWBVwo6VzsUyYx7quN0tHwS1w2tl61iFXhdpqdc304Z9UkhJLdOxFaFGWnmgv/7HcNnKH3x0RpqOoumHoOeACNROgV3L5b9+hSUO3Wq13aKBl2gqB+DdYSu3veFqFt67bGj1i6PtFxktrjaq+7vYjdu/QTJElE6JLxTj1ZBMU0e4zcaYWCvoiG2A== 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=/zQC0VT4l86d5QngFn0BjHs17Cgbwru8u0K68dAOLnM=; b=DOgEB5f9uhdavQKAQxFTRUCKCwKZe3z5ovzsz3z0x17nzjcQkTLtCzA+or4oWq6SPXzzRi4Y8527SVbNsipPatHwWHxbACylllIYxKJ8RFLe3D7yh+3SGH0sil6G46k1e4V7Mm0eC4xzEzuwL6GuOvur6AzYLhWejocg+HP/4Uv+w6i3fMPwfD2wEz5ji2+XPtSGzRdcCRDXpQfCCqzVZ9s6H5U3jMggnWxllUO3HQrYvw81ckKnTl3HSTNPoawInIWGxS/3tvKmVGCT5Z3CJ14eGOBJspFczKkoOr15icoh8etR1NMNWRLkfpU1cUSwUPcih6+PHvRE5NdkVHxQ4g== 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=/zQC0VT4l86d5QngFn0BjHs17Cgbwru8u0K68dAOLnM=; b=pQhm2jnZdYuXyBwcG4u2VCC03LYqEAYff3FbyTKFgd49w4Ta8X3Iq8BECl0HR/Ph9im/fxXEy9jcWnrH7TiJlZeQUZNQg5khp/iarX16AWqPjmJG4fcJDnqrkGSh5tByNiit7yQXjKHUoqCG0doNOAdoeOERl8Ou/FbIc1mRIcs= Received: from SN6PR08MB5053.namprd08.prod.outlook.com (52.135.107.153) by SN6PR08MB4175.namprd08.prod.outlook.com (52.135.69.156) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2602.12; Mon, 6 Jan 2020 00:48:00 +0000 Received: from SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139]) by SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139%4]) with mapi id 15.20.2602.015; Mon, 6 Jan 2020 00:48:00 +0000 Received: from localhost.localdomain (136.49.227.119) by SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.2602.13 via Frontend Transport; Mon, 6 Jan 2020 00:47:59 +0000 From: Jeff LaBundy To: "lee.jones@linaro.org" , "dmitry.torokhov@gmail.com" , "thierry.reding@gmail.com" , "jic23@kernel.org" , "devicetree@vger.kernel.org" CC: "linux-input@vger.kernel.org" , "u.kleine-koenig@pengutronix.de" , "linux-pwm@vger.kernel.org" , "knaack.h@gmx.de" , "lars@metafoo.de" , "pmeerw@pmeerw.net" , "linux-iio@vger.kernel.org" , "robh+dt@kernel.org" , "mark.rutland@arm.com" , Jeff LaBundy Subject: [PATCH v3 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625 Thread-Topic: [PATCH v3 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625 Thread-Index: AQHVxCrx9mZdEz3euEisil4Ehb2Abw== Date: Mon, 6 Jan 2020 00:48:00 +0000 Message-ID: <1578271620-2159-2-git-send-email-jeff@labundy.com> References: <1578271620-2159-1-git-send-email-jeff@labundy.com> In-Reply-To: <1578271620-2159-1-git-send-email-jeff@labundy.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-clientproxiedby: SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) To SN6PR08MB5053.namprd08.prod.outlook.com (2603:10b6:805:78::25) authentication-results: spf=none (sender IP is ) smtp.mailfrom=jeff@labundy.com; x-ms-exchange-messagesentrepresentingtype: 1 x-mailer: git-send-email 2.7.4 x-originating-ip: [136.49.227.119] x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: b1f9e005-d2c1-4c74-8dcc-08d792421416 x-ms-traffictypediagnostic: SN6PR08MB4175: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:8273; x-forefront-prvs: 0274272F87 x-forefront-antispam-report: SFV:NSPM;SFS:(10009020)(366004)(396003)(39830400003)(136003)(376002)(346002)(34096005)(199004)(189003)(36756003)(107886003)(2616005)(956004)(316002)(69590400006)(5660300002)(86362001)(6486002)(508600001)(2906002)(4326008)(966005)(6506007)(81166006)(110136005)(6512007)(54906003)(16526019)(26005)(8676002)(186003)(7416002)(8936002)(66476007)(71200400001)(6666004)(52116002)(66946007)(30864003)(66446008)(66556008)(64756008)(81156014);DIR:OUT;SFP:1101;SCL:1;SRVR:SN6PR08MB4175;H:SN6PR08MB5053.namprd08.prod.outlook.com;FPR:;SPF:None;LANG:en;PTR:InfoNoRecords;A:1;MX:1; received-spf: None (protection.outlook.com: labundy.com does not designate permitted sender hosts) x-ms-exchange-senderadcheck: 1 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: /g0AGbqWGWuFCKWonsHKDCEOdVda28NEHO6PwEmiyy8DZkmI/8EjGlY12EZdE5CMMpF8HJRFLIRpArlM3quElePGVR46Woo9aRqTCOyNqG5T2Yn0WNIYjRUqQUXf1WPSHuaKU8eUbDG0uoDXTcEpssH9wc1t/xeIGGYtQ35TIw/1TfIqswi9A/aezB2XyL0guy2TpBC/awDFAW0jSC33JgwZh2jR5B68pilNimiyh+Ai+v1/IMPYAN9s0rXHJBDSB5Uq75wOIaKMH/XvdUC+nlm9XsqW4yvjB8+Tgm2lGZUrPZVO5DQmo7b1zomrFjKATOPL6GN9NSPpq9/HyMhz5hWT1znVgFsq4prVE77wSuuUS56vdnpLxhOPCZx5GZKN4BOM68xi1Awi2kwY13BebdAW9k233EgDXf/eJMDhdk/CSMmBIAONCtH+GXqjIrNjPIH0siLhmDfv1KepFpqX3wLVbHLU2ViZQWEf8wc6Ym6rxMfAsJXSFR1FQPckg0PcngEJEGBOFSajuioHLXfZ8hwtO3mMzWuuK6zprIALXJM= MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: b1f9e005-d2c1-4c74-8dcc-08d792421416 X-MS-Exchange-CrossTenant-originalarrivaltime: 06 Jan 2020 00:48:00.1002 (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: 7Atw29+0CB5RoKPy+FTTwkzIOfpUVTnY0NLjrOmlpXtw/tHhZ3tGCmyeVjQd9WE3/0AI2wLjVS4D7HyODlwnJw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN6PR08MB4175 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds device tree bindings for the Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors. A total of three bindings are presented (one MFD and two child nodes); they are submitted as a single patch because the child node bindings have no meaning in the absence of the MFD binding. Signed-off-by: Jeff LaBundy Reviewed-by: Rob Herring --- Changes in v3: - Specified 'additionalProperties: false' within the parent MFD node and all child nodes ("keys", "hall-switch-north/south" and "pwm") - Defined the "hall-switch-north/south" child nodes unconditionally and then inverted the subsequent if/then to filter them from devices for which that functionality is unavailable - Added Reviewed-by trailer Changes in v2: - Removed "prox" child node and moved "keys" and "pwm" child nodes to their own bindings - Replaced linux,fw-file property with more common firmware-name property - Converted all bindings to YAML .../devicetree/bindings/input/iqs62x-keys.yaml | 132 +++++++++++++++ Documentation/devicetree/bindings/mfd/iqs62x.yaml | 179 +++++++++++++++++++++ .../devicetree/bindings/pwm/iqs620a-pwm.yaml | 32 ++++ 3 files changed, 343 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/iqs62x-keys.yaml create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.yaml create mode 100644 Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml -- 2.7.4 diff --git a/Documentation/devicetree/bindings/input/iqs62x-keys.yaml b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml new file mode 100644 index 0000000..5625c22 --- /dev/null +++ b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/iqs62x-keys.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Azoteq IQS620A/621/622/624/625 Keys and Switches + +maintainers: + - Jeff LaBundy + +description: | + The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors + feature a variety of self-capacitive, mutual-inductive and Hall-effect sens- + ing capabilities that can facilitate a variety of contactless key and switch + applications. + + These functions are collectively represented by a "keys" child node from the + parent MFD driver. See Documentation/devicetree/bindings/mfd/iqs62x.yaml for + further details and examples. Sensor hardware configuration (self-capacitive + vs. mutual-inductive, etc.) is selected based on the device's firmware. + +properties: + compatible: + enum: + - azoteq,iqs620a-keys + - azoteq,iqs621-keys + - azoteq,iqs622-keys + - azoteq,iqs624-keys + - azoteq,iqs625-keys + + linux,keycodes: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32-array + - minItems: 1 + maxItems: 16 + description: | + Specifies the numeric keycodes associated with each available touch or + proximity event according to the following table. An 'x' indicates the + event is supported for a given device. Specify 0 for unused events. + + ------------------------------------------------------------------------- + | # | Event | IQS620A | IQS621 | IQS622 | IQS624 | IQS625 | + ------------------------------------------------------------------------- + | 0 | CH0 Touch | x | x | x | x | x | + | | Antenna 1 Touch* | x | | | | | + ------------------------------------------------------------------------- + | 1 | CH0 Proximity | x | x | x | x | x | + | | Antenna 1 Prox.* | x | | | | | + ------------------------------------------------------------------------- + | 2 | CH1 Touch | x | x | x | x | x | + | | Ant. 1 Deep Touch* | x | | | | | + ------------------------------------------------------------------------- + | 3 | CH1 Proximity | x | x | x | x | x | + ------------------------------------------------------------------------- + | 4 | CH2 Touch | x | | | | | + ------------------------------------------------------------------------- + | 5 | CH2 Proximity | x | | | | | + | | Antenna 2 Prox.* | x | | | | | + ------------------------------------------------------------------------- + | 6 | Metal (+) Touch** | x | x | | | | + | | Ant. 2 Deep Touch* | x | | | | | + ------------------------------------------------------------------------- + | 7 | Metal (+) Prox.** | x | x | | | | + | | Antenna 2 Touch* | x | | | | | + ------------------------------------------------------------------------- + | 8 | Metal (-) Touch** | x | x | | | | + ------------------------------------------------------------------------- + | 9 | Metal (-) Prox.** | x | x | | | | + ------------------------------------------------------------------------- + | 10 | SAR Active*** | x | | x | | | + ------------------------------------------------------------------------- + | 11 | SAR Quick Rel.*** | x | | x | | | + ------------------------------------------------------------------------- + | 12 | SAR Movement*** | x | | x | | | + ------------------------------------------------------------------------- + | 13 | SAR Filter Halt*** | x | | x | | | + ------------------------------------------------------------------------- + | 14 | Wheel Up | | | | x | | + ------------------------------------------------------------------------- + | 15 | Wheel Down | | | | x | | + ------------------------------------------------------------------------- + * Two-channel SAR. Replaces CH0-2 plus metal touch and proximity events + if enabled via firmware. + ** "+" and "-" refer to the polarity of a channel's delta (LTA - counts), + where "LTA" is defined as the channel's long-term average. + *** One-channel SAR. Replaces CH0-2 touch and proximity events if enabled + via firmware. + +patternProperties: + "^hall-switch-(north|south)$": + type: object + description: + Represents north/south-field Hall-effect sensor touch or proximity + events. Note that north/south-field orientation is reversed on the + IQS620AXzCSR device due to its flip-chip package. + + properties: + linux,code: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Numeric switch code associated with the event. + + azoteq,use-prox: + $ref: /schemas/types.yaml#/definitions/flag + description: + If present, specifies that Hall-effect sensor reporting should + use the device's wide-range proximity threshold instead of its + close-range touch threshold (default). + + required: + - linux,code + + additionalProperties: false + +if: + properties: + compatible: + contains: + enum: + - azoteq,iqs624-keys + - azoteq,iqs625-keys +then: + patternProperties: + "^hall-switch-(north|south)$": false + +required: + - compatible + - linux,keycodes + +additionalProperties: false + +... diff --git a/Documentation/devicetree/bindings/mfd/iqs62x.yaml b/Documentation/devicetree/bindings/mfd/iqs62x.yaml new file mode 100644 index 0000000..46b7272 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/iqs62x.yaml @@ -0,0 +1,179 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/iqs62x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Azoteq IQS620A/621/622/624/625 Multi-Function Sensors + +maintainers: + - Jeff LaBundy + +description: | + The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors + integrate multiple sensing technologies in a single package. + + Link to data sheets: https://www.azoteq.com/ + +properties: + compatible: + enum: + - azoteq,iqs620a + - azoteq,iqs621 + - azoteq,iqs622 + - azoteq,iqs624 + - azoteq,iqs625 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + firmware-name: + $ref: /schemas/types.yaml#/definitions/string + description: + Specifies the name of the calibration and configuration file selected by + the driver. If this property is omitted, the name is chosen based on the + device name with ".bin" as the extension (e.g. iqs620a.bin for IQS620A). + + keys: + $ref: ../input/iqs62x-keys.yaml + + pwm: + $ref: ../pwm/iqs620a-pwm.yaml + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + /* + * Dual capacitive buttons with additional "air button," unipolar lid + * switch and panel-mounted LED. + */ + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + iqs620a@44 { + compatible = "azoteq,iqs620a"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_LEVEL_LOW>; + + keys { + compatible = "azoteq,iqs620a-keys"; + + linux,keycodes = , + , + , + ; + + hall-switch-south { + linux,code = ; + azoteq,use-prox; + }; + }; + + iqs620a_pwm: pwm { + compatible = "azoteq,iqs620a-pwm"; + #pwm-cells = <2>; + }; + }; + }; + + pwmleds { + compatible = "pwm-leds"; + + panel { + pwms = <&iqs620a_pwm 0 1000000>; + max-brightness = <255>; + }; + }; + + - | + /* Single inductive button with bipolar dock/tablet-mode switch. */ + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + iqs620a@44 { + compatible = "azoteq,iqs620a"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_LEVEL_LOW>; + + firmware-name = "iqs620a_coil.bin"; + + keys { + compatible = "azoteq,iqs620a-keys"; + + linux,keycodes = <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + ; + + hall-switch-north { + linux,code = ; + }; + + hall-switch-south { + linux,code = ; + }; + }; + }; + }; + + - | + /* Dual capacitive buttons with volume knob. */ + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + iqs624@44 { + compatible = "azoteq,iqs624"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_LEVEL_LOW>; + + keys { + compatible = "azoteq,iqs624-keys"; + + linux,keycodes = , + <0>, + , + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + , + ; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml new file mode 100644 index 0000000..1d7c27b --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/iqs620a-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Azoteq IQS620A PWM Generator + +maintainers: + - Jeff LaBundy + +description: | + The Azoteq IQS620A multi-function sensor generates a fixed-frequency PWM + output represented by a "pwm" child node from the parent MFD driver. See + Documentation/devicetree/bindings/mfd/iqs62x.yaml for further details as + well as an example. + +properties: + compatible: + enum: + - azoteq,iqs620a-pwm + + "#pwm-cells": + const: 2 + +required: + - compatible + - "#pwm-cells" + +additionalProperties: false + +... From patchwork Mon Jan 6 00:48:01 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11318585 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id CE085930 for ; Mon, 6 Jan 2020 00:49:58 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 84B6C21775 for ; Mon, 6 Jan 2020 00:49:58 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="lZKdGnIu" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727188AbgAFAt6 (ORCPT ); Sun, 5 Jan 2020 19:49:58 -0500 Received: from mail-bn7nam10on2049.outbound.protection.outlook.com ([40.107.92.49]:37536 "EHLO NAM10-BN7-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726496AbgAFAt5 (ORCPT ); Sun, 5 Jan 2020 19:49:57 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=nzd59wxnPgBpUcevL11Z6tKHECn/d460armqe+QHBJub3zp99ZYTit37rY5B2QFXv80RPI/U3ElNu7MF98zcL552iBTSXGSl9SzbtX1bSyYf5qbnoyxPw7TjOkbC46734TaQFEfI5h0XIDM9EU9tRMbYB2e/iI8WCWXbhMCkJanD8dW49xhxPBlBkzIaR40pA/P99F/h/uKDdkpXJaNRT4n9amoafQe+Fv1vSMB7dseacrm5vzm1N1StFU8WWdPauA188IRr2lioGRsKhImzwb5j5cZt0Q9mBXq04wHOZqOgbsBxKMBFQcZGGOE4OEb4TjS+ybiQaw54qPVKTeB7CQ== 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=1iZiM8qGgUUEIF81JYQLpWesllsuSq0zcpBpby5U/bI=; b=dblGgbvXURlYHjJMsJJMuKinlCpmGspByVHwojqnGTPoAEN4jFV8djOCjzaezy4Z1naDtptrw7jMLFd6sOrnV3DgQPIiNdR5Z1tbqn8LIYkmtoRxHnkKhRSXPozCI485UB2cc/ImhsZMF0UwjQoaOcTkiZV2CRukCigg3l7TraNyMM57bxvzFvrEGlcW+px/HXNGJDipyoCtpWY9+qf6lovhX2uxwpPQzhcMa76CnHxp+DJswQ6VBHd1nuKcVr2XMEY+TloFTUklyG7rylbcLlYbAVHnOIo9qmgUBMfLsQagzYGqVQjvUGmhXiUXvyUGSQKMsDb/F1kQldgbLTEd9A== 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=1iZiM8qGgUUEIF81JYQLpWesllsuSq0zcpBpby5U/bI=; b=lZKdGnIuH1zysdpX4zWKkeON+WouqEu8NJdZq2T1cO2Qp8b6u1fBq7i1DLb6nrMWTwEsK9JIYSIvfWtZderEhPOvd+6/CWkgd2fE5pLvpEUW2KYJGPIzWcU2eZe2+g7W7jgtR2ET3D2SdpFNESeo3lPsh7gF88OzrJJotgg6TkI= Received: from SN6PR08MB5053.namprd08.prod.outlook.com (52.135.107.153) by SN6PR08MB4175.namprd08.prod.outlook.com (52.135.69.156) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2602.12; Mon, 6 Jan 2020 00:48:01 +0000 Received: from SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139]) by SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139%4]) with mapi id 15.20.2602.015; Mon, 6 Jan 2020 00:48:01 +0000 Received: from localhost.localdomain (136.49.227.119) by SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.2602.13 via Frontend Transport; Mon, 6 Jan 2020 00:48:00 +0000 From: Jeff LaBundy To: "lee.jones@linaro.org" , "dmitry.torokhov@gmail.com" , "thierry.reding@gmail.com" , "jic23@kernel.org" , "devicetree@vger.kernel.org" CC: "linux-input@vger.kernel.org" , "u.kleine-koenig@pengutronix.de" , "linux-pwm@vger.kernel.org" , "knaack.h@gmx.de" , "lars@metafoo.de" , "pmeerw@pmeerw.net" , "linux-iio@vger.kernel.org" , "robh+dt@kernel.org" , "mark.rutland@arm.com" , Jeff LaBundy Subject: [PATCH v3 2/7] mfd: Add support for Azoteq IQS620A/621/622/624/625 Thread-Topic: [PATCH v3 2/7] mfd: Add support for Azoteq IQS620A/621/622/624/625 Thread-Index: AQHVxCryVpXgH6aBsECz/tAHc0+mhA== Date: Mon, 6 Jan 2020 00:48:01 +0000 Message-ID: <1578271620-2159-3-git-send-email-jeff@labundy.com> References: <1578271620-2159-1-git-send-email-jeff@labundy.com> In-Reply-To: <1578271620-2159-1-git-send-email-jeff@labundy.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-clientproxiedby: SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) To SN6PR08MB5053.namprd08.prod.outlook.com (2603:10b6:805:78::25) authentication-results: spf=none (sender IP is ) smtp.mailfrom=jeff@labundy.com; x-ms-exchange-messagesentrepresentingtype: 1 x-mailer: git-send-email 2.7.4 x-originating-ip: [136.49.227.119] x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: c1d4cf27-3aee-42ba-7f61-08d7924214a8 x-ms-traffictypediagnostic: SN6PR08MB4175: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:7691; x-forefront-prvs: 0274272F87 x-forefront-antispam-report: SFV:NSPM;SFS:(10009020)(366004)(396003)(39830400003)(136003)(376002)(346002)(34096005)(38354002)(60444003)(199004)(189003)(36756003)(107886003)(2616005)(956004)(316002)(69590400006)(5660300002)(86362001)(6486002)(508600001)(2906002)(4326008)(966005)(6506007)(81166006)(110136005)(6512007)(54906003)(16526019)(26005)(8676002)(186003)(7416002)(8936002)(66476007)(71200400001)(52116002)(66946007)(30864003)(66446008)(66556008)(64756008)(81156014)(461764006)(579004)(559001)(569006);DIR:OUT;SFP:1101;SCL:1;SRVR:SN6PR08MB4175;H:SN6PR08MB5053.namprd08.prod.outlook.com;FPR:;SPF:None;LANG:en;PTR:InfoNoRecords;A:1;MX:1; received-spf: None (protection.outlook.com: labundy.com does not designate permitted sender hosts) x-ms-exchange-senderadcheck: 1 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: UYemyWLD+gypPU5nqQzf4HCBPwPeJI7tFsB/Dt2s6C8I1mOQ2Ut4UrKUvvmQPieMHSOFF0cduhyzrPbqOOLLMH/Z1KCMsxNwUpEuRi3FZ6aZG399lreKzHLnBsM/7da4yt0vP/Lsl4AiputvBHaDJZMIT5VIhYU5siV93dYP2be/q443axSqcQciaOtTIk3jgfcrV6328sraXj6B3wYcuJj8WeAOrGp+GYBlSNo/v2LetbTziHJDUQ3+dSux1xDdTL1mS7B9Kbd6QRv09MKXHGv57QiYLFf5/+kDCrXLt74P/13mA4CeXml/RO2aSc+py2UHTLfwh09L+4jatuNATd00dGYaVd4rQGi4I+KtZ+l+sbiT8BYeofpivd36UFg0Ix4cCXLpH4yDZojHDwTSElriXSkn/9FOOiKmgflvbzpuySVjGLCrb3KREPcag8jMNqom6J6dVukG8L7zaEG2f78Mue+NwobhZtphzcR1Cy3rN8yrmkgTaXY2NfTbjKzhEU6ntz6DjxVXBDqhd2SgPLZA8lz0y+xsxMAsKr6IsUgmL/LXxD85JFnhTasrqxPVO36E/OvHNkFzXOGhbpsWhn0qVC5qRBqSaMKcBMlKZAWW40Sapiyvak7bQ35zhPB0 MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: c1d4cf27-3aee-42ba-7f61-08d7924214a8 X-MS-Exchange-CrossTenant-originalarrivaltime: 06 Jan 2020 00:48:01.1046 (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: UwZP0mdmfI1/a+JFytewuYuIHMAOBtwB1Hxa2LjiUNz2O3L01E01aLGL7JZCClUGQs55SGhnDaJse6DBuepdYA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN6PR08MB4175 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds core support for the Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors. Signed-off-by: Jeff LaBundy --- Changes in v3: - None Changes in v2: - Merged 'Copyright' and 'Author' lines into one in introductory comments - Replaced 'error' with 'ret' throughout - Updated iqs62x_dev_init to account for 4/8/16-MHz clock divider in start-up delays and replaced ATI timeout routine with regmap_read_poll_timeout - Added an error message to iqs62x_irq in case device status fails to be read - Replaced sw_num member of iqs62x_core with a local variable in iqs62x_probe as the former was unused anywhere else - Added comments throughout iqs62x_probe to clarify how devices are matched based on the presence of calibration data - Inverted the product and software number comparison logic in iqs62x_probe to avoid an else...continue branch - Changed iqs62x_probe from .probe callback to .probe_new callback, thereby eliminating the otherwise unused iqs62x_id array - Moved iqs62x_suspend and iqs62x_resume below iqs62x_remove - Eliminated tabbed alignment of regmap_config and i2c_driver struct members - Added register definitions for register addresses used in iqs621_cal_regs, iqs620at_cal_regs and iqs62x_devs arrays - Removed of_compatible string from IQS622 mfd_cell struct as its proximity (now ambient light) sensing functionality need not be represented using a child node - Dissolved union in iqs62x_event_data to allow simultaneous use of ir_flags and als_flags - Removed temp_flags member of iqs62x_event_data, IQS62X_EVENT_TEMP register enumeration and IQS62X_EVENT_UI_HI/LO from iqs620a_event_regs (thereby re- ducing IQS62X_EVENT_SIZE to 10) as they were unused drivers/mfd/Kconfig | 13 + drivers/mfd/Makefile | 3 + drivers/mfd/iqs62x-core.c | 639 ++++++++++++++++++++++++++++++++++++++++++++ drivers/mfd/iqs62x-tables.c | 438 ++++++++++++++++++++++++++++++ include/linux/mfd/iqs62x.h | 146 ++++++++++ 5 files changed, 1239 insertions(+) create mode 100644 drivers/mfd/iqs62x-core.c create mode 100644 drivers/mfd/iqs62x-tables.c create mode 100644 include/linux/mfd/iqs62x.h -- 2.7.4 diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 4209008..151984c 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -642,6 +642,19 @@ config MFD_IPAQ_MICRO AT90LS8535 microcontroller flashed with a special iPAQ firmware using the custom protocol implemented in this driver. +config MFD_IQS62X + tristate "Azoteq IQS620A/621/622/624/625 core support" + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + Say Y here if you want to build core support for the Azoteq IQS620A, + IQS621, IQS622, IQS624 and IQS625 multi-function sensors. Additional + options must be selected to enable device-specific functions. + + To compile this driver as a module, choose M here: the module will + be called iqs62x. + config MFD_JANZ_CMODIO tristate "Janz CMOD-IO PCI MODULbus Carrier Board" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index aed99f0..c4fc26b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -232,6 +232,9 @@ obj-$(CONFIG_MFD_DLN2) += dln2.o obj-$(CONFIG_MFD_RT5033) += rt5033.o obj-$(CONFIG_MFD_SKY81452) += sky81452.o +iqs62x-objs := iqs62x-core.o iqs62x-tables.o +obj-$(CONFIG_MFD_IQS62X) += iqs62x.o + intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o obj-$(CONFIG_INTEL_SOC_PMIC_BXTWC) += intel_soc_pmic_bxtwc.o diff --git a/drivers/mfd/iqs62x-core.c b/drivers/mfd/iqs62x-core.c new file mode 100644 index 0000000..767f9d8 --- /dev/null +++ b/drivers/mfd/iqs62x-core.c @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors + * + * Copyright (C) 2019 Jeff LaBundy + * + * These devices rely on application-specific register settings and calibration + * data developed in and exported from a suite of GUIs offered by the vendor. A + * separate tool converts the GUIs' ASCII-based output into a standard firmware + * file parsed by the driver. + * + * Link to data sheets and GUIs: https://www.azoteq.com/ + * + * Link to conversion tool: https://github.com/jlabundy/iqs62x-h2bin.git + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define IQS62X_PROD_NUM 0x00 + +#define IQS62X_SYS_FLAGS 0x10 +#define IQS62X_SYS_FLAGS_IN_ATI BIT(2) + +#define IQS622_PROX_SETTINGS_4 0x48 +#define IQS620_PROX_SETTINGS_4 0x50 +#define IQS620_PROX_SETTINGS_4_SAR_EN BIT(7) + +#define IQS62X_SYS_SETTINGS 0xD0 +#define IQS62X_SYS_SETTINGS_SOFT_RESET BIT(7) +#define IQS62X_SYS_SETTINGS_ACK_RESET BIT(6) +#define IQS62X_SYS_SETTINGS_EVENT_MODE BIT(5) +#define IQS62X_SYS_SETTINGS_CLK_DIV BIT(4) +#define IQS62X_SYS_SETTINGS_REDO_ATI BIT(1) + +#define IQS62X_PWR_SETTINGS 0xD2 +#define IQS62X_PWR_SETTINGS_DIS_AUTO BIT(5) +#define IQS62X_PWR_SETTINGS_PWR_MODE_MASK (BIT(4) | BIT(3)) +#define IQS62X_PWR_SETTINGS_PWR_MODE_HALT (BIT(4) | BIT(3)) +#define IQS62X_PWR_SETTINGS_PWR_MODE_NORM 0 + +#define IQS62X_OTP_CMD 0xF0 +#define IQS62X_OTP_CMD_FG3 0x13 +#define IQS62X_OTP_DATA 0xF1 +#define IQS62X_MAX_REG 0xFF + +#define IQS62X_HALL_CAL_MASK GENMASK(3, 0) + +#define IQS62X_FW_REC_TYPE_INFO 0 +#define IQS62X_FW_REC_TYPE_PROD 1 +#define IQS62X_FW_REC_TYPE_HALL 2 +#define IQS62X_FW_REC_TYPE_MASK 3 +#define IQS62X_FW_REC_TYPE_DATA 4 + +struct iqs62x_fw_rec { + u8 type; + u8 addr; + u8 len; + u8 data; +} __packed; + +struct iqs62x_fw_blk { + struct list_head list; + u8 addr; + u8 mask; + u8 len; + u8 data[]; +}; + +struct iqs62x_info { + u8 prod_num; + u8 sw_num; + u8 hw_num; +} __packed; + +static int iqs62x_dev_init(struct iqs62x_core *iqs62x) +{ + struct iqs62x_fw_blk *fw_blk; + unsigned int val; + int ret; + u8 clk_div = 1; + + list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) { + if (fw_blk->mask) + ret = regmap_update_bits(iqs62x->map, fw_blk->addr, + fw_blk->mask, *fw_blk->data); + else + ret = regmap_raw_write(iqs62x->map, fw_blk->addr, + fw_blk->data, fw_blk->len); + if (ret) + return ret; + } + + switch (iqs62x->dev_desc->prod_num) { + case IQS620_PROD_NUM: + case IQS622_PROD_NUM: + ret = regmap_read(iqs62x->map, iqs62x->dev_desc->prod_num == + IQS620_PROD_NUM ? IQS620_PROX_SETTINGS_4 : + IQS622_PROX_SETTINGS_4, + &val); + if (ret) + return ret; + + if (val & IQS620_PROX_SETTINGS_4_SAR_EN) + iqs62x->ui_sel = IQS62X_UI_SAR1; + /* fall through */ + + case IQS621_PROD_NUM: + ret = regmap_write(iqs62x->map, IQS620_GLBL_EVENT_MASK, + IQS620_GLBL_EVENT_MASK_PMU | + iqs62x->dev_desc->prox_mask | + iqs62x->dev_desc->sar_mask | + iqs62x->dev_desc->hall_mask | + iqs62x->dev_desc->hyst_mask | + iqs62x->dev_desc->temp_mask | + iqs62x->dev_desc->als_mask | + iqs62x->dev_desc->ir_mask); + if (ret) + return ret; + break; + + default: + ret = regmap_write(iqs62x->map, IQS624_HALL_UI, + IQS624_HALL_UI_WHL_EVENT | + IQS624_HALL_UI_INT_EVENT | + IQS624_HALL_UI_AUTO_CAL); + if (ret) + return ret; + + ret = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV, &val); + if (ret) + return ret; + + if (val >= iqs62x->dev_desc->interval_div) + break; + + ret = regmap_write(iqs62x->map, IQS624_INTERVAL_DIV, + iqs62x->dev_desc->interval_div); + if (ret) + return ret; + } + + ret = regmap_read(iqs62x->map, IQS62X_SYS_SETTINGS, &val); + if (ret) + return ret; + + if (val & IQS62X_SYS_SETTINGS_CLK_DIV) + clk_div = iqs62x->dev_desc->clk_div; + + ret = regmap_write(iqs62x->map, IQS62X_SYS_SETTINGS, val | + IQS62X_SYS_SETTINGS_ACK_RESET | + IQS62X_SYS_SETTINGS_EVENT_MODE | + IQS62X_SYS_SETTINGS_REDO_ATI); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(iqs62x->map, IQS62X_SYS_FLAGS, val, + !(val & IQS62X_SYS_FLAGS_IN_ATI), + 10000, clk_div * 500000); + if (ret) + return ret; + + /* + * The following delay accommodates the post-ATI stabilization time + * specified in the data sheet (with additional margin). + */ + msleep(clk_div * 150); + + return 0; +} + +static int iqs62x_fw_prs(struct iqs62x_core *iqs62x, const struct firmware *fw) +{ + struct i2c_client *client = iqs62x->client; + struct iqs62x_fw_rec *fw_rec; + struct iqs62x_fw_blk *fw_blk; + unsigned int val; + size_t pos = 0; + int ret = 0; + u8 mask, len, *data; + u8 hall_cal_index = 0; + + while (pos < fw->size) { + if (pos + sizeof(*fw_rec) > fw->size) { + ret = -EINVAL; + break; + } + fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos); + pos += sizeof(*fw_rec); + + if (pos + fw_rec->len - 1 > fw->size) { + ret = -EINVAL; + break; + } + pos += fw_rec->len - 1; + + switch (fw_rec->type) { + case IQS62X_FW_REC_TYPE_INFO: + continue; + + case IQS62X_FW_REC_TYPE_PROD: + if (fw_rec->data == iqs62x->dev_desc->prod_num) + continue; + + dev_err(&client->dev, + "Incompatible product number: 0x%02X\n", + fw_rec->data); + ret = -EINVAL; + break; + + case IQS62X_FW_REC_TYPE_HALL: + if (!hall_cal_index) { + ret = regmap_write(iqs62x->map, IQS62X_OTP_CMD, + IQS62X_OTP_CMD_FG3); + if (ret) + break; + + ret = regmap_read(iqs62x->map, IQS62X_OTP_DATA, + &val); + if (ret) + break; + + hall_cal_index = val & IQS62X_HALL_CAL_MASK; + if (!hall_cal_index) { + dev_err(&client->dev, + "Uncalibrated device\n"); + ret = -ENODATA; + break; + } + } + + if (hall_cal_index > fw_rec->len) { + ret = -EINVAL; + break; + } + + mask = 0; + data = &fw_rec->data + hall_cal_index - 1; + len = sizeof(*data); + break; + + case IQS62X_FW_REC_TYPE_MASK: + if (fw_rec->len < (sizeof(mask) + sizeof(*data))) { + ret = -EINVAL; + break; + } + + mask = fw_rec->data; + data = &fw_rec->data + sizeof(mask); + len = sizeof(*data); + break; + + case IQS62X_FW_REC_TYPE_DATA: + mask = 0; + data = &fw_rec->data; + len = fw_rec->len; + break; + + default: + dev_err(&client->dev, + "Unrecognized record type: 0x%02X\n", + fw_rec->type); + ret = -EINVAL; + } + + if (ret) + break; + + fw_blk = devm_kzalloc(&client->dev, + struct_size(fw_blk, data, len), + GFP_KERNEL); + if (!fw_blk) { + ret = -ENOMEM; + break; + } + + fw_blk->addr = fw_rec->addr; + fw_blk->mask = mask; + fw_blk->len = len; + memcpy(fw_blk->data, data, len); + + list_add(&fw_blk->list, &iqs62x->fw_blk_head); + } + + release_firmware(fw); + + return ret; +} + +static irqreturn_t iqs62x_irq(int irq, void *context) +{ + struct iqs62x_core *iqs62x = context; + struct i2c_client *client = iqs62x->client; + struct iqs62x_event_data event_data; + struct iqs62x_event_desc event_desc; + enum iqs62x_event_reg event_reg; + unsigned long event_flags = 0; + int ret, i, j; + u8 event_map[IQS62X_EVENT_SIZE]; + + /* + * The device asserts the RDY output to signal the beginning of a + * communication window, which is closed by an I2C stop condition. + * As such, all interrupt status is captured in a single read and + * broadcast to any interested sub-device drivers. + */ + ret = regmap_raw_read(iqs62x->map, IQS62X_SYS_FLAGS, event_map, + sizeof(event_map)); + if (ret) { + dev_err(&client->dev, "Failed to read device status: %d\n", + ret); + return IRQ_NONE; + } + + for (i = 0; i < sizeof(event_map); i++) { + event_reg = iqs62x->dev_desc->event_regs[iqs62x->ui_sel][i]; + + switch (event_reg) { + case IQS62X_EVENT_UI_LO: + event_data.ui_data = get_unaligned_le16(&event_map[i]); + /* fall through */ + case IQS62X_EVENT_UI_HI: + case IQS62X_EVENT_NONE: + continue; + + case IQS62X_EVENT_ALS: + event_data.als_flags = event_map[i]; + continue; + + case IQS62X_EVENT_IR: + event_data.ir_flags = event_map[i]; + continue; + + case IQS62X_EVENT_INTER: + event_data.interval = event_map[i]; + continue; + + case IQS62X_EVENT_HYST: + event_map[i] <<= iqs62x->dev_desc->hyst_shift; + /* fall through */ + case IQS62X_EVENT_WHEEL: + case IQS62X_EVENT_HALL: + case IQS62X_EVENT_PROX: + case IQS62X_EVENT_SYS: + break; + } + + for (j = 0; j < IQS62X_NUM_EVENTS; j++) { + event_desc = iqs62x_events[j]; + + if (event_desc.reg != event_reg) + continue; + + if ((event_map[i] & event_desc.mask) == event_desc.val) + event_flags |= BIT(j); + } + } + + /* + * The device resets itself in response to the I2C master stalling + * communication past a fixed timeout. In this case, all registers + * are restored and any interested sub-device drivers are notified. + */ + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + dev_err(&client->dev, "Unexpected device reset\n"); + + ret = iqs62x_dev_init(iqs62x); + if (ret) { + dev_err(&client->dev, + "Failed to re-initialize device: %d\n", ret); + return IRQ_NONE; + } + } + + ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags, + &event_data); + if (ret & NOTIFY_STOP_MASK) + return IRQ_NONE; + + /* + * Once the communication window is closed, a small delay is added to + * ensure the device's RDY output has been deasserted by the time the + * interrupt handler returns. + */ + usleep_range(50, 100); + + return IRQ_HANDLED; +} + +static void iqs62x_fw_cb(const struct firmware *fw, void *context) +{ + struct iqs62x_core *iqs62x = context; + struct i2c_client *client = iqs62x->client; + int ret; + + if (fw) { + ret = iqs62x_fw_prs(iqs62x, fw); + if (ret) { + dev_err(&client->dev, "Failed to parse firmware: %d\n", + ret); + goto err_out; + } + } + + ret = iqs62x_dev_init(iqs62x); + if (ret) { + dev_err(&client->dev, "Failed to initialize device: %d\n", ret); + goto err_out; + } + + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, iqs62x_irq, IRQF_ONESHOT, + client->name, iqs62x); + if (ret) { + dev_err(&client->dev, "Failed to request IRQ: %d\n", ret); + goto err_out; + } + + ret = devm_mfd_add_devices(&client->dev, -1, + iqs62x->dev_desc->sub_devs, + iqs62x->dev_desc->num_sub_devs, + NULL, 0, NULL); + if (ret) + dev_err(&client->dev, "Failed to add devices: %d\n", ret); + +err_out: + complete_all(&iqs62x->fw_done); +} + +static const struct regmap_config iqs62x_map_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = IQS62X_MAX_REG, +}; + +static int iqs62x_probe(struct i2c_client *client) +{ + struct iqs62x_core *iqs62x; + struct iqs62x_info info; + unsigned int val; + int ret, i, j; + u8 sw_num = 0; + const char *fw_name = NULL; + + iqs62x = devm_kzalloc(&client->dev, sizeof(*iqs62x), GFP_KERNEL); + if (!iqs62x) + return -ENOMEM; + + i2c_set_clientdata(client, iqs62x); + iqs62x->client = client; + + BLOCKING_INIT_NOTIFIER_HEAD(&iqs62x->nh); + INIT_LIST_HEAD(&iqs62x->fw_blk_head); + init_completion(&iqs62x->fw_done); + + iqs62x->map = devm_regmap_init_i2c(client, &iqs62x_map_config); + if (IS_ERR(iqs62x->map)) { + ret = PTR_ERR(iqs62x->map); + dev_err(&client->dev, "Failed to initialize register map: %d\n", + ret); + return ret; + } + + ret = regmap_raw_read(iqs62x->map, IQS62X_PROD_NUM, &info, + sizeof(info)); + if (ret) + return ret; + + /* + * The following sequence validates the device's product and software + * numbers. It then determines if the device is factory-calibrated by + * checking for nonzero values in the device's designated calibration + * registers (if applicable). Depending on the device, the absence of + * calibration data indicates a reduced feature set or invalid device. + * + * For devices given in both calibrated and uncalibrated versions, the + * calibrated version (e.g. IQS620AT) appears first in the iqs62x_devs + * array. The uncalibrated version (e.g. IQS620A) appears next and has + * the same product and software numbers, but no calibration registers + * are specified. + */ + for (i = 0; i < IQS62X_NUM_DEV; i++) { + if (info.prod_num != iqs62x_devs[i].prod_num) + continue; + iqs62x->dev_desc = &iqs62x_devs[i]; + + if (info.sw_num < iqs62x->dev_desc->sw_num) + continue; + sw_num = info.sw_num; + + /* + * Read each of the device's designated calibration registers, + * if any, and exit from the inner loop early if any are equal + * to zero. + */ + for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) { + ret = regmap_read(iqs62x->map, + iqs62x->dev_desc->cal_regs[j], &val); + if (ret) + return ret; + + if (!val) + break; + } + + /* + * If the number of nonzero values read from the device equals + * the number of designated calibration registers (which could + * be zero), exit from the outer loop early to signal a device + * has been matched. + */ + if (j == iqs62x->dev_desc->num_cal_regs) + break; + } + + if (!iqs62x->dev_desc) { + dev_err(&client->dev, "Unrecognized product number: 0x%02X\n", + info.prod_num); + return -EINVAL; + } + + if (!sw_num) { + dev_err(&client->dev, "Unrecognized software number: 0x%02X\n", + info.sw_num); + return -EINVAL; + } + + if (i == IQS62X_NUM_DEV) { + dev_err(&client->dev, "Uncalibrated device\n"); + return -ENODATA; + } + + ret = regmap_write(iqs62x->map, IQS62X_SYS_SETTINGS, + IQS62X_SYS_SETTINGS_SOFT_RESET); + if (ret) + return ret; + usleep_range(10000, 10100); + + device_property_read_string(&client->dev, "firmware-name", &fw_name); + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fw_name ? : iqs62x->dev_desc->fw_name, + &client->dev, GFP_KERNEL, iqs62x, + iqs62x_fw_cb); + if (ret) + dev_err(&client->dev, "Failed to request firmware: %d\n", ret); + + return ret; +} + +static int iqs62x_remove(struct i2c_client *client) +{ + struct iqs62x_core *iqs62x = i2c_get_clientdata(client); + + wait_for_completion(&iqs62x->fw_done); + + return 0; +} + +static int __maybe_unused iqs62x_suspend(struct device *dev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(dev); + int ret; + + wait_for_completion(&iqs62x->fw_done); + + /* + * As per the data sheet, automatic mode switching must be disabled + * before the device is placed in or taken out of halt mode. + */ + ret = regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_DIS_AUTO, + IQS62X_PWR_SETTINGS_DIS_AUTO); + if (ret) + return ret; + + return regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_PWR_MODE_MASK, + IQS62X_PWR_SETTINGS_PWR_MODE_HALT); +} + +static int __maybe_unused iqs62x_resume(struct device *dev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_PWR_MODE_MASK, + IQS62X_PWR_SETTINGS_PWR_MODE_NORM); + if (ret) + return ret; + + return regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_DIS_AUTO, 0); +} + +static SIMPLE_DEV_PM_OPS(iqs62x_pm, iqs62x_suspend, iqs62x_resume); + +static const struct of_device_id iqs62x_of_match[] = { + { .compatible = "azoteq,iqs620a" }, + { .compatible = "azoteq,iqs621" }, + { .compatible = "azoteq,iqs622" }, + { .compatible = "azoteq,iqs624" }, + { .compatible = "azoteq,iqs625" }, + { } +}; +MODULE_DEVICE_TABLE(of, iqs62x_of_match); + +static struct i2c_driver iqs62x_i2c_driver = { + .driver = { + .name = "iqs62x", + .of_match_table = iqs62x_of_match, + .pm = &iqs62x_pm, + }, + .probe_new = iqs62x_probe, + .remove = iqs62x_remove, +}; +module_i2c_driver(iqs62x_i2c_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Multi-Function Sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/iqs62x-tables.c b/drivers/mfd/iqs62x-tables.c new file mode 100644 index 0000000..580f6ac --- /dev/null +++ b/drivers/mfd/iqs62x-tables.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#include +#include +#include + +#define IQS620_HALL_FLAGS 0x16 +#define IQS620_TEMP_CAL_MULT 0xC2 +#define IQS620_TEMP_CAL_DIV 0xC3 +#define IQS620_TEMP_CAL_OFFS 0xC4 + +#define IQS621_HALL_FLAGS 0x19 +#define IQS621_ALS_CAL_DIV_LUX 0x82 +#define IQS621_ALS_CAL_DIV_IR 0x83 + +#define IQS622_HALL_FLAGS IQS621_HALL_FLAGS + +#define IQS624_INTERVAL_NUM 0x18 +#define IQS625_INTERVAL_NUM 0x12 + +static const struct mfd_cell iqs620at_sub_devs[] = { + { + .name = IQS62X_DRV_NAME_KEYS, + .of_compatible = "azoteq,iqs620a-keys", + }, + { + .name = IQS620_DRV_NAME_PWM, + .of_compatible = "azoteq,iqs620a-pwm", + }, + { + .name = IQS620_DRV_NAME_TEMP, + }, +}; + +static const struct mfd_cell iqs620a_sub_devs[] = { + { + .name = IQS62X_DRV_NAME_KEYS, + .of_compatible = "azoteq,iqs620a-keys", + }, + { + .name = IQS620_DRV_NAME_PWM, + .of_compatible = "azoteq,iqs620a-pwm", + }, +}; + +static const struct mfd_cell iqs621_sub_devs[] = { + { + .name = IQS62X_DRV_NAME_KEYS, + .of_compatible = "azoteq,iqs621-keys", + }, + { + .name = IQS621_DRV_NAME_ALS, + }, +}; + +static const struct mfd_cell iqs622_sub_devs[] = { + { + .name = IQS62X_DRV_NAME_KEYS, + .of_compatible = "azoteq,iqs622-keys", + }, + { + .name = IQS621_DRV_NAME_ALS, + }, +}; + +static const struct mfd_cell iqs624_sub_devs[] = { + { + .name = IQS62X_DRV_NAME_KEYS, + .of_compatible = "azoteq,iqs624-keys", + }, + { + .name = IQS624_DRV_NAME_POS, + }, +}; + +static const struct mfd_cell iqs625_sub_devs[] = { + { + .name = IQS62X_DRV_NAME_KEYS, + .of_compatible = "azoteq,iqs625-keys", + }, + { + .name = IQS624_DRV_NAME_POS, + }, +}; + +static const u8 iqs620at_cal_regs[] = { + IQS620_TEMP_CAL_MULT, + IQS620_TEMP_CAL_DIV, + IQS620_TEMP_CAL_OFFS, +}; + +static const u8 iqs621_cal_regs[] = { + IQS621_ALS_CAL_DIV_LUX, + IQS621_ALS_CAL_DIV_IR, +}; + +static const enum iqs62x_event_reg iqs620a_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_HALL, /* 0x16 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, + [IQS62X_UI_SAR1] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_HALL, /* 0x16 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, +}; + +static const enum iqs62x_event_reg iqs621_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_ALS, /* 0x16 */ + IQS62X_EVENT_UI_LO, /* 0x17 */ + IQS62X_EVENT_UI_HI, /* 0x18 */ + IQS62X_EVENT_HALL, /* 0x19 */ + }, +}; + +static const enum iqs62x_event_reg iqs622_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_ALS, /* 0x14 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_IR, /* 0x16 */ + IQS62X_EVENT_UI_LO, /* 0x17 */ + IQS62X_EVENT_UI_HI, /* 0x18 */ + IQS62X_EVENT_HALL, /* 0x19 */ + }, + [IQS62X_UI_SAR1] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_ALS, /* 0x14 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_IR, /* 0x16 */ + IQS62X_EVENT_UI_LO, /* 0x17 */ + IQS62X_EVENT_UI_HI, /* 0x18 */ + IQS62X_EVENT_HALL, /* 0x19 */ + }, +}; + +static const enum iqs62x_event_reg iqs624_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_WHEEL, /* 0x14 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_UI_LO, /* 0x16 */ + IQS62X_EVENT_UI_HI, /* 0x17 */ + IQS62X_EVENT_INTER, /* 0x18 */ + IQS62X_EVENT_NONE, + }, +}; + +static const enum iqs62x_event_reg iqs625_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_PROX, /* 0x11 */ + IQS62X_EVENT_INTER, /* 0x12 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, +}; + +enum { + IQS620AT_DEV, + IQS620A_DEV, + IQS621_DEV, + IQS622_DEV, + IQS624_DEV, + IQS625_DEV, +}; + +const struct iqs62x_dev_desc iqs62x_devs[IQS62X_NUM_DEV] = { + [IQS620AT_DEV] = { + .dev_name = "iqs620at", + .sub_devs = iqs620at_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs620at_sub_devs), + + .prod_num = IQS620_PROD_NUM, + .sw_num = 0x08, + .cal_regs = iqs620at_cal_regs, + .num_cal_regs = ARRAY_SIZE(iqs620at_cal_regs), + + .prox_mask = BIT(0), + .sar_mask = BIT(1) | BIT(7), + .hall_mask = BIT(2), + .hyst_mask = BIT(3), + .temp_mask = BIT(4), + + .hall_flags = IQS620_HALL_FLAGS, + + .clk_div = 4, + .fw_name = "iqs620a.bin", + .event_regs = &iqs620a_event_regs[IQS62X_UI_PROX], + }, + [IQS620A_DEV] = { + .dev_name = "iqs620a", + .sub_devs = iqs620a_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs620a_sub_devs), + + .prod_num = IQS620_PROD_NUM, + .sw_num = 0x08, + + .prox_mask = BIT(0), + .sar_mask = BIT(1) | BIT(7), + .hall_mask = BIT(2), + .hyst_mask = BIT(3), + .temp_mask = BIT(4), + + .hall_flags = IQS620_HALL_FLAGS, + + .clk_div = 4, + .fw_name = "iqs620a.bin", + .event_regs = &iqs620a_event_regs[IQS62X_UI_PROX], + }, + [IQS621_DEV] = { + .dev_name = "iqs621", + .sub_devs = iqs621_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs621_sub_devs), + + .prod_num = IQS621_PROD_NUM, + .sw_num = 0x09, + .cal_regs = iqs621_cal_regs, + .num_cal_regs = ARRAY_SIZE(iqs621_cal_regs), + + .prox_mask = BIT(0), + .hall_mask = BIT(1), + .als_mask = BIT(2), + .hyst_mask = BIT(3), + .temp_mask = BIT(4), + + .als_flags = IQS621_ALS_FLAGS, + .hall_flags = IQS621_HALL_FLAGS, + .hyst_shift = 5, + + .clk_div = 2, + .fw_name = "iqs621.bin", + .event_regs = &iqs621_event_regs[IQS62X_UI_PROX], + }, + [IQS622_DEV] = { + .dev_name = "iqs622", + .sub_devs = iqs622_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs622_sub_devs), + + .prod_num = IQS622_PROD_NUM, + .sw_num = 0x06, + + .prox_mask = BIT(0), + .sar_mask = BIT(1), + .hall_mask = BIT(2), + .als_mask = BIT(3), + .ir_mask = BIT(4), + + .als_flags = IQS622_ALS_FLAGS, + .hall_flags = IQS622_HALL_FLAGS, + + .clk_div = 2, + .fw_name = "iqs622.bin", + .event_regs = &iqs622_event_regs[IQS62X_UI_PROX], + }, + [IQS624_DEV] = { + .dev_name = "iqs624", + .sub_devs = iqs624_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs624_sub_devs), + + .prod_num = IQS624_PROD_NUM, + .sw_num = 0x0B, + + .interval = IQS624_INTERVAL_NUM, + .interval_div = 3, + + .clk_div = 2, + .fw_name = "iqs624.bin", + .event_regs = &iqs624_event_regs[IQS62X_UI_PROX], + }, + [IQS625_DEV] = { + .dev_name = "iqs625", + .sub_devs = iqs625_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs625_sub_devs), + + .prod_num = IQS625_PROD_NUM, + .sw_num = 0x0B, + + .interval = IQS625_INTERVAL_NUM, + .interval_div = 10, + + .clk_div = 2, + .fw_name = "iqs625.bin", + .event_regs = &iqs625_event_regs[IQS62X_UI_PROX], + }, +}; +EXPORT_SYMBOL_GPL(iqs62x_devs); + +const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS] = { + [IQS62X_EVENT_PROX_CH0_T] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(4), + .val = BIT(4), + }, + [IQS62X_EVENT_PROX_CH0_P] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(0), + .val = BIT(0), + }, + [IQS62X_EVENT_PROX_CH1_T] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(5), + .val = BIT(5), + }, + [IQS62X_EVENT_PROX_CH1_P] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(1), + .val = BIT(1), + }, + [IQS62X_EVENT_PROX_CH2_T] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(6), + .val = BIT(6), + }, + [IQS62X_EVENT_PROX_CH2_P] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(2), + .val = BIT(2), + }, + [IQS62X_EVENT_HYST_POS_T] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(6) | BIT(7), + .val = BIT(6), + }, + [IQS62X_EVENT_HYST_POS_P] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(5) | BIT(7), + .val = BIT(5), + }, + [IQS62X_EVENT_HYST_NEG_T] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(6) | BIT(7), + .val = BIT(6) | BIT(7), + }, + [IQS62X_EVENT_HYST_NEG_P] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(5) | BIT(7), + .val = BIT(5) | BIT(7), + }, + [IQS62X_EVENT_SAR1_ACT] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(4), + .val = BIT(4), + }, + [IQS62X_EVENT_SAR1_QRD] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(2), + .val = BIT(2), + }, + [IQS62X_EVENT_SAR1_MOVE] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(1), + .val = BIT(1), + }, + [IQS62X_EVENT_SAR1_HALT] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(0), + .val = BIT(0), + }, + [IQS62X_EVENT_WHEEL_UP] = { + .reg = IQS62X_EVENT_WHEEL, + .mask = BIT(7) | BIT(6), + .val = BIT(7), + }, + [IQS62X_EVENT_WHEEL_DN] = { + .reg = IQS62X_EVENT_WHEEL, + .mask = BIT(7) | BIT(6), + .val = BIT(7) | BIT(6), + }, + [IQS62X_EVENT_HALL_N_T] = { + .reg = IQS62X_EVENT_HALL, + .mask = BIT(2) | BIT(0), + .val = BIT(2), + }, + [IQS62X_EVENT_HALL_N_P] = { + .reg = IQS62X_EVENT_HALL, + .mask = BIT(1) | BIT(0), + .val = BIT(1), + }, + [IQS62X_EVENT_HALL_S_T] = { + .reg = IQS62X_EVENT_HALL, + .mask = BIT(2) | BIT(0), + .val = BIT(2) | BIT(0), + }, + [IQS62X_EVENT_HALL_S_P] = { + .reg = IQS62X_EVENT_HALL, + .mask = BIT(1) | BIT(0), + .val = BIT(1) | BIT(0), + }, + [IQS62X_EVENT_SYS_RESET] = { + .reg = IQS62X_EVENT_SYS, + .mask = BIT(7), + .val = BIT(7), + }, +}; +EXPORT_SYMBOL_GPL(iqs62x_events); diff --git a/include/linux/mfd/iqs62x.h b/include/linux/mfd/iqs62x.h new file mode 100644 index 0000000..0dc5997 --- /dev/null +++ b/include/linux/mfd/iqs62x.h @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#ifndef __LINUX_MFD_IQS62X_H +#define __LINUX_MFD_IQS62X_H + +#define IQS620_PROD_NUM 0x41 +#define IQS621_PROD_NUM 0x46 +#define IQS622_PROD_NUM 0x42 +#define IQS624_PROD_NUM 0x43 +#define IQS625_PROD_NUM 0x4E + +#define IQS621_ALS_FLAGS 0x16 +#define IQS622_ALS_FLAGS 0x14 + +#define IQS624_HALL_UI 0x70 +#define IQS624_HALL_UI_WHL_EVENT BIT(4) +#define IQS624_HALL_UI_INT_EVENT BIT(3) +#define IQS624_HALL_UI_AUTO_CAL BIT(2) + +#define IQS624_INTERVAL_DIV 0x7D + +#define IQS620_GLBL_EVENT_MASK 0xD7 +#define IQS620_GLBL_EVENT_MASK_PMU BIT(6) + +#define IQS62X_NUM_DEV 6 +#define IQS62X_NUM_KEYS 16 +#define IQS62X_NUM_EVENTS (IQS62X_NUM_KEYS + 5) + +#define IQS62X_EVENT_SIZE 10 + +#define IQS62X_DRV_NAME_KEYS "iqs62x-keys" +#define IQS620_DRV_NAME_TEMP "iqs620at-temp" +#define IQS620_DRV_NAME_PWM "iqs620a-pwm" +#define IQS621_DRV_NAME_ALS "iqs621-als" +#define IQS624_DRV_NAME_POS "iqs624-pos" + +enum iqs62x_ui_sel { + IQS62X_UI_PROX, + IQS62X_UI_SAR1, +}; + +enum iqs62x_event_reg { + IQS62X_EVENT_NONE, + IQS62X_EVENT_SYS, + IQS62X_EVENT_PROX, + IQS62X_EVENT_HYST, + IQS62X_EVENT_HALL, + IQS62X_EVENT_ALS, + IQS62X_EVENT_IR, + IQS62X_EVENT_WHEEL, + IQS62X_EVENT_INTER, + IQS62X_EVENT_UI_LO, + IQS62X_EVENT_UI_HI, +}; + +enum iqs62x_event_flag { + /* keys */ + IQS62X_EVENT_PROX_CH0_T, + IQS62X_EVENT_PROX_CH0_P, + IQS62X_EVENT_PROX_CH1_T, + IQS62X_EVENT_PROX_CH1_P, + IQS62X_EVENT_PROX_CH2_T, + IQS62X_EVENT_PROX_CH2_P, + IQS62X_EVENT_HYST_POS_T, + IQS62X_EVENT_HYST_POS_P, + IQS62X_EVENT_HYST_NEG_T, + IQS62X_EVENT_HYST_NEG_P, + IQS62X_EVENT_SAR1_ACT, + IQS62X_EVENT_SAR1_QRD, + IQS62X_EVENT_SAR1_MOVE, + IQS62X_EVENT_SAR1_HALT, + IQS62X_EVENT_WHEEL_UP, + IQS62X_EVENT_WHEEL_DN, + + /* switches */ + IQS62X_EVENT_HALL_N_T, + IQS62X_EVENT_HALL_N_P, + IQS62X_EVENT_HALL_S_T, + IQS62X_EVENT_HALL_S_P, + + /* everything else */ + IQS62X_EVENT_SYS_RESET, +}; + +struct iqs62x_event_data { + u16 ui_data; + u8 als_flags; + u8 ir_flags; + u8 interval; +}; + +struct iqs62x_event_desc { + enum iqs62x_event_reg reg; + u8 mask; + u8 val; +}; + +struct iqs62x_dev_desc { + const char *dev_name; + const struct mfd_cell *sub_devs; + int num_sub_devs; + + u8 prod_num; + u8 sw_num; + const u8 *cal_regs; + int num_cal_regs; + + u8 prox_mask; + u8 sar_mask; + u8 hall_mask; + u8 hyst_mask; + u8 temp_mask; + u8 als_mask; + u8 ir_mask; + + u8 als_flags; + u8 hall_flags; + u8 hyst_shift; + + u8 interval; + u8 interval_div; + + u8 clk_div; + const char *fw_name; + const enum iqs62x_event_reg (*event_regs)[IQS62X_EVENT_SIZE]; +}; + +struct iqs62x_core { + const struct iqs62x_dev_desc *dev_desc; + struct i2c_client *client; + struct regmap *map; + struct blocking_notifier_head nh; + struct list_head fw_blk_head; + struct completion fw_done; + enum iqs62x_ui_sel ui_sel; +}; + +extern const struct iqs62x_dev_desc iqs62x_devs[IQS62X_NUM_DEV]; +extern const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS]; + +#endif /* __LINUX_MFD_IQS62X_H */ From patchwork Mon Jan 6 00:48:02 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11318571 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id A14411580 for ; Mon, 6 Jan 2020 00:48:42 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 61BB521775 for ; Mon, 6 Jan 2020 00:48:42 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="Ko6xbLyA" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727232AbgAFAsl (ORCPT ); Sun, 5 Jan 2020 19:48:41 -0500 Received: from mail-bn7nam10on2068.outbound.protection.outlook.com ([40.107.92.68]:6166 "EHLO NAM10-BN7-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726526AbgAFAsl (ORCPT ); Sun, 5 Jan 2020 19:48:41 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=nt7p5Gtev7iW0LQ7fulrIC82bj8nsFxPEu9/SV1zsX9fV6pzQzZxbebT8AZjpENdV5m2CWFNhWByN8jEfDB7teW+Zss8RthbMW5qAX5wQMF4I9KRFSbEOHQq2fYevZhUhIvOqNMPIBs7S73L7nMLxVop2eWEBCG+s5S0qTPk6AoqJ8JNinCRt9DftraCH50TdXnABvbE/WWFiVdqpLo63vMpCepc6GGaB+ruqnU5kjCvAPmlS9laPBQJvPQIowsAoUKIGhBqfiEJw4G31VZyWmjt7Ai0Bepb3plzf+dciDR9UVaj5XxWzqnjzS0GZUUDh7kJuw8LXrS+Xtc+bBz0Nw== 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=Li827rvCjFoukrwEuJCYFzKDpwnBcdSnmR/hT+FlhKk=; b=HbhImZJh0qQiO6jVoyMrVd+AKYv75jB1P45OmPfgzMFOh9J36FcgzCAa76+9Bz4BR9+JF//UMDuBvLlBVvmhoS6p+JW5sjTqfBMV9HBhLFmdTFNgzMcILw8Nalcty3+tuJhcGIVNsYhlPwO2qaW1ZQpfvmjVXcmZ99l6pFQNeQVwprCuigjeJ0awaCX6APgxmOxnYsPZJdAb1HrGdyg5D0qywXz2RxVcbqxO47RL95P6HsPQzVLFtJcCvkOt0ydA8Pjt7GMW+ufzJXuftPkcAsAJboQVxpLlAGNhZhM6sQnOqxOnGL9FqXexszrp2Pg9KuWReTBNhqS4Wd7GVFN//Q== 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=Li827rvCjFoukrwEuJCYFzKDpwnBcdSnmR/hT+FlhKk=; b=Ko6xbLyAT0p4oEhw6LocgV7AwIntFxMken4SiXqP8S01djjnPREWYWT2ucYCfHiXW10lUi2uKKS4AD5VJt5M2xrUeQ52pbgqbCVgFGhKo8EhR3d8a135cMIPFY5SoTYrlxYTGUs/jf1TSGS4EFhm6s8D1YwHIZ/YwAel+J/8EHA= Received: from SN6PR08MB5053.namprd08.prod.outlook.com (52.135.107.153) by SN6PR08MB4175.namprd08.prod.outlook.com (52.135.69.156) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2602.12; Mon, 6 Jan 2020 00:48:02 +0000 Received: from SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139]) by SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139%4]) with mapi id 15.20.2602.015; Mon, 6 Jan 2020 00:48:02 +0000 Received: from localhost.localdomain (136.49.227.119) by SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.2602.13 via Frontend Transport; Mon, 6 Jan 2020 00:48:01 +0000 From: Jeff LaBundy To: "lee.jones@linaro.org" , "dmitry.torokhov@gmail.com" , "thierry.reding@gmail.com" , "jic23@kernel.org" , "devicetree@vger.kernel.org" CC: "linux-input@vger.kernel.org" , "u.kleine-koenig@pengutronix.de" , "linux-pwm@vger.kernel.org" , "knaack.h@gmx.de" , "lars@metafoo.de" , "pmeerw@pmeerw.net" , "linux-iio@vger.kernel.org" , "robh+dt@kernel.org" , "mark.rutland@arm.com" , Jeff LaBundy Subject: [PATCH v3 3/7] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625 Thread-Topic: [PATCH v3 3/7] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625 Thread-Index: AQHVxCryRi1p6AWP10yXqEUZq0D5Iw== Date: Mon, 6 Jan 2020 00:48:02 +0000 Message-ID: <1578271620-2159-4-git-send-email-jeff@labundy.com> References: <1578271620-2159-1-git-send-email-jeff@labundy.com> In-Reply-To: <1578271620-2159-1-git-send-email-jeff@labundy.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-clientproxiedby: SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) To SN6PR08MB5053.namprd08.prod.outlook.com (2603:10b6:805:78::25) authentication-results: spf=none (sender IP is ) smtp.mailfrom=jeff@labundy.com; x-ms-exchange-messagesentrepresentingtype: 1 x-mailer: git-send-email 2.7.4 x-originating-ip: [136.49.227.119] x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: 3c5ee51d-6872-4f8a-cb47-08d792421546 x-ms-traffictypediagnostic: SN6PR08MB4175: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:3826; x-forefront-prvs: 0274272F87 x-forefront-antispam-report: SFV:NSPM;SFS:(10009020)(366004)(396003)(39830400003)(136003)(376002)(346002)(34096005)(199004)(189003)(36756003)(107886003)(2616005)(956004)(316002)(69590400006)(5660300002)(86362001)(6486002)(508600001)(2906002)(4326008)(6506007)(81166006)(110136005)(6512007)(54906003)(16526019)(26005)(8676002)(186003)(7416002)(8936002)(66476007)(71200400001)(52116002)(66946007)(30864003)(66446008)(66556008)(64756008)(81156014);DIR:OUT;SFP:1101;SCL:1;SRVR:SN6PR08MB4175;H:SN6PR08MB5053.namprd08.prod.outlook.com;FPR:;SPF:None;LANG:en;PTR:InfoNoRecords;A:1;MX:1; received-spf: None (protection.outlook.com: labundy.com does not designate permitted sender hosts) x-ms-exchange-senderadcheck: 1 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: X/eQ2guczIpmkIFjcXeVmCxx9/8nlGYsl409iMGwI+4SJmdHckrfCwLVDx6kvcfJHKVjXnEPVVj41zXSsssholrRWjZ+4GTcDjhRcau2vtHHfWO8slXsdZI4R6fMrxmBAmRi/RqMy2C7LvlOil1J32WHehu4zpyAoP3nUIGaWMEvRFghZDgg4cs/hISaprlXDn6gyX5P+s6Swg0mQR5MwD1IPO1b7lXPnfCN4CchHb83Rv6zLtZ0hfoOUfxTTIUxAlL4JEVfJyaKDBE+CIxZw5KgN/6A2LX9QxsrZiw9hMP0qFOM8NiiO5vKsKYXdOFHYXTKyolslOBDZbWMBEa2hc+BOCXolx96MJU5fJGq3shi1YxMsE91VU8BZYfQlQ8rsWEVO8b0mxxkre+uH1cwNGfaqy+b9qnhS14Mj8Qd3mIRhHslnUXAQIt2sugUbE6KaAY55q1K7BD8W6hYJKzdzNHt3juLiltD1wccTCo3EP0cji2DA75dl3H6o2WFru1u MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: 3c5ee51d-6872-4f8a-cb47-08d792421546 X-MS-Exchange-CrossTenant-originalarrivaltime: 06 Jan 2020 00:48:02.0531 (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: 3ngHQ8b+nmvu3rT9XAi8AGEFwTfdupAUx5inpWQ+EjPv/Mjf1Uoji7JIT7fZcOnYbw/eh50BISvcfpMlGa59bQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN6PR08MB4175 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds key and switch support for the Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors. Signed-off-by: Jeff LaBundy --- Changes in v3: - None Changes in v2: - Merged 'Copyright' and 'Author' lines into one in introductory comments - Replaced 'error' with 'ret' throughout - Updated iqs62x_keys_parse_prop to use unified device property interface - Clarified the comment in iqs62x_keys_notifier to state that wheel up or down events elicit an emulated release cycle - Eliminated tabbed alignment of platform_driver struct members drivers/input/keyboard/Kconfig | 10 ++ drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/iqs62x-keys.c | 340 +++++++++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 drivers/input/keyboard/iqs62x-keys.c -- 2.7.4 diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 4706ff0..28de965 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -663,6 +663,16 @@ config KEYBOARD_IPAQ_MICRO To compile this driver as a module, choose M here: the module will be called ipaq-micro-keys. +config KEYBOARD_IQS62X + tristate "Azoteq IQS620A/621/622/624/625 keys and switches" + depends on MFD_IQS62X + help + Say Y here to enable key and switch support for the Azoteq IQS620A, + IQS621, IQS622, IQS624 and IQS625 multi-function sensors. + + To compile this driver as a module, choose M here: the module will + be called iqs62x-keys. + config KEYBOARD_OMAP tristate "TI OMAP keypad support" depends on ARCH_OMAP1 diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index f5b1752..1d689fd 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_KEYBOARD_TCA8418) += tca8418_keypad.o obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o obj-$(CONFIG_KEYBOARD_IPAQ_MICRO) += ipaq-micro-keys.o +obj-$(CONFIG_KEYBOARD_IQS62X) += iqs62x-keys.o obj-$(CONFIG_KEYBOARD_IMX) += imx_keypad.o obj-$(CONFIG_KEYBOARD_IMX_SC_KEY) += imx_sc_key.o obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o diff --git a/drivers/input/keyboard/iqs62x-keys.c b/drivers/input/keyboard/iqs62x-keys.c new file mode 100644 index 0000000..b477334 --- /dev/null +++ b/drivers/input/keyboard/iqs62x-keys.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A/621/622/624/625 Keys and Switches + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + IQS62X_SW_HALL_N, + IQS62X_SW_HALL_S, +}; + +static const char * const iqs62x_switch_names[] = { + [IQS62X_SW_HALL_N] = "hall-switch-north", + [IQS62X_SW_HALL_S] = "hall-switch-south", +}; + +struct iqs62x_switch_desc { + enum iqs62x_event_flag flag; + unsigned int code; + bool enabled; +}; + +struct iqs62x_keys_private { + struct iqs62x_core *iqs62x; + struct input_dev *input; + struct notifier_block notifier; + struct iqs62x_switch_desc switches[ARRAY_SIZE(iqs62x_switch_names)]; + unsigned int keycode[IQS62X_NUM_KEYS]; + unsigned int keycodemax; + u8 interval; +}; + +static int iqs62x_keys_parse_prop(struct platform_device *pdev, + struct iqs62x_keys_private *iqs62x_keys) +{ + struct fwnode_handle *child; + unsigned int val; + int ret, i; + + ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes", + NULL, 0); + if (ret > IQS62X_NUM_KEYS) { + dev_err(&pdev->dev, "Too many keycodes present\n"); + return -EINVAL; + } else if (ret < 0) { + dev_err(&pdev->dev, "Failed to count keycodes: %d\n", ret); + return ret; + } + iqs62x_keys->keycodemax = ret; + + ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes", + iqs62x_keys->keycode, + iqs62x_keys->keycodemax); + if (ret) { + dev_err(&pdev->dev, "Failed to read keycodes: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) { + child = device_get_named_child_node(&pdev->dev, + iqs62x_switch_names[i]); + if (!child) + continue; + + ret = fwnode_property_read_u32(child, "linux,code", &val); + if (ret) { + dev_err(&pdev->dev, "Failed to read switch code: %d\n", + ret); + return ret; + } + iqs62x_keys->switches[i].code = val; + iqs62x_keys->switches[i].enabled = true; + + if (fwnode_property_present(child, "azoteq,use-prox")) + iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ? + IQS62X_EVENT_HALL_N_P : + IQS62X_EVENT_HALL_S_P); + else + iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ? + IQS62X_EVENT_HALL_N_T : + IQS62X_EVENT_HALL_S_T); + } + + return 0; +} + +static int iqs62x_keys_init(struct iqs62x_keys_private *iqs62x_keys) +{ + struct iqs62x_core *iqs62x = iqs62x_keys->iqs62x; + enum iqs62x_event_flag flag; + unsigned int event_mask_reg; + unsigned int event_mask = 0; + unsigned int val; + int ret, i; + + switch (iqs62x->dev_desc->prod_num) { + case IQS620_PROD_NUM: + case IQS621_PROD_NUM: + case IQS622_PROD_NUM: + event_mask_reg = IQS620_GLBL_EVENT_MASK; + + /* + * Discreet button, hysteresis and SAR UI flags represent keys + * and are unmasked if mapped to a valid keycode. + */ + for (i = 0; i < iqs62x_keys->keycodemax; i++) { + if (iqs62x_keys->keycode[i] == KEY_RESERVED) + continue; + + if (iqs62x_events[i].reg == IQS62X_EVENT_PROX) + event_mask |= iqs62x->dev_desc->prox_mask; + else if (iqs62x_events[i].reg == IQS62X_EVENT_HYST) + event_mask |= (iqs62x->dev_desc->hyst_mask | + iqs62x->dev_desc->sar_mask); + } + + ret = regmap_read(iqs62x->map, iqs62x->dev_desc->hall_flags, + &val); + if (ret) + return ret; + + /* + * Hall UI flags represent switches and are unmasked if their + * corresponding child nodes are present. + */ + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) { + if (!(iqs62x_keys->switches[i].enabled)) + continue; + + flag = iqs62x_keys->switches[i].flag; + + if (iqs62x_events[flag].reg != IQS62X_EVENT_HALL) + continue; + + event_mask |= iqs62x->dev_desc->hall_mask; + + input_report_switch(iqs62x_keys->input, + iqs62x_keys->switches[i].code, + (val & iqs62x_events[flag].mask) == + iqs62x_events[flag].val); + } + + input_sync(iqs62x_keys->input); + break; + + case IQS624_PROD_NUM: + event_mask_reg = IQS624_HALL_UI; + + /* + * Interval change events represent keys and are unmasked if + * either wheel movement flag is mapped to a valid keycode. + */ + if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP] != KEY_RESERVED) + event_mask |= IQS624_HALL_UI_INT_EVENT; + + if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN] != KEY_RESERVED) + event_mask |= IQS624_HALL_UI_INT_EVENT; + + ret = regmap_read(iqs62x->map, iqs62x->dev_desc->interval, + &val); + if (ret) + return ret; + + iqs62x_keys->interval = val; + break; + + default: + return 0; + } + + return regmap_update_bits(iqs62x->map, event_mask_reg, event_mask, 0); +} + +static int iqs62x_keys_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs62x_keys_private *iqs62x_keys; + int ret, i; + + iqs62x_keys = container_of(notifier, struct iqs62x_keys_private, + notifier); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs62x_keys_init(iqs62x_keys); + if (ret) { + dev_err(iqs62x_keys->input->dev.parent, + "Failed to re-initialize device: %d\n", ret); + return NOTIFY_BAD; + } + + return NOTIFY_OK; + } + + for (i = 0; i < iqs62x_keys->keycodemax; i++) { + if (iqs62x_events[i].reg == IQS62X_EVENT_WHEEL && + event_data->interval == iqs62x_keys->interval) + continue; + + input_report_key(iqs62x_keys->input, iqs62x_keys->keycode[i], + event_flags & BIT(i)); + } + + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) + if (iqs62x_keys->switches[i].enabled) + input_report_switch(iqs62x_keys->input, + iqs62x_keys->switches[i].code, + event_flags & + BIT(iqs62x_keys->switches[i].flag)); + + input_sync(iqs62x_keys->input); + + if (event_data->interval == iqs62x_keys->interval) + return NOTIFY_OK; + + /* + * Each frame contains at most one wheel event (up or down), in which + * case a complementary release cycle is emulated. + */ + if (event_flags & BIT(IQS62X_EVENT_WHEEL_UP)) { + input_report_key(iqs62x_keys->input, + iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP], + 0); + input_sync(iqs62x_keys->input); + } else if (event_flags & BIT(IQS62X_EVENT_WHEEL_DN)) { + input_report_key(iqs62x_keys->input, + iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN], + 0); + input_sync(iqs62x_keys->input); + } + + iqs62x_keys->interval = event_data->interval; + + return NOTIFY_OK; +} + +static int iqs62x_keys_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs62x_keys_private *iqs62x_keys; + struct input_dev *input; + int ret, i; + + iqs62x_keys = devm_kzalloc(&pdev->dev, sizeof(*iqs62x_keys), + GFP_KERNEL); + if (!iqs62x_keys) + return -ENOMEM; + + platform_set_drvdata(pdev, iqs62x_keys); + + ret = iqs62x_keys_parse_prop(pdev, iqs62x_keys); + if (ret) + return ret; + + input = devm_input_allocate_device(&pdev->dev); + if (!input) + return -ENOMEM; + + input->keycodemax = iqs62x_keys->keycodemax; + input->keycode = iqs62x_keys->keycode; + input->keycodesize = sizeof(*iqs62x_keys->keycode); + + input->name = iqs62x->dev_desc->dev_name; + input->id.bustype = BUS_I2C; + + __set_bit(EV_KEY, input->evbit); + + for (i = 0; i < iqs62x_keys->keycodemax; i++) + __set_bit(iqs62x_keys->keycode[i], input->keybit); + + __clear_bit(KEY_RESERVED, input->keybit); + + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) + if (iqs62x_keys->switches[i].enabled) { + __set_bit(EV_SW, input->evbit); + __set_bit(iqs62x_keys->switches[i].code, input->swbit); + } + + iqs62x_keys->iqs62x = iqs62x; + iqs62x_keys->input = input; + + ret = iqs62x_keys_init(iqs62x_keys); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize device: %d\n", ret); + return ret; + } + + ret = input_register_device(iqs62x_keys->input); + if (ret) { + dev_err(&pdev->dev, "Failed to register device: %d\n", ret); + return ret; + } + + iqs62x_keys->notifier.notifier_call = iqs62x_keys_notifier; + ret = blocking_notifier_chain_register(&iqs62x_keys->iqs62x->nh, + &iqs62x_keys->notifier); + if (ret) + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + + return ret; +} + +static int iqs62x_keys_remove(struct platform_device *pdev) +{ + struct iqs62x_keys_private *iqs62x_keys = platform_get_drvdata(pdev); + int ret; + + ret = blocking_notifier_chain_unregister(&iqs62x_keys->iqs62x->nh, + &iqs62x_keys->notifier); + if (ret) + dev_err(&pdev->dev, "Failed to unregister notifier: %d\n", ret); + + return ret; +} + +static struct platform_driver iqs62x_keys_platform_driver = { + .driver = { + .name = IQS62X_DRV_NAME_KEYS, + }, + .probe = iqs62x_keys_probe, + .remove = iqs62x_keys_remove, +}; +module_platform_driver(iqs62x_keys_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Keys and Switches"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS62X_DRV_NAME_KEYS); From patchwork Mon Jan 6 00:48:02 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11318573 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id D2F63930 for ; Mon, 6 Jan 2020 00:48:43 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 996A421775 for ; Mon, 6 Jan 2020 00:48:43 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="a164uhC9" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727236AbgAFAsn (ORCPT ); Sun, 5 Jan 2020 19:48:43 -0500 Received: from mail-bn7nam10on2068.outbound.protection.outlook.com ([40.107.92.68]:6166 "EHLO NAM10-BN7-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727215AbgAFAsn (ORCPT ); Sun, 5 Jan 2020 19:48:43 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=HXsNgeKicbR1qd9gQlIcwp8Per+pIE4VGP16aKrrXGcHUCgWg/JdrGZ+K1iVAKYidxRZ6mV9RwVLSEVwbi6zqQ2UjLJgbUEPMsrDMWCLbjdE081rz2X160/nQxWrySkTb3W+++SPVmnxQge46A25W1F/iZAOpoMsxUm76+LmN/slK6E9m43OeOwGoD0LXqRbehLGYm5XhxOmA4543dD9LUPkYxXxsGjOpWdrdyRgDr29njujdsW0DmABGQLuBfLYBBSJbCpjug9thNJBEJ7YvEMBK9OW660RXAz+mKjfXxQR/I7m1ppCPAdO38TifKzYeAqHok7UxZ40aC4vcJWvvQ== 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=KzuCve0csxMPnyxcx+URnL0j7SSZ2YyLyqYJSZ1xwC0=; b=BJzKVzkXhZlB7REwAdrfVNxpAOyobhvSTGzrsOv2Lcbqa1iOr4abKC3QHG86+vmsIRJ26+ijvhG+V3DWC1GPGQ/nbTyS8WxMvehGz+4Tb4s4KCm0dKIVT+X+RYnzG8ja0S7YBSW7GSd/BNQcWqN6Sroh6NpGN3/JkUmdnbjeHQX4h70SpszONJZtEWOZX12irk7/wmV+4dgK6QiPfsZJs/wLBZMWPD3oqXu+LcjORzpG3h7DqqAEgrBiL8KlSkJxqFT0M0S3p3zrY9nIXG1C04ZyuS9nvKBtlnzIvG2fwARr2je1AJd8VE2FbdWJzn8sFmc2D9UjBxdHhr7/DaN5rQ== 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=KzuCve0csxMPnyxcx+URnL0j7SSZ2YyLyqYJSZ1xwC0=; b=a164uhC9oluDLKnS8eMUT35oNu5egwsWy/xeRY+bFltGi6GjT48gDXBcHHmEKqVBiE3KZXXRtCzDkqF2ZQRKHjrlk3W4j+Phyy0gzSEq4iWCgEDsFPm1RokcasYKAux8egDTqJLmU+6woQCoXf8LpEQJBzy6Kx9vtovGdh+Ijxk= Received: from SN6PR08MB5053.namprd08.prod.outlook.com (52.135.107.153) by SN6PR08MB4175.namprd08.prod.outlook.com (52.135.69.156) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2602.12; Mon, 6 Jan 2020 00:48:03 +0000 Received: from SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139]) by SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139%4]) with mapi id 15.20.2602.015; Mon, 6 Jan 2020 00:48:03 +0000 Received: from localhost.localdomain (136.49.227.119) by SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.2602.13 via Frontend Transport; Mon, 6 Jan 2020 00:48:02 +0000 From: Jeff LaBundy To: "lee.jones@linaro.org" , "dmitry.torokhov@gmail.com" , "thierry.reding@gmail.com" , "jic23@kernel.org" , "devicetree@vger.kernel.org" CC: "linux-input@vger.kernel.org" , "u.kleine-koenig@pengutronix.de" , "linux-pwm@vger.kernel.org" , "knaack.h@gmx.de" , "lars@metafoo.de" , "pmeerw@pmeerw.net" , "linux-iio@vger.kernel.org" , "robh+dt@kernel.org" , "mark.rutland@arm.com" , Jeff LaBundy Subject: [PATCH v3 4/7] pwm: Add support for Azoteq IQS620A PWM generator Thread-Topic: [PATCH v3 4/7] pwm: Add support for Azoteq IQS620A PWM generator Thread-Index: AQHVxCrzh4C7ONAGT0GQryJS4hKCGA== Date: Mon, 6 Jan 2020 00:48:02 +0000 Message-ID: <1578271620-2159-5-git-send-email-jeff@labundy.com> References: <1578271620-2159-1-git-send-email-jeff@labundy.com> In-Reply-To: <1578271620-2159-1-git-send-email-jeff@labundy.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-clientproxiedby: SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) To SN6PR08MB5053.namprd08.prod.outlook.com (2603:10b6:805:78::25) authentication-results: spf=none (sender IP is ) smtp.mailfrom=jeff@labundy.com; x-ms-exchange-messagesentrepresentingtype: 1 x-mailer: git-send-email 2.7.4 x-originating-ip: [136.49.227.119] x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: eaf285ea-c90c-44a7-215a-08d7924215cf x-ms-traffictypediagnostic: SN6PR08MB4175: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:2043; x-forefront-prvs: 0274272F87 x-forefront-antispam-report: SFV:NSPM;SFS:(10009020)(366004)(396003)(39830400003)(136003)(376002)(346002)(34096005)(199004)(189003)(36756003)(107886003)(2616005)(956004)(316002)(69590400006)(5660300002)(86362001)(6486002)(508600001)(2906002)(4326008)(6506007)(81166006)(110136005)(6512007)(54906003)(16526019)(26005)(8676002)(186003)(7416002)(8936002)(66476007)(71200400001)(52116002)(66946007)(30864003)(66446008)(66556008)(64756008)(81156014);DIR:OUT;SFP:1101;SCL:1;SRVR:SN6PR08MB4175;H:SN6PR08MB5053.namprd08.prod.outlook.com;FPR:;SPF:None;LANG:en;PTR:InfoNoRecords;A:1;MX:1; received-spf: None (protection.outlook.com: labundy.com does not designate permitted sender hosts) x-ms-exchange-senderadcheck: 1 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: 4HLehEvYlo5voscxXYgzphlGMW6eaRq7jtMtDEUQj0z03plpXyvKCjuNtthw3+FX3r6EHzvgntzdzBp4JmKHW6M/upxo1KEh/rP6UfqOjj9XnI/5sa8Czlb8m0OC16G83Je82pJBdafZGZHEsuxD4LkFJDKQNTznNtf3rdKgrRFqhtw2ndgBoPWzk8Q+12DTdQ5TJ3Awxm7VSX//UGmz28cY12qv0bulPS0tbmuUx+im9JkhOcBimQ5yidQiJRML2Y0CR62r5LBu7JBI3O2jWX9yfYCyKY2u+JZgbXkQ5QEbJwywnRSGOB4QhcOhqLby7ha4iW4KaUKpse+tJXw2feD+DFEkgw8uhewbypUjkjtnCaczmh3xGMTo+U6sDE4QYDpAGG9K6lhcNglEIvpcPTf05xJA1O5kNH3N52fgS6HjDVK0TlyzKCT74tzYCnTJGVrwLvTubr5Jl35E4WQAjaR8EaPLLSfBWuImLr+YFtPbq0vBIF3oJL1Hv9RXWYgs MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: eaf285ea-c90c-44a7-215a-08d7924215cf X-MS-Exchange-CrossTenant-originalarrivaltime: 06 Jan 2020 00:48:02.9426 (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: vY+7qqqh+uLDSthVw+THmCcYFltymWlixdWl75wP7m8G7dQEiajivDZJxkRop1VOvz8qcwhYTZy/e02mrUYKyw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN6PR08MB4175 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS620A, capable of generating a 1-kHz PWM output with duty cycle between ~0.4% and 100% (inclusive). Signed-off-by: Jeff LaBundy --- Changes in v3: - Updated the commit message to say "~0.4%" instead of "0.4%" - Clarified the effect of duty cycle and state changes in the 'Limitations' section and added a restriction regarding 0% duty cycle - Added a comment in iqs620_pwm_apply to explain how duty cycle is derived - Updated iqs620_pwm_apply to disable the output first and enable it last to prevent temporarily driving a stale duty cycle - Rounded the calculation for duty cycle up and down in iqs620_pwm_get_state and iqs620_pwm_apply, respectively - Added a comment in iqs620_pwm_get_state to explain what it reports follow- ing requests to set duty cycle to 0% - Added a lock to prevent back-to-back access of IQS620_PWR_SETTINGS_PWM_OUT and IQS620_PWM_DUTY_CYCLE from being interrupted - Updated iqs620_pwm_notifier to reference pwm->state directly as opposed to calling pwm_get_state - Moved notifier unregistration back to a device-managed action - Added a completion to prevent iqs620_pwm_notifier from referencing the pwm_chip structure until it has been initialized by pwmchip_add Changes in v2: - Merged 'Copyright' and 'Author' lines into one in introductory comments - Added 'Limitations' section to introductory comments - Replaced 'error' with 'ret' throughout - Added const qualifier to state argument of iqs620_pwm_apply and removed all modifications to the variable's contents - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested polarity is inverted or the requested period is below 1 ms, respectively - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero - Added iqs620_pwm_get_state - Eliminated tabbed alignment of pwm_ops and platform_driver struct members - Moved notifier unregistration to already present iqs620_pwm_remove, which eliminated the need for a device-managed action and ready flag - Added a comment in iqs620_pwm_probe to explain the order of operations - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-iqs620a.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 drivers/pwm/pwm-iqs620a.c -- 2.7.4 diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index bd21655..60bcf6c 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -222,6 +222,16 @@ config PWM_IMX_TPM To compile this driver as a module, choose M here: the module will be called pwm-imx-tpm. +config PWM_IQS620A + tristate "Azoteq IQS620A PWM support" + depends on MFD_IQS62X || COMPILE_TEST + help + Generic PWM framework driver for the Azoteq IQS620A multi-function + sensor. + + To compile this driver as a module, choose M here: the module will + be called pwm-iqs620a. + config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" depends on MACH_INGENIC diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9a47507..a59c710 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o obj-$(CONFIG_PWM_IMX_TPM) += pwm-imx-tpm.o +obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c new file mode 100644 index 0000000..ee5d8b5 --- /dev/null +++ b/drivers/pwm/pwm-iqs620a.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A PWM Generator + * + * Copyright (C) 2019 Jeff LaBundy + * + * Limitations: + * - The period is fixed to 1 ms and is generated continuously despite changes + * to the duty cycle or enable/disable state. + * - Changes to the duty cycle or enable/disable state take effect immediately + * and may result in a glitch during the period in which the change is made. + * - The device cannot generate a 0% duty cycle. For duty cycles below 1 / 256 + * ms, the output is disabled and relies upon an external pull-down resistor + * to hold the GPIO3/LTX pin low. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS620_PWR_SETTINGS 0xD2 +#define IQS620_PWR_SETTINGS_PWM_OUT BIT(7) + +#define IQS620_PWM_DUTY_CYCLE 0xD8 + +#define IQS620_PWM_PERIOD_NS 1000000 + +struct iqs620_pwm_private { + struct iqs62x_core *iqs62x; + struct pwm_chip chip; + struct notifier_block notifier; + struct completion chip_ready; + struct mutex lock; +}; + +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct iqs620_pwm_private *iqs620_pwm; + struct iqs62x_core *iqs62x; + int duty_scale, ret; + + if (state->polarity != PWM_POLARITY_NORMAL) + return -ENOTSUPP; + + if (state->period < IQS620_PWM_PERIOD_NS) + return -EINVAL; + + iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip); + iqs62x = iqs620_pwm->iqs62x; + + mutex_lock(&iqs620_pwm->lock); + + /* + * The duty cycle generated by the device is calculated as follows: + * + * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms + * + * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255 + * (inclusive). Therefore the lowest duty cycle the device can generate + * while the output is enabled is 1 / 256 ms. + * + * For lower duty cycles (e.g. 0), the PWM output is simply disabled to + * allow an on-board pull-down resistor to hold the GPIO3/LTX pin low. + */ + duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS; + + if (!state->enabled || !duty_scale) { + ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, 0); + if (ret) + goto err_mutex; + } + + if (duty_scale) { + ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE, + min(duty_scale - 1, 0xFF)); + if (ret) + goto err_mutex; + } + + if (state->enabled && duty_scale) + ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, 0xFF); + +err_mutex: + mutex_unlock(&iqs620_pwm->lock); + + return ret; +} + +static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct iqs620_pwm_private *iqs620_pwm; + struct iqs62x_core *iqs62x; + unsigned int val; + int ret; + + iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip); + iqs62x = iqs620_pwm->iqs62x; + + mutex_lock(&iqs620_pwm->lock); + + /* + * Since the device cannot generate a 0% duty cycle, requests to do so + * cause subsequent calls to iqs620_pwm_get_state to report the output + * as disabled with duty cycle equal to that which was in use prior to + * the request. This is not ideal, but is the best compromise based on + * the capabilities of the device. + */ + ret = regmap_read(iqs62x->map, IQS620_PWR_SETTINGS, &val); + if (ret) + goto err_mutex; + state->enabled = val & IQS620_PWR_SETTINGS_PWM_OUT; + + ret = regmap_read(iqs62x->map, IQS620_PWM_DUTY_CYCLE, &val); + if (ret) + goto err_mutex; + state->duty_cycle = DIV_ROUND_UP((val + 1) * IQS620_PWM_PERIOD_NS, 256); + state->period = IQS620_PWM_PERIOD_NS; + +err_mutex: + mutex_unlock(&iqs620_pwm->lock); + + if (ret) + dev_err(iqs620_pwm->chip.dev, "Failed to get state: %d\n", ret); +} + +static int iqs620_pwm_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs620_pwm_private *iqs620_pwm; + int ret; + + iqs620_pwm = container_of(notifier, struct iqs620_pwm_private, + notifier); + + if (!completion_done(&iqs620_pwm->chip_ready) || + !(event_flags & BIT(IQS62X_EVENT_SYS_RESET))) + return NOTIFY_DONE; + + ret = iqs620_pwm_apply(&iqs620_pwm->chip, &iqs620_pwm->chip.pwms[0], + &iqs620_pwm->chip.pwms[0].state); + if (ret) { + dev_err(iqs620_pwm->chip.dev, + "Failed to re-initialize device: %d\n", ret); + return NOTIFY_BAD; + } + + return NOTIFY_OK; +} + +static const struct pwm_ops iqs620_pwm_ops = { + .apply = iqs620_pwm_apply, + .get_state = iqs620_pwm_get_state, + .owner = THIS_MODULE, +}; + +static void iqs620_pwm_notifier_unregister(void *context) +{ + struct iqs620_pwm_private *iqs620_pwm = context; + int ret; + + ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (ret) + dev_err(iqs620_pwm->chip.dev, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs620_pwm_probe(struct platform_device *pdev) +{ + struct iqs620_pwm_private *iqs620_pwm; + int ret; + + iqs620_pwm = devm_kzalloc(&pdev->dev, sizeof(*iqs620_pwm), GFP_KERNEL); + if (!iqs620_pwm) + return -ENOMEM; + + platform_set_drvdata(pdev, iqs620_pwm); + iqs620_pwm->iqs62x = dev_get_drvdata(pdev->dev.parent); + + iqs620_pwm->chip.dev = &pdev->dev; + iqs620_pwm->chip.ops = &iqs620_pwm_ops; + iqs620_pwm->chip.base = -1; + iqs620_pwm->chip.npwm = 1; + + init_completion(&iqs620_pwm->chip_ready); + mutex_init(&iqs620_pwm->lock); + + iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier; + ret = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs620_pwm_notifier_unregister, + iqs620_pwm); + if (ret) + return ret; + + ret = pwmchip_add(&iqs620_pwm->chip); + if (ret) { + dev_err(&pdev->dev, "Failed to add device: %d\n", ret); + return ret; + } + + /* + * pwmchip_add is called last to avoid a messy tear-down path, so the + * following completion prevents iqs620_pwm_notifier from referencing + * the pwm_chip structure until it has been completely initialized. + */ + complete_all(&iqs620_pwm->chip_ready); + + return 0; +} + +static int iqs620_pwm_remove(struct platform_device *pdev) +{ + struct iqs620_pwm_private *iqs620_pwm = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&iqs620_pwm->chip); + if (ret) + dev_err(&pdev->dev, "Failed to remove device: %d\n", ret); + + return ret; +} + +static struct platform_driver iqs620_pwm_platform_driver = { + .driver = { + .name = IQS620_DRV_NAME_PWM, + }, + .probe = iqs620_pwm_probe, + .remove = iqs620_pwm_remove, +}; +module_platform_driver(iqs620_pwm_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS620A PWM Generator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS620_DRV_NAME_PWM); From patchwork Mon Jan 6 00:48:03 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11318579 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 812BD1580 for ; Mon, 6 Jan 2020 00:48:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 54F44217F4 for ; Mon, 6 Jan 2020 00:48:45 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="daVVQOBE" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727259AbgAFAso (ORCPT ); Sun, 5 Jan 2020 19:48:44 -0500 Received: from mail-bn7nam10on2068.outbound.protection.outlook.com ([40.107.92.68]:6166 "EHLO NAM10-BN7-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726526AbgAFAso (ORCPT ); Sun, 5 Jan 2020 19:48:44 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=jVTitPyFrEGM40veNZneDsB0MhxUrNLqy72Y8rBw3xV9HMhUHaL77MQZWetoyXd1yyyOVRASkJ1CHALzEItCRm1Bx9UE1qRigMNRfHHAN6RZNrsvD8kallHznW9m1POmeqPdkgjjIAtN1s+hO7nc5NzfEdbgefj4Y3+mJJ+WsSUUNNWi2Gl4bAqdCXXpRJ30pQbkGra9p51YVECOV188CjtkI7wMSW2cTKLn8X8DedKCF+8fQi87K/bsUFpcEjhGMaxI5t/Z68pNkXbAZvDh9IfYbygC+nYkV9A7yByEvVaJ92iYMI51VHhy5Z/C229Hq79WGU8rIAkLjjQUVh4aOg== 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=e/fHvtpsQEIg51WOGAxxBkkGfgR7CRVroD1x+Aj1ffk=; b=VEtJFvG1L/x0ZbGQAulCxnbWaweg1t0DWiZlsUDHoqHIvyIdtyJAfA2I0sch7oPEwQ641zBvmQ3qv3Q2AOLqKflkJ1R+lpPcg2gMOHfUr2kNoHx+pxx5F55nNx8XWEOxltUG08zzqHhxTH7b19T0gJgfIRuZht01EGieR9Tp4kQylHxzAjbTILZEym5ZcuqhYhlwUtNWtKqIp/EsugJXanFF17RXGFWBTddMUsRA2GP1rZd7Jk5WlnvGP/RBtAiRat68q9WwVpf3TboSgVBD4tLTopVa+clzIO5uveZV4LmbR6kvZR1iX9WI9cyiiCnuypvqhL+Wm+B5QLxNH/X3Zg== 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=e/fHvtpsQEIg51WOGAxxBkkGfgR7CRVroD1x+Aj1ffk=; b=daVVQOBEnFdLfLyTq/ZcLn6icANbLDsu/5Y42vX8gUGVB9wRJiDWPYJxxRBugEODSOdEINH7sp5/v0CnzoP1BHLfLSgfbtzYDBlES/5Q10U+4TEPYJxvtPktV4nqo0u/kF1AVlUooiJMSu10KehQscLj0zs/ek4SFMr4LLwhe2I= Received: from SN6PR08MB5053.namprd08.prod.outlook.com (52.135.107.153) by SN6PR08MB4175.namprd08.prod.outlook.com (52.135.69.156) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2602.12; Mon, 6 Jan 2020 00:48:06 +0000 Received: from SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139]) by SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139%4]) with mapi id 15.20.2602.015; Mon, 6 Jan 2020 00:48:06 +0000 Received: from localhost.localdomain (136.49.227.119) by SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.2602.13 via Frontend Transport; Mon, 6 Jan 2020 00:48:03 +0000 From: Jeff LaBundy To: "lee.jones@linaro.org" , "dmitry.torokhov@gmail.com" , "thierry.reding@gmail.com" , "jic23@kernel.org" , "devicetree@vger.kernel.org" CC: "linux-input@vger.kernel.org" , "u.kleine-koenig@pengutronix.de" , "linux-pwm@vger.kernel.org" , "knaack.h@gmx.de" , "lars@metafoo.de" , "pmeerw@pmeerw.net" , "linux-iio@vger.kernel.org" , "robh+dt@kernel.org" , "mark.rutland@arm.com" , Jeff LaBundy Subject: [PATCH v3 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Thread-Topic: [PATCH v3 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Thread-Index: AQHVxCrzaUNk+i1RRk+ClxiQZe1edw== Date: Mon, 6 Jan 2020 00:48:03 +0000 Message-ID: <1578271620-2159-6-git-send-email-jeff@labundy.com> References: <1578271620-2159-1-git-send-email-jeff@labundy.com> In-Reply-To: <1578271620-2159-1-git-send-email-jeff@labundy.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-clientproxiedby: SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) To SN6PR08MB5053.namprd08.prod.outlook.com (2603:10b6:805:78::25) authentication-results: spf=none (sender IP is ) smtp.mailfrom=jeff@labundy.com; x-ms-exchange-messagesentrepresentingtype: 1 x-mailer: git-send-email 2.7.4 x-originating-ip: [136.49.227.119] x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: 7999fc40-a38b-4ed7-d6c4-08d792421658 x-ms-traffictypediagnostic: SN6PR08MB4175: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:7219; x-forefront-prvs: 0274272F87 x-forefront-antispam-report: SFV:NSPM;SFS:(10009020)(366004)(396003)(39830400003)(136003)(376002)(346002)(34096005)(199004)(189003)(36756003)(107886003)(2616005)(956004)(316002)(69590400006)(5660300002)(86362001)(6486002)(508600001)(2906002)(4326008)(6506007)(81166006)(110136005)(6512007)(54906003)(16526019)(26005)(8676002)(186003)(7416002)(8936002)(66476007)(71200400001)(52116002)(66946007)(66446008)(66556008)(64756008)(81156014);DIR:OUT;SFP:1101;SCL:1;SRVR:SN6PR08MB4175;H:SN6PR08MB5053.namprd08.prod.outlook.com;FPR:;SPF:None;LANG:en;PTR:InfoNoRecords;A:1;MX:1; received-spf: None (protection.outlook.com: labundy.com does not designate permitted sender hosts) x-ms-exchange-senderadcheck: 1 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: +ePViaqf+L2cSYnoVlx+08BQOBKvknmuJsT8ay1i5mfRPGmkq97ujuC6pE7iqYpvnFHv27OkL6U+OsCCxQIRZnOUeb+kAaMav62EJeiZ9Em4tMBeIUDm8WzK8Fiujwlvd1cfx/UsuoVw9U9rVETAlmkKggYL/1NK77+p5KWo0O9cvRWi4eip0bEgFiAZVTvuK1u6LCHfvi2XhRDhiUKgo9WotrvasRK5Z0oRVsRD/Gr3MdJu4fNJOb5FHgcIPkNHVxh6X1mUAmLo3pN5aywn2ikWfJ/L9O83lQajD9Hth0V1im92k9dBKBsjNfz73wk1F7tHtu8NLgrA3o5FeECUN6oWDDD8a3TA/sDqFJcFLuNemKvOKRNsc1xn/poOydOs9mu8dpewDk59YY5DM7PCwCaVrNlcK/8Iv29075kGSwibeLBvt8OVzw6GlVHvh09D3xTHh4U0xf2e/m4BdreNh1xg82nv5YEQKPHQehNj7xI4ZT22q2zniHN/5gMh65JD MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: 7999fc40-a38b-4ed7-d6c4-08d792421658 X-MS-Exchange-CrossTenant-originalarrivaltime: 06 Jan 2020 00:48:04.0310 (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: pWz2hJt23eGz/dhu9EH7wuVYt8ieEMIbyUTsd3RGfxAbLKr04IiEMuBYeBaOg6Q5rrfoGz17JyNtFPTN0ScsdA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN6PR08MB4175 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS620AT temperature sensor, capable of reporting its absolute die temperature. Signed-off-by: Jeff LaBundy Reviewed-by: Jonathan Cameron --- Changes in v3: - Added Reviewed-by trailer Changes in v2: - Moved the driver from hwmon to iio - Merged 'Copyright' and 'Author' lines into one in introductory comments - Replaced 'error' with 'ret' throughout - Eliminated tabbed alignment of platform_driver struct members - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST drivers/iio/temperature/Kconfig | 10 ++++ drivers/iio/temperature/Makefile | 1 + drivers/iio/temperature/iqs620at-temp.c | 97 +++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 drivers/iio/temperature/iqs620at-temp.c -- 2.7.4 diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index e1ccb40..f1f2a14 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -4,6 +4,16 @@ # menu "Temperature sensors" +config IQS620AT_TEMP + tristate "Azoteq IQS620AT temperature sensor" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS620AT + temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called iqs620at-temp. + config LTC2983 tristate "Analog Devices Multi-Sensor Digital Temperature Measurement System" depends on SPI diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile index d6b850b..90c1131 100644 --- a/drivers/iio/temperature/Makefile +++ b/drivers/iio/temperature/Makefile @@ -3,6 +3,7 @@ # Makefile for industrial I/O temperature drivers # +obj-$(CONFIG_IQS620AT_TEMP) += iqs620at-temp.o obj-$(CONFIG_LTC2983) += ltc2983.o obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c new file mode 100644 index 0000000..d20cb6ad --- /dev/null +++ b/drivers/iio/temperature/iqs620at-temp.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620AT Temperature Sensor + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include + +#define IQS620_TEMP_UI_OUT 0x1A + +#define IQS620_TEMP_SCALE 1000 +#define IQS620_TEMP_OFFSET (-100) + +static int iqs620_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs62x_core *iqs62x = iio_device_get_drvdata(indio_dev); + int ret; + __le16 val_buf; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_raw_read(iqs62x->map, IQS620_TEMP_UI_OUT, &val_buf, + sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = IQS620_TEMP_SCALE; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OFFSET: + *val = IQS620_TEMP_OFFSET; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static const struct iio_info iqs620_temp_info = { + .read_raw = &iqs620_temp_read_raw, +}; + +static const struct iio_chan_spec iqs620_temp_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + }, +}; + +static int iqs620_temp_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&pdev->dev, 0); + if (!indio_dev) + return -ENOMEM; + + iio_device_set_drvdata(indio_dev, iqs62x); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->channels = iqs620_temp_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs620_temp_channels); + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs620_temp_info; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs620_temp_platform_driver = { + .driver = { + .name = IQS620_DRV_NAME_TEMP, + }, + .probe = iqs620_temp_probe, +}; +module_platform_driver(iqs620_temp_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS620AT Temperature Sensor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS620_DRV_NAME_TEMP); From patchwork Mon Jan 6 00:48:05 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11318581 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 2D72F930 for ; Mon, 6 Jan 2020 00:48:48 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id D94C321775 for ; Mon, 6 Jan 2020 00:48:47 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="nUxh3BvX" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727263AbgAFAsr (ORCPT ); Sun, 5 Jan 2020 19:48:47 -0500 Received: from mail-bn7nam10on2068.outbound.protection.outlook.com ([40.107.92.68]:6166 "EHLO NAM10-BN7-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727215AbgAFAsr (ORCPT ); Sun, 5 Jan 2020 19:48:47 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=DoI9Z36B8xEKN3u4M4cHcKTFvGhwHXVGI9gUCg8TEtD3A1GtEjVtU8SXEhUBzbc0VkjaeDWs+B8krSvOw0j5kVqxGdnpgLSrWD0G/eAHR4kmP5yOj4BCMl49xffbb1IyVYG39az16TLbIQBvO5RPVDEh8OBU1rFIGUM3eruLb77ccOmP3KIdwrqSJQ42jagTXR5CaVM9nOVPwW1w/GVCCMj/jxTUov9iAChWbJCFv3sayNQluOilaoTRtmyZ7Dr9QaIqRoiUyT0dKGFJkvt8kSxurhK4eSHuZmt3Sy4ADhL+yVcE8LFNYQGYDSOIbF8iv6bRp5quRGmznGPdno2RUQ== 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=yG4QL/79Wjd7eJ06bDMpZnwIxz/gysBLwT0rc2it7as=; b=dxIlIpEUFy5FmBo59e16y3GDiR0ludD7BsE3r0850Kashzmb2F8lS17sshkaps4BbrHDYhQC6oLQeIXzatL707sjrznCPKApWrSZGLdbhgZpZbMO/s0gSdf320tIaucqAlWnHAYeXhhhJjT9mCKuItnxW1kukevgpQRN1c8jup833gCR7CXYk3JALe3wsp7eRrw2LXLZ/5ZoenIndRT/dSC5N++q926LAf82o7EXlSCgscOZ0/YDD8Fqy18GaCqoHk142h8XnoMgF75Yvylf5m/om8NgSZ/FDuFmLHC/AUprtukV0eHQDrixEwJkwhJoSeAo7vCnghDy28vc+fr02Q== 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=yG4QL/79Wjd7eJ06bDMpZnwIxz/gysBLwT0rc2it7as=; b=nUxh3BvXeYb8nmmPfmgt3QFC/Ekbcu/bqbuWs+gr+7AnKfmxXs8+q96ptrBhI2nazym0u4mUQqrBpPwzlMY3Rrm9SKplMA8JG0A+cVmBGkGuPsPe2W5oZEDc2C/kAAzqgqBTZYrx4bGOPr4iWyxI/yPMnu4DkkG6P47CMIAWTmY= Received: from SN6PR08MB5053.namprd08.prod.outlook.com (52.135.107.153) by SN6PR08MB4175.namprd08.prod.outlook.com (52.135.69.156) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2602.12; Mon, 6 Jan 2020 00:48:07 +0000 Received: from SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139]) by SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139%4]) with mapi id 15.20.2602.015; Mon, 6 Jan 2020 00:48:07 +0000 Received: from localhost.localdomain (136.49.227.119) by SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.2602.13 via Frontend Transport; Mon, 6 Jan 2020 00:48:04 +0000 From: Jeff LaBundy To: "lee.jones@linaro.org" , "dmitry.torokhov@gmail.com" , "thierry.reding@gmail.com" , "jic23@kernel.org" , "devicetree@vger.kernel.org" CC: "linux-input@vger.kernel.org" , "u.kleine-koenig@pengutronix.de" , "linux-pwm@vger.kernel.org" , "knaack.h@gmx.de" , "lars@metafoo.de" , "pmeerw@pmeerw.net" , "linux-iio@vger.kernel.org" , "robh+dt@kernel.org" , "mark.rutland@arm.com" , Jeff LaBundy Subject: [PATCH v3 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Thread-Topic: [PATCH v3 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Thread-Index: AQHVxCr0ovMEL8BNo0C4gyC5lWeccg== Date: Mon, 6 Jan 2020 00:48:05 +0000 Message-ID: <1578271620-2159-7-git-send-email-jeff@labundy.com> References: <1578271620-2159-1-git-send-email-jeff@labundy.com> In-Reply-To: <1578271620-2159-1-git-send-email-jeff@labundy.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-clientproxiedby: SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) To SN6PR08MB5053.namprd08.prod.outlook.com (2603:10b6:805:78::25) authentication-results: spf=none (sender IP is ) smtp.mailfrom=jeff@labundy.com; x-ms-exchange-messagesentrepresentingtype: 1 x-mailer: git-send-email 2.7.4 x-originating-ip: [136.49.227.119] x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: afb25e0b-1cc4-4366-ced9-08d79242170a x-ms-traffictypediagnostic: SN6PR08MB4175: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:10000; x-forefront-prvs: 0274272F87 x-forefront-antispam-report: SFV:NSPM;SFS:(10009020)(366004)(396003)(39830400003)(136003)(376002)(346002)(34096005)(199004)(189003)(36756003)(107886003)(2616005)(956004)(316002)(69590400006)(5660300002)(86362001)(6486002)(508600001)(2906002)(4326008)(6506007)(81166006)(110136005)(6512007)(54906003)(16526019)(26005)(8676002)(186003)(7416002)(8936002)(66476007)(71200400001)(52116002)(66946007)(30864003)(66446008)(66556008)(64756008)(81156014);DIR:OUT;SFP:1101;SCL:1;SRVR:SN6PR08MB4175;H:SN6PR08MB5053.namprd08.prod.outlook.com;FPR:;SPF:None;LANG:en;PTR:InfoNoRecords;A:1;MX:1; received-spf: None (protection.outlook.com: labundy.com does not designate permitted sender hosts) x-ms-exchange-senderadcheck: 1 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: FCn24I1IU6Bb6raP7SqVpCW8t0nO75bqsK0VXn7JVE/uBuOn4Ag/cJWQqLo5fhroBUwpCq4TcM9ShDh4Lw1hkX4bYEEM5yqT4NVFUbqAe3jHVIbDEG2tSRXzTNXzr9KRImthZZrCmcxRnoyQANIH7qMIiif/1Pxy4XY+BWytTPOfIGCfN4Ia/3uO9dQJyro13qva14EzYHj0o5tRfby3iED3i7lF4Z3B+sqmbjXrsJMLrd4mS8LjU64i96vy8V1E+3fzjHR5BKJ6L8s0JwF7xnnhNSCSRstf7xbq35t9BLmUd38f62UA1m9+YzqmES3kZrgdoUgVg1nnD9kMUkiiIVPlY6+BfslNxLE+YkiqoJqVP2rVJYLFZ3LJurw+GxnTq9Ca+YteviC5XkBFIA5zK5hTZBy3VIjgUvn7KonxmvD4tMK0VmChjmWrEmfn6eyMqBEEItSCkVe04VpPiu5UKvJsAOeJrwqDFgGGT0LLEEfzmWZ7SX1fRU7Y4c3UehPr MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: afb25e0b-1cc4-4366-ced9-08d79242170a X-MS-Exchange-CrossTenant-originalarrivaltime: 06 Jan 2020 00:48:05.3522 (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: wYIdfVKANkvj9EwTuhyW2dn9UD8FbtWnniPFw/VFy+XSBhzZ58PYhlacsqIZuUmz4TfhC9DGZe/b4z44WRZbpQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN6PR08MB4175 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS621 and IQS622 ambient light sensors, both of which can report a four-bit representation of ambient light intensity. The IQS621 can additionally report illuminace directly in units of lux, while the IQS622 can report a four-bit representation of infrared light intensity. Furthermore, the IQS622 can report a unitless measurement of a target's proximity to the device. Signed-off-by: Jeff LaBundy Reviewed-by: Jonathan Cameron --- Changes in v3: - Added Reviewed-by trailer Changes in v2: - Merged 'Copyright' and 'Author' lines into one in introductory comments - Replaced 'error' with 'ret' throughout - Merged support for the closely related IQS622 (formerly represented by a separate iio/proximity driver) - Added support for unitless ambient light intensity (IQS621 and IQS622) and infrared light intensity (IQS622 only) - Moved the read of IQS621_ALS_FLAGS to iqs621_als_write_event_config to account for the fact that IQS621_ALS_FLAGS may have changed in between having first been read in iqs621_als_init and the time at which events are enabled, thereby eliminating the need to call iqs621_als_init from iqs621_als_probe - Refactored the logic in iqs621_als_notifier and added a lock to safely evaluate variables that may change in response to user action - Added locks to iqs621_als_read_event_config/value to account for cases in which the corresponding hardware state is in the process of being updated - Refactored the logic in iqs621_als_read/write_event_value and removed all #defines that could instead be represented by simple math - Based the decision whether to select the IQS622 IR touch vs. proximity threshold on the single proximity threshold written by user space, and added a comment to describe the difference between either threshold - Replaced IIO_CHAN_INFO_RAW with IIO_CHAN_INFO_PROCESSED for the IIO_LIGHT channel (IQS621 only) - Removed devm_add_action_or_reset failure message - Eliminated tabbed alignment of platform_driver struct members - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/iqs621-als.c | 614 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 625 insertions(+) create mode 100644 drivers/iio/light/iqs621-als.c -- 2.7.4 diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 9968f98..baf7958b 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -173,6 +173,16 @@ config GP2AP020A00F To compile this driver as a module, choose M here: the module will be called gp2ap020a00f. +config IQS621_ALS + tristate "Azoteq IQS621/622 ambient light sensors" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS621 + and IQS622 ambient light sensors. + + To compile this driver as a module, choose M here: the module + will be called iqs621-als. + config SENSORS_ISL29018 tristate "Intersil 29018 light and proximity sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index c98d1ce..988e8f4 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o +obj-$(CONFIG_IQS621_ALS) += iqs621-als.o obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o obj-$(CONFIG_ISL29125) += isl29125.o diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c new file mode 100644 index 0000000..a4dd718 --- /dev/null +++ b/drivers/iio/light/iqs621-als.c @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS621/622 Ambient Light Sensors + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS621_ALS_FLAGS_LIGHT BIT(7) +#define IQS621_ALS_FLAGS_RANGE GENMASK(3, 0) + +#define IQS621_ALS_UI_OUT 0x17 + +#define IQS621_ALS_THRESH_DARK 0x80 +#define IQS621_ALS_THRESH_LIGHT 0x81 + +#define IQS622_IR_RANGE 0x15 +#define IQS622_IR_FLAGS 0x16 +#define IQS622_IR_FLAGS_TOUCH BIT(1) +#define IQS622_IR_FLAGS_PROX BIT(0) + +#define IQS622_IR_UI_OUT 0x17 + +#define IQS622_IR_THRESH_PROX 0x91 +#define IQS622_IR_THRESH_TOUCH 0x92 + +struct iqs621_als_private { + struct iqs62x_core *iqs62x; + struct notifier_block notifier; + struct mutex lock; + bool light_en; + bool range_en; + bool prox_en; + u8 als_flags; + u8 ir_flags_mask; + u8 ir_flags; + u8 thresh_light; + u8 thresh_dark; + u8 thresh_prox; +}; + +static int iqs621_als_init(struct iqs621_als_private *iqs621_als) +{ + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int event_mask = 0; + int ret; + + switch (iqs621_als->ir_flags_mask) { + case IQS622_IR_FLAGS_TOUCH: + ret = regmap_write(iqs62x->map, IQS622_IR_THRESH_TOUCH, + iqs621_als->thresh_prox); + break; + + case IQS622_IR_FLAGS_PROX: + ret = regmap_write(iqs62x->map, IQS622_IR_THRESH_PROX, + iqs621_als->thresh_prox); + break; + + default: + ret = regmap_write(iqs62x->map, IQS621_ALS_THRESH_LIGHT, + iqs621_als->thresh_light); + if (ret) + return ret; + + ret = regmap_write(iqs62x->map, IQS621_ALS_THRESH_DARK, + iqs621_als->thresh_dark); + } + + if (ret) + return ret; + + if (iqs621_als->light_en || iqs621_als->range_en) + event_mask |= iqs62x->dev_desc->als_mask; + + if (iqs621_als->prox_en) + event_mask |= iqs62x->dev_desc->ir_mask; + + return regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK, + event_mask, 0); +} + +static int iqs621_als_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs621_als_private *iqs621_als; + struct iio_dev *indio_dev; + bool light_new, light_old; + bool prox_new, prox_old; + u8 range_new, range_old; + s64 timestamp; + int ret; + + iqs621_als = container_of(notifier, struct iqs621_als_private, + notifier); + indio_dev = iio_priv_to_dev(iqs621_als); + timestamp = iio_get_time_ns(indio_dev); + + mutex_lock(&iqs621_als->lock); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs621_als_init(iqs621_als); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", ret); + ret = NOTIFY_BAD; + } else { + ret = NOTIFY_OK; + } + + goto err_mutex; + } + + if (!iqs621_als->light_en && !iqs621_als->range_en && + !iqs621_als->prox_en) { + ret = NOTIFY_DONE; + goto err_mutex; + } + + /* IQS621 only */ + light_new = event_data->als_flags & IQS621_ALS_FLAGS_LIGHT; + light_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_LIGHT; + + if (iqs621_als->light_en && light_new && !light_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->light_en && !light_new && light_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + /* IQS621 and IQS622 */ + range_new = event_data->als_flags & IQS621_ALS_FLAGS_RANGE; + range_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_RANGE; + + if (iqs621_als->range_en && (range_new > range_old)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->range_en && (range_new < range_old)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_FALLING), + timestamp); + + /* IQS622 only */ + prox_new = event_data->ir_flags & iqs621_als->ir_flags_mask; + prox_old = iqs621_als->ir_flags & iqs621_als->ir_flags_mask; + + if (iqs621_als->prox_en && prox_new && !prox_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->prox_en && !prox_new && prox_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + iqs621_als->als_flags = event_data->als_flags; + iqs621_als->ir_flags = event_data->ir_flags; + ret = NOTIFY_OK; + +err_mutex: + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static void iqs621_als_notifier_unregister(void *context) +{ + struct iqs621_als_private *iqs621_als = context; + struct iio_dev *indio_dev = iio_priv_to_dev(iqs621_als); + int ret; + + ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (ret) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs621_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + int ret; + __le16 val_buf; + + switch (chan->type) { + case IIO_INTENSITY: + ret = regmap_read(iqs62x->map, chan->address, val); + if (ret) + return ret; + + *val &= IQS621_ALS_FLAGS_RANGE; + return IIO_VAL_INT; + + case IIO_PROXIMITY: + case IIO_LIGHT: + ret = regmap_raw_read(iqs62x->map, chan->address, &val_buf, + sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int iqs621_als_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + int ret; + + mutex_lock(&iqs621_als->lock); + + switch (chan->type) { + case IIO_LIGHT: + ret = iqs621_als->light_en; + break; + + case IIO_INTENSITY: + ret = iqs621_als->range_en; + break; + + case IIO_PROXIMITY: + ret = iqs621_als->prox_en; + break; + + default: + ret = -EINVAL; + } + + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static int iqs621_als_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int val; + int ret; + + mutex_lock(&iqs621_als->lock); + + ret = regmap_read(iqs62x->map, iqs62x->dev_desc->als_flags, &val); + if (ret) + goto err_mutex; + iqs621_als->als_flags = val; + + switch (chan->type) { + case IIO_LIGHT: + ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->range_en | state ? 0 : + 0xFF); + if (!ret) + iqs621_als->light_en = state; + break; + + case IIO_INTENSITY: + ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->light_en | state ? 0 : + 0xFF); + if (!ret) + iqs621_als->range_en = state; + break; + + case IIO_PROXIMITY: + ret = regmap_read(iqs62x->map, IQS622_IR_FLAGS, &val); + if (ret) + goto err_mutex; + iqs621_als->ir_flags = val; + + ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->ir_mask, + state ? 0 : 0xFF); + if (!ret) + iqs621_als->prox_en = state; + break; + + default: + ret = -EINVAL; + } + +err_mutex: + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static int iqs621_als_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + int ret = IIO_VAL_INT; + + mutex_lock(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = iqs621_als->thresh_light * 16; + break; + + case IIO_EV_DIR_FALLING: + *val = iqs621_als->thresh_dark * 4; + break; + + case IIO_EV_DIR_EITHER: + if (iqs621_als->ir_flags_mask == IQS622_IR_FLAGS_TOUCH) + *val = iqs621_als->thresh_prox * 4; + else + *val = iqs621_als->thresh_prox; + break; + + default: + ret = -EINVAL; + } + + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static int iqs621_als_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int thresh_reg, thresh_val; + u8 ir_flags_mask, *thresh_cache; + int ret = -EINVAL; + + mutex_lock(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + thresh_reg = IQS621_ALS_THRESH_LIGHT; + thresh_val = val / 16; + + thresh_cache = &iqs621_als->thresh_light; + ir_flags_mask = 0; + break; + + case IIO_EV_DIR_FALLING: + thresh_reg = IQS621_ALS_THRESH_DARK; + thresh_val = val / 4; + + thresh_cache = &iqs621_als->thresh_dark; + ir_flags_mask = 0; + break; + + case IIO_EV_DIR_EITHER: + /* + * The IQS622 supports two detection thresholds, both measured + * in the same arbitrary units reported by read_raw: proximity + * (0 through 255 in steps of 1), and touch (0 through 1020 in + * steps of 4). + * + * Based on the single detection threshold chosen by the user, + * select the hardware threshold that gives the best trade-off + * between range and resolution. + * + * By default, the close-range (but coarse) touch threshold is + * chosen during probe. + */ + switch (val) { + case 0 ... 255: + thresh_reg = IQS622_IR_THRESH_PROX; + thresh_val = val; + + ir_flags_mask = IQS622_IR_FLAGS_PROX; + break; + + case 256 ... 1020: + thresh_reg = IQS622_IR_THRESH_TOUCH; + thresh_val = val / 4; + + ir_flags_mask = IQS622_IR_FLAGS_TOUCH; + break; + + default: + goto err_mutex; + } + + thresh_cache = &iqs621_als->thresh_prox; + break; + + default: + goto err_mutex; + } + + if (thresh_val > 0xFF) + goto err_mutex; + + ret = regmap_write(iqs62x->map, thresh_reg, thresh_val); + if (ret) + goto err_mutex; + + *thresh_cache = thresh_val; + iqs621_als->ir_flags_mask = ir_flags_mask; + +err_mutex: + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static const struct iio_info iqs621_als_info = { + .read_raw = &iqs621_als_read_raw, + .read_event_config = iqs621_als_read_event_config, + .write_event_config = iqs621_als_write_event_config, + .read_event_value = iqs621_als_read_event_value, + .write_event_value = iqs621_als_write_event_value, +}; + +static const struct iio_event_spec iqs621_als_range_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec iqs621_als_light_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec iqs621_als_channels[] = { + { + .type = IIO_INTENSITY, + .address = IQS621_ALS_FLAGS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_range_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_range_events), + }, + { + .type = IIO_LIGHT, + .address = IQS621_ALS_UI_OUT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .event_spec = iqs621_als_light_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_light_events), + }, +}; + +static const struct iio_event_spec iqs622_als_prox_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec iqs622_als_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_BOTH, + .address = IQS622_ALS_FLAGS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_range_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_range_events), + .modified = true, + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .address = IQS622_IR_RANGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = true, + }, + { + .type = IIO_PROXIMITY, + .address = IQS622_IR_UI_OUT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs622_als_prox_events, + .num_event_specs = ARRAY_SIZE(iqs622_als_prox_events), + }, +}; + +static int iqs621_als_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs621_als_private *iqs621_als; + struct iio_dev *indio_dev; + unsigned int val; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als)); + if (!indio_dev) + return -ENOMEM; + + iqs621_als = iio_priv(indio_dev); + iqs621_als->iqs62x = iqs62x; + + if (iqs62x->dev_desc->prod_num == IQS622_PROD_NUM) { + ret = regmap_read(iqs62x->map, IQS622_IR_THRESH_TOUCH, &val); + if (ret) + return ret; + iqs621_als->thresh_prox = val; + iqs621_als->ir_flags_mask = IQS622_IR_FLAGS_TOUCH; + + indio_dev->channels = iqs622_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs622_als_channels); + } else { + ret = regmap_read(iqs62x->map, IQS621_ALS_THRESH_LIGHT, &val); + if (ret) + return ret; + iqs621_als->thresh_light = val; + + ret = regmap_read(iqs62x->map, IQS621_ALS_THRESH_DARK, &val); + if (ret) + return ret; + iqs621_als->thresh_dark = val; + + indio_dev->channels = iqs621_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels); + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs621_als_info; + + mutex_init(&iqs621_als->lock); + + iqs621_als->notifier.notifier_call = iqs621_als_notifier; + ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs621_als_notifier_unregister, + iqs621_als); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs621_als_platform_driver = { + .driver = { + .name = IQS621_DRV_NAME_ALS, + }, + .probe = iqs621_als_probe, +}; +module_platform_driver(iqs621_als_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS621/622 Ambient Light Sensors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS621_DRV_NAME_ALS); From patchwork Mon Jan 6 00:48:06 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11318567 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 971791580 for ; Mon, 6 Jan 2020 00:48:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 752882072C for ; Mon, 6 Jan 2020 00:48:24 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="H6woFCvy" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727226AbgAFAsX (ORCPT ); Sun, 5 Jan 2020 19:48:23 -0500 Received: from mail-eopbgr750070.outbound.protection.outlook.com ([40.107.75.70]:6116 "EHLO NAM02-BL2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726526AbgAFAsX (ORCPT ); Sun, 5 Jan 2020 19:48:23 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=lg2nicEoWx4WeY6ouEQyK3K9lJnMvN4ReaJ5DadLHT4Frz5oEi1Afkx11s9qIA3QVly3e/WRKe+0hvEvD4FQYncNX0GR7cbmpfvXrbqEwPEei01KlGQMUS2NISjHsK6ljD67cGyyrmehjE6tUxIGb+5TQt3iNFT87W6FO2uYG0UMnHu437BoBWFIOalMlSGqAv5PNMbIyEoxPJfjHh6JupvbDUSsjjauJymTCR/UH27N6/JytiPuH3h9xM3O3zIE2dQX6aM4SYECxPi6Lx3OUon1sETdJ+GDfyOUJszvB7e/086WnmFdfKAcAHQpmYmxrWzsCd2n2WooTDruK2KFEA== 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=V6v0bLZmI4PP6zWQ7iFOwvPQj0+XJkSCeIt3k7jT1t4=; b=GVW1CA9GlVkBIwmoveAeOIzWtSg27/ICnmq5NtLZmlZ2ctEP7MHmhpTqv5pvN0AuRPSGonJ0NJZMUvx9tHPwnUdK8+pJiXjDieeGaNm7TcRBNP6OLcQ2BvqGRkqqIRmtDq7+MrgvBdgTjBxtwKVLi05Xhp4dc+Q93h2bf6cpoYeykp3e5NSGhOc7btYYSvETgBANVu0GHvJll2r+9l04Qit7r7gbBXIEdV2ESFn47UA3v/TCwyLUjWS1CZuHUFvql5Lpo1Jyps+4E0J159o1spUFnsEQudLmAZCiu0c7M5l6AhtEreg6vf0eVkxm839fW5sgML1TyAyzm0iPH99UHw== 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=V6v0bLZmI4PP6zWQ7iFOwvPQj0+XJkSCeIt3k7jT1t4=; b=H6woFCvyzyGgAszMRRlXsUXKJGa6IcQT6CDgPp4YB1IeOfzJmdpkcAvajuV/o9HU899iR8c9wLZaK8cHhl6R/AFTB6QG3XG/siRal6SEHQSozmdas/JuSdcITsJcvbvSWvx4NIjDGl86r3zQ9iPp7bU5Gc4XYUEMfI0pc5Tgcmw= Received: from SN6PR08MB5053.namprd08.prod.outlook.com (52.135.107.153) by SN6PR08MB5629.namprd08.prod.outlook.com (20.178.6.150) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2602.15; Mon, 6 Jan 2020 00:48:08 +0000 Received: from SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139]) by SN6PR08MB5053.namprd08.prod.outlook.com ([fe80::7c80:2b62:5d9a:2139%4]) with mapi id 15.20.2602.015; Mon, 6 Jan 2020 00:48:08 +0000 Received: from localhost.localdomain (136.49.227.119) by SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.20.2602.13 via Frontend Transport; Mon, 6 Jan 2020 00:48:06 +0000 From: Jeff LaBundy To: "lee.jones@linaro.org" , "dmitry.torokhov@gmail.com" , "thierry.reding@gmail.com" , "jic23@kernel.org" , "devicetree@vger.kernel.org" CC: "linux-input@vger.kernel.org" , "u.kleine-koenig@pengutronix.de" , "linux-pwm@vger.kernel.org" , "knaack.h@gmx.de" , "lars@metafoo.de" , "pmeerw@pmeerw.net" , "linux-iio@vger.kernel.org" , "robh+dt@kernel.org" , "mark.rutland@arm.com" , Jeff LaBundy Subject: [PATCH v3 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Thread-Topic: [PATCH v3 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Thread-Index: AQHVxCr1epeZE2lp7EG7ASuCK0LX4Q== Date: Mon, 6 Jan 2020 00:48:06 +0000 Message-ID: <1578271620-2159-8-git-send-email-jeff@labundy.com> References: <1578271620-2159-1-git-send-email-jeff@labundy.com> In-Reply-To: <1578271620-2159-1-git-send-email-jeff@labundy.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-clientproxiedby: SN4PR0701CA0020.namprd07.prod.outlook.com (2603:10b6:803:28::30) To SN6PR08MB5053.namprd08.prod.outlook.com (2603:10b6:805:78::25) authentication-results: spf=none (sender IP is ) smtp.mailfrom=jeff@labundy.com; x-ms-exchange-messagesentrepresentingtype: 1 x-mailer: git-send-email 2.7.4 x-originating-ip: [136.49.227.119] x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: 0d5e57c0-3368-4677-f870-08d792421820 x-ms-traffictypediagnostic: SN6PR08MB5629: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:9508; x-forefront-prvs: 0274272F87 x-forefront-antispam-report: SFV:NSPM;SFS:(10009020)(136003)(396003)(346002)(376002)(39830400003)(366004)(34096005)(189003)(199004)(7416002)(316002)(956004)(2616005)(6512007)(110136005)(69590400006)(81156014)(86362001)(81166006)(8676002)(508600001)(71200400001)(54906003)(6486002)(107886003)(8936002)(30864003)(4326008)(2906002)(66946007)(66476007)(66446008)(64756008)(66556008)(16526019)(6506007)(52116002)(186003)(26005)(36756003)(5660300002);DIR:OUT;SFP:1101;SCL:1;SRVR:SN6PR08MB5629;H:SN6PR08MB5053.namprd08.prod.outlook.com;FPR:;SPF:None;LANG:en;PTR:InfoNoRecords;MX:1;A:1; received-spf: None (protection.outlook.com: labundy.com does not designate permitted sender hosts) x-ms-exchange-senderadcheck: 1 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: zLZqRUTF06eEaz54kXl3Ic4drc9H15lJR5vslO9wpMng05tDa8dVb5xGBn5+1EE0ledCuwNGah6fr1p+qn3LCYNerFFdKHOu0OdTcgR5/E0h3hpPW2ImJSRwoJaj4V9kp4bKTjlMpUcT740LDyPegC2oJSHBbASnwfdPGKT+8G2GalIvxS/e/SZ1nkaNUk61xMNl/m/xA8fU3NEErthcrJirGnTClx5BZhphxKEM8ITeD31zs7qXmG+dOLxeYBJJm+3rcXVKtr2MTViT+lw8EsZ5CufM/uE5N7522n1AJV7NdA6ymi2rv8Q2Ftw1NOsHHgJjPlhdsOIAKRNty4h5ndXogkCJTPIvckgGwd5bAbVh3HRMI7TX0xmLOO8RIbbRZZhI9atinTLJda2s7HHuhXJMJoKngmxDKSKiEAY2joqYjTQ4mNP1qCLnqMVOyLZdBW/2o5RihSFGahjeF0Y+YLQ9hUMNJ7kRLeV2J9d3wB2FzeBUtvJpgJYamh4NKW01 MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: 0d5e57c0-3368-4677-f870-08d792421820 X-MS-Exchange-CrossTenant-originalarrivaltime: 06 Jan 2020 00:48:06.8714 (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: y/Kcik1f5x+g1eNOwDczCVw+2DvVWxzOMqkousiZgQcacPO4TRrcN7C5As/TRya/UApH80vKi9F9ueWET8UAGg== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN6PR08MB5629 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS624 and IQS625 angular position sensors, capable of reporting the angle of a rotating shaft down to 1 and 10 degrees of accuracy, respectively. This patch also introduces a home for linear and angular position sensors. Unlike resolvers, they are typically contactless and use the Hall effect. Signed-off-by: Jeff LaBundy Reviewed-by: Jonathan Cameron --- Changes in v3: - Added Reviewed-by trailer Changes in v2: - Merged 'Copyright' and 'Author' lines into one in introductory comments - Replaced 'error' with 'ret' throughout - Added iqs624_pos_angle_en and iqs624_pos_angle_get to remove duplicate logic previously used throughout - Refactored the logic in iqs624_pos_notifier and added a lock to safely evaluate variables that may change in response to user action - Refactored the logic in iqs624_pos_read_raw - Added a lock to iqs624_pos_read_event_config to account for cases in which the corresponding hardware state is in the process of being updated - Refactored the logic in iqs624_pos_write_event_config and read the initial angle in case it changed since having first been read in iqs624_pos_init - Removed iqs624_pos_init as its logic has since been absorbed elsewhere - Removed devm_add_action_or_reset failure message - Eliminated tabbed alignment of platform_driver struct members - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/position/Kconfig | 19 +++ drivers/iio/position/Makefile | 7 + drivers/iio/position/iqs624-pos.c | 284 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 312 insertions(+) create mode 100644 drivers/iio/position/Kconfig create mode 100644 drivers/iio/position/Makefile create mode 100644 drivers/iio/position/iqs624-pos.c -- 2.7.4 diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 5bd5185..d5c073a 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -88,6 +88,7 @@ source "drivers/iio/orientation/Kconfig" if IIO_TRIGGER source "drivers/iio/trigger/Kconfig" endif #IIO_TRIGGER +source "drivers/iio/position/Kconfig" source "drivers/iio/potentiometer/Kconfig" source "drivers/iio/potentiostat/Kconfig" source "drivers/iio/pressure/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index bff682a..1712011 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -31,6 +31,7 @@ obj-y += light/ obj-y += magnetometer/ obj-y += multiplexer/ obj-y += orientation/ +obj-y += position/ obj-y += potentiometer/ obj-y += potentiostat/ obj-y += pressure/ diff --git a/drivers/iio/position/Kconfig b/drivers/iio/position/Kconfig new file mode 100644 index 0000000..eda67f0 --- /dev/null +++ b/drivers/iio/position/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Linear and angular position sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Linear and angular position sensors" + +config IQS624_POS + tristate "Azoteq IQS624/625 angular position sensors" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS624 + and IQS625 angular position sensors. + + To compile this driver as a module, choose M here: the module + will be called iqs624-pos. + +endmenu diff --git a/drivers/iio/position/Makefile b/drivers/iio/position/Makefile new file mode 100644 index 0000000..3cbe7a7 --- /dev/null +++ b/drivers/iio/position/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for IIO linear and angular position sensors +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_IQS624_POS) += iqs624-pos.o diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c new file mode 100644 index 0000000..af629bf5 --- /dev/null +++ b/drivers/iio/position/iqs624-pos.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS624/625 Angular Position Sensors + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS624_POS_DEG_OUT 0x16 + +#define IQS624_POS_SCALE1 (314159 / 180) +#define IQS624_POS_SCALE2 100000 + +struct iqs624_pos_private { + struct iqs62x_core *iqs62x; + struct notifier_block notifier; + struct mutex lock; + bool angle_en; + u16 angle; +}; + +static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en) +{ + unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT; + + /* + * The IQS625 reports angular position in the form of coarse intervals, + * so only interval change events are unmasked. Conversely, the IQS624 + * reports angular position down to one degree of resolution, so wheel + * movement events are unmasked instead. + */ + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + event_mask = IQS624_HALL_UI_INT_EVENT; + + return regmap_update_bits(iqs62x->map, IQS624_HALL_UI, event_mask, + angle_en ? 0 : 0xFF); +} + +static int iqs624_pos_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs624_pos_private *iqs624_pos; + struct iqs62x_core *iqs62x; + struct iio_dev *indio_dev; + u16 angle = event_data->ui_data; + s64 timestamp; + int ret; + + iqs624_pos = container_of(notifier, struct iqs624_pos_private, + notifier); + indio_dev = iio_priv_to_dev(iqs624_pos); + timestamp = iio_get_time_ns(indio_dev); + + iqs62x = iqs624_pos->iqs62x; + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + angle = event_data->interval; + + mutex_lock(&iqs624_pos->lock); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", ret); + ret = NOTIFY_BAD; + } else { + ret = NOTIFY_OK; + } + } else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + + iqs624_pos->angle = angle; + ret = NOTIFY_OK; + } else { + ret = NOTIFY_DONE; + } + + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static void iqs624_pos_notifier_unregister(void *context) +{ + struct iqs624_pos_private *iqs624_pos = context; + struct iio_dev *indio_dev = iio_priv_to_dev(iqs624_pos); + int ret; + + ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); + if (ret) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val) +{ + int ret; + __le16 val_buf; + + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + return regmap_read(iqs62x->map, iqs62x->dev_desc->interval, + val); + + ret = regmap_raw_read(iqs62x->map, IQS624_POS_DEG_OUT, &val_buf, + sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + + return 0; +} + +static int iqs624_pos_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; + unsigned int scale = 1; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iqs624_pos_angle_get(iqs62x, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) { + ret = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV, + &scale); + if (ret) + return ret; + } + + *val = scale * IQS624_POS_SCALE1; + *val2 = IQS624_POS_SCALE2; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } +} + +static int iqs624_pos_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + int ret; + + mutex_lock(&iqs624_pos->lock); + ret = iqs624_pos->angle_en; + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static int iqs624_pos_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; + unsigned int val; + int ret; + + mutex_lock(&iqs624_pos->lock); + + ret = iqs624_pos_angle_get(iqs62x, &val); + if (ret) + goto err_mutex; + + ret = iqs624_pos_angle_en(iqs62x, state); + if (ret) + goto err_mutex; + + iqs624_pos->angle = val; + iqs624_pos->angle_en = state; + +err_mutex: + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static const struct iio_info iqs624_pos_info = { + .read_raw = &iqs624_pos_read_raw, + .read_event_config = iqs624_pos_read_event_config, + .write_event_config = iqs624_pos_write_event_config, +}; + +static const struct iio_event_spec iqs624_pos_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec iqs624_pos_channels[] = { + { + .type = IIO_ANGL, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = iqs624_pos_events, + .num_event_specs = ARRAY_SIZE(iqs624_pos_events), + }, +}; + +static int iqs624_pos_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs624_pos_private *iqs624_pos; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos)); + if (!indio_dev) + return -ENOMEM; + + iqs624_pos = iio_priv(indio_dev); + iqs624_pos->iqs62x = iqs62x; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->channels = iqs624_pos_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels); + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs624_pos_info; + + mutex_init(&iqs624_pos->lock); + + iqs624_pos->notifier.notifier_call = iqs624_pos_notifier; + ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs624_pos_notifier_unregister, + iqs624_pos); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs624_pos_platform_driver = { + .driver = { + .name = IQS624_DRV_NAME_POS, + }, + .probe = iqs624_pos_probe, +}; +module_platform_driver(iqs624_pos_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS624_DRV_NAME_POS);