From patchwork Mon Dec 9 00:38:32 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 1205802 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=labundy.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="nPrLbBrm"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47WPSh69Kvz9sPK for ; Mon, 9 Dec 2019 11:38:44 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726718AbfLIAin (ORCPT ); Sun, 8 Dec 2019 19:38:43 -0500 Received: from mail-mw2nam12on2065.outbound.protection.outlook.com ([40.107.244.65]:11831 "EHLO NAM12-MW2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726654AbfLIAin (ORCPT ); Sun, 8 Dec 2019 19:38:43 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=N621hrktsC90BHws7QSdCQbMqgMvoRhBTqkDTpJnzmIAO0YwlHsjsJGqEpUkpfs2I7NisHvVCnFjL0kqRJpQbtMbOJ3CZQcARlYhSDv9p8Jm9JAIuPTPSipXBwTOX8PGgObEUZOLLdjxNWERmYoVQepJOxcfRdVVA3Um4vvRWzDcs4ghbbmA4gvwA1C6/2nvNKSqq3Rwe/sUxI5lA9tW5SP9zAgbmecyT4/fYoFXaZT6htvYzIYe2ot9aABE81j8LYbQForgKAJEPacHL08WPKsbKsSwMYEN1XtDb3t0qTAK22plZCHrhE6Eg1eKdeg3cAQw4H81ygs/oV5bvNgi7w== 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=KJu2GRi4HrHCo4OB/bnX69S3TccINdZu0YRb64+UvjE=; b=YZ0+Qu5Ar7Nf036El7sdUKYXSlgvUct356QN243ZPfsiEd9uijG6PMbnVgIBSkGH6mFXEH8jMmPQTIoywDZB6f9MEpPT9qksmOWsCcUpR/pX0F9KIlVfLnwwbQyXLt174OMGwCQ15ZRxmKM0+flA5AnqpQUwd+ribvml/yW4MKlFIVoA2chGt5q112QGXXN55iGJzKmNYW2CgzkI5zDQIfZsJwQFumQKlDaos8EaWAazt7GvkHqDhSRpq27LCyCSMwkKlvxEVdmPM/7oefQgc5IUZxjGl4k1VWnEcIEXwrNh6nYuy0w6S7Ypt44xb0brf8ZaiWx0Xu2UvauSacDl1A== 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=KJu2GRi4HrHCo4OB/bnX69S3TccINdZu0YRb64+UvjE=; b=nPrLbBrmPAfSkVvYlY0OOTe5Nn4ONL4hh1E31SJD7grNx4d4bGrDMdFcQg4BJljdKjEOMQsPVtyUMAYgLFHsbu99J41VCA+UdRanMIM/J/cpGzqPdP7iBz1x5DD3yzOpWaQw3bN2EtDsJgOq6g3D0rxHn3n/wH8nRBEpfXvcedY= Received: from BN7PR08MB5042.namprd08.prod.outlook.com (20.176.177.14) by BN7PR08MB4402.namprd08.prod.outlook.com (52.132.223.26) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2516.12; Mon, 9 Dec 2019 00:38:32 +0000 Received: from BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1]) by BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1%6]) with mapi id 15.20.2516.018; Mon, 9 Dec 2019 00:38:32 +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 v2 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625 Thread-Topic: [PATCH v2 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625 Thread-Index: AQHVrij7SDxpuO5/BEuAwvUFNi/Xqg== Date: Mon, 9 Dec 2019 00:38:32 +0000 Message-ID: <1575851866-18919-2-git-send-email-jeff@labundy.com> References: <1575851866-18919-1-git-send-email-jeff@labundy.com> In-Reply-To: <1575851866-18919-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: SN1PR12CA0082.namprd12.prod.outlook.com (2603:10b6:802:21::17) To BN7PR08MB5042.namprd08.prod.outlook.com (2603:10b6:408:2c::14) 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: b1be61e4-8b90-4c65-3440-08d77c401e29 x-ms-traffictypediagnostic: BN7PR08MB4402: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:8273; x-forefront-prvs: 02462830BE x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(376002)(34096005)(366004)(136003)(396003)(346002)(39830400003)(199004)(189003)(99286004)(76176011)(71200400001)(52116002)(71190400001)(54906003)(4326008)(2906002)(6512007)(38610400001)(66556008)(66476007)(6486002)(66946007)(586005)(64756008)(66446008)(110136005)(107886003)(36756003)(966005)(8936002)(102836004)(50226002)(30864003)(305945005)(2616005)(7416002)(5660300002)(6506007)(81166006)(81156014)(498600001)(26005)(86362001)(186003)(8676002); DIR:OUT; SFP:1101; SCL:1; SRVR:BN7PR08MB4402; H:BN7PR08MB5042.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: BLMNwunzV0zvB8EZ46e4KAv3Xh4EvosltCgm3OupnoTkfGpUJ8tfhjGxT2aEOiaaB3auF+SBivUGF6IRQTWWsyp6806+wbPY5Y9qAjUU7Iv+bXyXjg2bRruoifhBj/hlZJIym8vm3csKbWK5iZxKyH/pKtbJ+pvEY+sUWFdLpFmOSSJNKpDvOaIUOC/2ExFD/MnDVhRtXEs/wBj+RKQXuNu3HOIx/Y0FalKLaVWfOKoRcmqUZwJebbvfIg95YxgRsU6IkV5Rj5pf/Io0vQp4hKb1LchtIQyWhD3VRdwPJtCvZe4N4vOUXG4DEy+Bl8WVb2ea4WJl6FVOywPgxDQr8CeA6BVRtmJiJQw0GP8WTjKHiAqLYFCV220qlSqKm5SzvbRtM/kqUHlilD2lWzQxmlG2mgMOUsK8//G+KDAg6NVHssWzALOMOZlH8xBILVROvc8E6K1xcV34GfeUk6se68yAZvvGlxOgQ7fyonB3Qyo= MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: b1be61e4-8b90-4c65-3440-08d77c401e29 X-MS-Exchange-CrossTenant-originalarrivaltime: 09 Dec 2019 00:38:32.5251 (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: Gcn8wnr0UoSGz0G7Rs35QmfxqLH0UzyrE1Q9C6blsqSrIZlP7/iJqByXKB6lRYA5L3R4WbXsuwF+Vei9dK1VZQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: BN7PR08MB4402 Sender: linux-pwm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pwm@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 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 | 126 +++++++++++++++ Documentation/devicetree/bindings/mfd/iqs62x.yaml | 177 +++++++++++++++++++++ .../devicetree/bindings/pwm/iqs620a-pwm.yaml | 30 ++++ 3 files changed, 333 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..e9b54e0 --- /dev/null +++ b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml @@ -0,0 +1,126 @@ +# 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. + +required: + - compatible + - linux,keycodes + +if: + properties: + compatible: + contains: + enum: + - azoteq,iqs620a-keys + - azoteq,iqs621-keys + - azoteq,iqs622-keys +then: + 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 + +... diff --git a/Documentation/devicetree/bindings/mfd/iqs62x.yaml b/Documentation/devicetree/bindings/mfd/iqs62x.yaml new file mode 100644 index 0000000..24e6004 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/iqs62x.yaml @@ -0,0 +1,177 @@ +# 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 + +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..6b7aaef --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml @@ -0,0 +1,30 @@ +# 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" + +... From patchwork Mon Dec 9 00:38:34 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 1205807 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=labundy.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="A5vg9w06"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47WPSq6x43z9sR0 for ; Mon, 9 Dec 2019 11:38:51 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726845AbfLIAiv (ORCPT ); Sun, 8 Dec 2019 19:38:51 -0500 Received: from mail-mw2nam12on2087.outbound.protection.outlook.com ([40.107.244.87]:6163 "EHLO NAM12-MW2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726596AbfLIAiv (ORCPT ); Sun, 8 Dec 2019 19:38:51 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=Ipm61//V/7teXbpOZ7tn2NkHO/IzM+YogA8jdjVV5wrrgmly6BBgWBGNwqk6DjSIT3vZDG+m3DIBhR7yslYT8uLMPj9YOQ5cHk9PNZkNIpbHEmdPbYqUIjZK63mfAX8GXbGScg2qxJf9umZo1EGrOSUh/36+KrAD7xpbhFS55HatTxDVm5FsgV/4l9GZA2PFI6XFIQS4gDueaw2K8KvATtda1pmo7zSoqs3vlwyiTxGaa3UIpMGpaAO3IEv0qlk55qGt2zOF/mNWpMuoxaAiGf2+z24+zz8wCkKLYy8p9irl4DxpGU0UaAu3SLkAnuSRjrV+bxiHFa70DLEzX0nHag== 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=YnJpk7HUAj5HFvwlsSkV3ISo+1K4bSpSauHDGB78HRg=; b=jSVedJd8/Ypw6L2K+skPM+PuujAC7kPDMcZaYPqKFmzKKM3loGQVZIfC79dHes0tCtGbUv67jr0Sr+KcNVY4ZJL2buve9vjsHe2oWvbJTCsNY2LQjPbzJc5MgNLRhbFKmObkIUbEIcM0E9i0lcxpv0pW3X9S5kB0P2HCSoh1JV0kY4YcJj8mSDwwrKfDmNHt8yeHVUoHBm4VUcEwZFNxfuSw/vw4/hvuQ5ES64AnMpwsU3qHXb3N6WTViHEf1yYHxhuwfYu24b6+5ybEpMfe9+N1S1c4pDY54gYkwwfJhzBWJ1GrGLpporQKxzhgHgztubMRRp+bgPjMxK3/FBGm1Q== 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=YnJpk7HUAj5HFvwlsSkV3ISo+1K4bSpSauHDGB78HRg=; b=A5vg9w06dFTUR+cP3oIvaZiFnjl6LB+nA8y/8MWrpA96ZiOLochv3Ih6uA8Vv5AnoCQtgyzfGisk2LSLK2KLw8CL15Bz9PRoFch5o862vQ2Sw4a6nMQTca0DaZW5d8NVjnHJeCosNxky3lSjUerObbY9ypbAwZp/j91Yo9H2lkA= Received: from BN7PR08MB5042.namprd08.prod.outlook.com (20.176.177.14) by BN7PR08MB4402.namprd08.prod.outlook.com (52.132.223.26) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2516.12; Mon, 9 Dec 2019 00:38:34 +0000 Received: from BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1]) by BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1%6]) with mapi id 15.20.2516.018; Mon, 9 Dec 2019 00:38:34 +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 v2 2/7] mfd: Add support for Azoteq IQS620A/621/622/624/625 Thread-Topic: [PATCH v2 2/7] mfd: Add support for Azoteq IQS620A/621/622/624/625 Thread-Index: AQHVrij87naVyfxwsUWDW/KNdR436g== Date: Mon, 9 Dec 2019 00:38:34 +0000 Message-ID: <1575851866-18919-3-git-send-email-jeff@labundy.com> References: <1575851866-18919-1-git-send-email-jeff@labundy.com> In-Reply-To: <1575851866-18919-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: SN1PR12CA0082.namprd12.prod.outlook.com (2603:10b6:802:21::17) To BN7PR08MB5042.namprd08.prod.outlook.com (2603:10b6:408:2c::14) 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: 521eb19f-f44b-49c4-70a0-08d77c401f14 x-ms-traffictypediagnostic: BN7PR08MB4402: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:7691; x-forefront-prvs: 02462830BE x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(376002)(34096005)(366004)(136003)(396003)(346002)(39830400003)(60444003)(38354002)(199004)(189003)(99286004)(76176011)(71200400001)(52116002)(71190400001)(54906003)(4326008)(2906002)(6512007)(38610400001)(66556008)(66476007)(6486002)(66946007)(586005)(64756008)(66446008)(110136005)(107886003)(36756003)(966005)(8936002)(102836004)(50226002)(30864003)(305945005)(2616005)(7416002)(5660300002)(6506007)(81166006)(81156014)(498600001)(26005)(86362001)(186003)(8676002)(461764006)(559001)(579004)(569006); DIR:OUT; SFP:1101; SCL:1; SRVR:BN7PR08MB4402; H:BN7PR08MB5042.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: RAXdZDmx1Tg0rya8Cxx5GxuEo3FCgKOu1YpGRry0O9G/I4+JPE8UsZPVgHlBvvtRKk8uA9dUPoTq2AXLAtYuLNmsXzdMBQmMFizmlOvl4U1ltVsMTkCUzHtGhVhE8NydQRAG38ajCLQLAsLfLG1c33D/XCeU4l7RYtGgtThAmAV2wdG7vGIO3dL6ZJ2F9BbQxaxvAyyyVI8IEClSub0nYmLFO6wkw2vkWSU3jQwAFbhMw2rwq7c9YAE884jkD8buTMAHKI84mlAuqb8L6aostzBzpzWEKtUGggxtPVVkbi1k3OORaIqRWyqmoNQl4sgj4fdJh0RnLIsLTL5L7liekihzh+ZITbdh1mSR2VP37GLNEElTAoP8kntIYp7sjnBFwHdoY5Hc9YY5Z4CATK2P6/GgJHRdNbS4NGtE7/yHPTHcrflrxYr94VIjLeTgJ+wWpICsnr9KvjLCGHTxzDcbyZJpX0umD9NPj6hK1pUAhoBWKJ/a9gy62usUm26rwU8TH85470djIbQ9gJqyg3D3TMGQjGEM133/K31QcfTUzAbRJ9Xj/ZbdS85dmu1mEx9Qsiyum+MpIYSsg47+ct1iEA== MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: 521eb19f-f44b-49c4-70a0-08d77c401f14 X-MS-Exchange-CrossTenant-originalarrivaltime: 09 Dec 2019 00:38:34.0867 (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: owJl5b1PdO0JFLD4mv2UNEykN7j3tIGQbsUg3vY0qbfiOEPgOiU1U3SnHhE1mSbJz2w2K0dGwHRuTRsaZ3DS0g== X-MS-Exchange-Transport-CrossTenantHeadersStamped: BN7PR08MB4402 Sender: linux-pwm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pwm@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 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 Dec 9 00:38:35 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 1205804 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=labundy.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="WdABihN8"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47WPSk3gjWz9sPK for ; Mon, 9 Dec 2019 11:38:46 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726793AbfLIAip (ORCPT ); Sun, 8 Dec 2019 19:38:45 -0500 Received: from mail-mw2nam12on2065.outbound.protection.outlook.com ([40.107.244.65]:11831 "EHLO NAM12-MW2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726659AbfLIAio (ORCPT ); Sun, 8 Dec 2019 19:38:44 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=P2BAulDkUxnf4Aic6Q96US8iV7x+ocyqoz/cuFAaTuN78q3YZwdg//50ZhjS7UwS5NJRDBCS1QJDO0ZVtx0q5AazdA2cCAM90t4Fd87430A9abKuBD4H4ECMc+hu/yXG1CWOXbjn/cUVBOjhSb0mKw1QEJH1I3cJJN1HrNscLwJQH8Uaj52KIxHVONDTPXyN1GZIQz3/U14y4++RI452O6l6aOC8Eu/4f3DVnPT4uxUBp3OgLIiVkO2eJCi7PcVLUmJS+ZgVyjxTSJIeYh39u/QWLH+1QyQ4JCXoV9+hUOVfUGAk6f/h9I5ZS4spZt8xJvZo7OhMGiuA/II9k/q8Aw== 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=IdGVSsvFfBLzJsDLULuN078Ft1c8An0zp0bohKishv4=; b=DMHF2hZ6oDRHxZZdvQgXrlnTbZ1Ko/Ne+cLOLKMJozT4FiOKUxacBdvKXI9a3YgmAmA07WxqUPLpe/2PqdTIhag8XbwTqHZDr/6AGcZG6I5IEhCT1ezklyolMbI5BY3ZeWDrCY8h1EE3L/9U0KtRl2VZ70wYfjj4Xm59lhVUQvzk8ED8GjQhKJ72RqX5k3s+/6kqq5HZT8pC0zq8WrOnM6MPrHI25wSGv2tp6oMb+TjCYYcws64WzUK6WQBbbEmFbMgiaA3PIr2ERTF3wqjp2KIwYy0qKI8wGpZa3MOaHXQhAWT1hhxe19NnVSRsuPiea7Vy/x8KuWthzYUustPC0Q== 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=IdGVSsvFfBLzJsDLULuN078Ft1c8An0zp0bohKishv4=; b=WdABihN82sijKeU9jmABa2c53aepMxHF/icWzTvV6ldvHYlmOt62gZFgjtEuFVk0H2hhfOE3FLeLZOgZ8LnnvgypDrK0bcuM/Y+6iS7W7a6tVLeVF/rn4leDgJG5nxTF0+AseCrjqseWHEtvObzayR3POdUyjsORmvIJoHDAsi4= Received: from BN7PR08MB5042.namprd08.prod.outlook.com (20.176.177.14) by BN7PR08MB4402.namprd08.prod.outlook.com (52.132.223.26) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2516.12; Mon, 9 Dec 2019 00:38:35 +0000 Received: from BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1]) by BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1%6]) with mapi id 15.20.2516.018; Mon, 9 Dec 2019 00:38:35 +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 v2 3/7] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625 Thread-Topic: [PATCH v2 3/7] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625 Thread-Index: AQHVrij9Kfk3AlJ9zEGQVl4oHUIQVA== Date: Mon, 9 Dec 2019 00:38:35 +0000 Message-ID: <1575851866-18919-4-git-send-email-jeff@labundy.com> References: <1575851866-18919-1-git-send-email-jeff@labundy.com> In-Reply-To: <1575851866-18919-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: SN1PR12CA0082.namprd12.prod.outlook.com (2603:10b6:802:21::17) To BN7PR08MB5042.namprd08.prod.outlook.com (2603:10b6:408:2c::14) 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: 6061c0b0-72a2-438b-390f-08d77c401ff7 x-ms-traffictypediagnostic: BN7PR08MB4402: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:3826; x-forefront-prvs: 02462830BE x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(376002)(34096005)(366004)(136003)(396003)(346002)(39830400003)(199004)(189003)(99286004)(76176011)(71200400001)(52116002)(71190400001)(54906003)(4326008)(2906002)(6512007)(38610400001)(66556008)(66476007)(6486002)(66946007)(586005)(64756008)(66446008)(110136005)(107886003)(36756003)(8936002)(102836004)(50226002)(30864003)(305945005)(2616005)(7416002)(5660300002)(6506007)(81166006)(81156014)(498600001)(26005)(86362001)(186003)(8676002); DIR:OUT; SFP:1101; SCL:1; SRVR:BN7PR08MB4402; H:BN7PR08MB5042.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: uafIQNVwABbwDg3ZPh8if9oHbjNi8yYy9uYm6m8FXjco73YtisR+hSvyKR9csebExZycZMPyCxQVLjuOCUIMVIAPPbKigEfny8S3OOaNp4kMd+9nKtFq1c+Vr5/hOQG3ZqJN+sF9iYFFU5oINJu1eDzJucK5iaGJpDelBZ3mH3r7MSzHdG76Qh8Yhhy7tEGwab28HB0O+qjfW1I1ZA3yHUlFj3T2AOPJ1Sq3nVBBPBboV08MbJvKjnySvxA/9d9A1X5EgNM+LMGPNXv2031ihSsGZQyYhKl8AgEmmM4zVQo9FoYXYmosJlu97Ygkcs9Jx9RN3rCoRCln8zHNirsJk7XPILIQStOTM+cA1l7m7KoNn+EwwKVGyNIw6V0pigXuS9VskCajK8xXSkdEfjzpj9ZXMx8HeKfCRjpg+8rdDDKTzWcMiCPyASQXACP7RrHM MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: 6061c0b0-72a2-438b-390f-08d77c401ff7 X-MS-Exchange-CrossTenant-originalarrivaltime: 09 Dec 2019 00:38:35.4761 (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: /jv5mo8sMrq4JGGsV9pU9Dq5IUy0JIxtOftTWz68Ung1RxuyORHIwf5npvQ/Ko0gM5k8wIhqneqLJqD/YsrwMQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: BN7PR08MB4402 Sender: linux-pwm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pwm@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 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 Dec 9 00:38:36 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 1205805 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=labundy.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="uGHsRR75"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47WPSm0GL1z9sPK for ; Mon, 9 Dec 2019 11:38:48 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726823AbfLIAir (ORCPT ); Sun, 8 Dec 2019 19:38:47 -0500 Received: from mail-mw2nam12on2065.outbound.protection.outlook.com ([40.107.244.65]:11831 "EHLO NAM12-MW2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726654AbfLIAir (ORCPT ); Sun, 8 Dec 2019 19:38:47 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=Bz6PkOJKPTTkgIldAs5mRKgAIrYVHDaIFaBr4e2eKcA4sLb7OzkYgatdviuvR9x3V4QT685vY7Ydg+pqEpH15mpyBSa9fn4ZUqDX2kOwr7cvIdGehUzCEQJyOqFmFMzNAYSE1KCJw/QUfpOuwaH7AdKlwdtbGTew1tlJSAqoKfNItm9K4KKziqfaz+2q30Do6E4rKSK1KYWeGckHk2jiDH/sbHKiIxrJLrUOky6vajkJwfxROg5jZhJkljoSGuxg+wNQJAXWLSJMAYE75soB0By0s7soUDck5QR2mgVKBMUbKuI8D093rL1ukHIezbfnPYFNMKt6YrwYNkAp0fq2Xg== 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=uSO6vpuxPuJIEW5Lt1VY9IduJ+r6rltft3Onl7Vl6CI=; b=jmVv0SsfEyZ2p/EPAJ8q0enfRs3Ls37Fw95r1TA0b8gKox+lF3Sn/MQOWGAYYLJuML/89Sjoc7PC2r/r3scymINPCFPTp22DXsh5NF25hudLOntAuqOpX4IEULR/0Shnr5OGxNUc+e+JvVBU6dCs/xrOnOW8g6PEYMcZHv7oe/XYesqUg/gLYKxtu/4G6Qofi0xBEohu0lX38Sh7mO65iw+E+6+8D78rkFc0SHrwqKL9jQ9CA2tf2oRz1B6q3k4+2wkKKqCGvHGkuj2uC131PvKmx4KGCCHuZhymRWNH4qsqBOtg4c4MLyxH70Fy7TwAzTE5hIEcJBtmjZYYYhTqZQ== 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=uSO6vpuxPuJIEW5Lt1VY9IduJ+r6rltft3Onl7Vl6CI=; b=uGHsRR75BL8eRuzDI901bXKP9OZKNaLN7OEJwTBNDQZTzKhKc3VzjKwDS0GLgW3KMjEGot0Al3hUzB+LtN82t1JUOaKRY/JMjVh5+Nv5YBb1FcXLXICI0g9xM5Tgn6v0dn48+aLqDBZ8V6JMcnH2GN6prCzrb5LThhhi43QjuXs= Received: from BN7PR08MB5042.namprd08.prod.outlook.com (20.176.177.14) by BN7PR08MB4402.namprd08.prod.outlook.com (52.132.223.26) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2516.12; Mon, 9 Dec 2019 00:38:37 +0000 Received: from BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1]) by BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1%6]) with mapi id 15.20.2516.018; Mon, 9 Dec 2019 00:38:37 +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 v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator Thread-Topic: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator Thread-Index: AQHVrij+8yO8xEtV+ECzt9/U9i+G6g== Date: Mon, 9 Dec 2019 00:38:36 +0000 Message-ID: <1575851866-18919-5-git-send-email-jeff@labundy.com> References: <1575851866-18919-1-git-send-email-jeff@labundy.com> In-Reply-To: <1575851866-18919-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: SN1PR12CA0082.namprd12.prod.outlook.com (2603:10b6:802:21::17) To BN7PR08MB5042.namprd08.prod.outlook.com (2603:10b6:408:2c::14) 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: 1d4b9967-ac84-444f-344d-08d77c4020dd x-ms-traffictypediagnostic: BN7PR08MB4402: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:8882; x-forefront-prvs: 02462830BE x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(376002)(34096005)(366004)(136003)(396003)(346002)(39830400003)(199004)(189003)(99286004)(76176011)(71200400001)(52116002)(71190400001)(54906003)(4326008)(2906002)(6512007)(38610400001)(66556008)(66476007)(6486002)(66946007)(586005)(64756008)(66446008)(110136005)(107886003)(36756003)(8936002)(102836004)(50226002)(305945005)(2616005)(7416002)(5660300002)(6506007)(81166006)(81156014)(498600001)(26005)(86362001)(186003)(8676002); DIR:OUT; SFP:1101; SCL:1; SRVR:BN7PR08MB4402; H:BN7PR08MB5042.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: Rphtfl0oR5lYDxbKWSJD+9KjMMcxc844/JcYon7Uz292lXk3emGLTdgZDP4bWcyubIJVKVLKEB7a7pL0rBTlvRecz8JcFgkm9uXYmUA2N9Gi7mSi4rKJdjyWCXx+FO9VZQ1cYRUnYYveEoGoOzgZ8sAF8EeQVV82Eb7FUW5GT8U7qvYyWIpYvx0ihuOZpWL9uqLqTLUO9gnjAjDIfGZ88f6rsffDvJsPU6EaN/ZmAbkh7W10EENNOiNolcg0iT18X7nvVQG9g5cHHD+5JogwZpB7vmTlvH0Sn8tmxEsyNYtl9QtuJwcrA6Y1Z+u8ZEQdqcSlrMiXyKxw/jEescHJO1vHhy9rIpEQ0M2I5jg0F3c4M7+QawJ3JgFksGJPX1yiuUNbMj+772oaoz2xIfjcPSXjJHReMm5IjxN02QLd2uNN3CuZpjLuRjL0VapfQfWf MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: 1d4b9967-ac84-444f-344d-08d77c4020dd X-MS-Exchange-CrossTenant-originalarrivaltime: 09 Dec 2019 00:38:36.9853 (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: V6XAybU6eogDX9hrmIcaaphJ0ovTpTifWOF4+nNakLD8FQFwQ62153+9+chbxtpSg0hiONK8FcPN077Ue2kXcQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: BN7PR08MB4402 Sender: linux-pwm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pwm@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 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 | 206 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 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..1ea11b9 --- /dev/null +++ b/drivers/pwm/pwm-iqs620a.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A PWM Generator + * + * Copyright (C) 2019 Jeff LaBundy + * + * Limitations: + * - The period is not guaranteed to run to completion when the duty cycle is + * changed or the output is disabled. + * - The period is fixed to 1 ms. + */ + +#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; +}; + +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; + unsigned int pwm_out = 0; + 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; + + duty_scale = DIV_ROUND_CLOSEST(state->duty_cycle * 256, + IQS620_PWM_PERIOD_NS); + + if (duty_scale) { + ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE, + min(duty_scale - 1, 0xFF)); + if (ret) + return ret; + + if (state->enabled) + pwm_out = IQS620_PWR_SETTINGS_PWM_OUT; + } + + return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, pwm_out); +} + +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; + + ret = regmap_read(iqs62x->map, IQS620_PWR_SETTINGS, &val); + if (ret) + goto err_out; + state->enabled = val & IQS620_PWR_SETTINGS_PWM_OUT; + + ret = regmap_read(iqs62x->map, IQS620_PWM_DUTY_CYCLE, &val); + if (ret) + goto err_out; + state->duty_cycle = DIV_ROUND_CLOSEST((val + 1) * IQS620_PWM_PERIOD_NS, + 256); + state->period = IQS620_PWM_PERIOD_NS; + +err_out: + 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; + struct pwm_state state; + int ret; + + if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET))) + return NOTIFY_DONE; + + iqs620_pwm = container_of(notifier, struct iqs620_pwm_private, + notifier); + pwm_get_state(&iqs620_pwm->chip.pwms[0], &state); + + ret = iqs620_pwm_apply(&iqs620_pwm->chip, + &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 int iqs620_pwm_probe(struct platform_device *pdev) +{ + struct iqs620_pwm_private *iqs620_pwm; + int ret1, ret2; + + 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; + + ret1 = pwmchip_add(&iqs620_pwm->chip); + if (ret1) { + dev_err(&pdev->dev, "Failed to add device: %d\n", ret1); + return ret1; + } + + /* + * Since iqs620_pwm_notifier uses iqs620_pwm->chip.pwms[], the notifier + * is not registered until pwmchip_add (which allocates that array) has + * been called. If registration fails, the newly added device has to be + * removed because the driver fails to probe and iqs620_pwm_remove will + * never be called. + */ + iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier; + ret1 = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (ret1) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret1); + + ret2 = pwmchip_remove(&iqs620_pwm->chip); + if (ret2) { + dev_err(&pdev->dev, "Failed to remove device: %d\n", + ret2); + return ret2; + } + } + + return ret1; +} + +static int iqs620_pwm_remove(struct platform_device *pdev) +{ + struct iqs620_pwm_private *iqs620_pwm = platform_get_drvdata(pdev); + int ret; + + ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to unregister notifier: %d\n", ret); + return 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 Dec 9 00:38:38 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 1205806 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=labundy.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="Jj9tCTh4"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47WPSn4x00z9sR1 for ; Mon, 9 Dec 2019 11:38:49 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726828AbfLIAit (ORCPT ); Sun, 8 Dec 2019 19:38:49 -0500 Received: from mail-mw2nam12on2065.outbound.protection.outlook.com ([40.107.244.65]:11831 "EHLO NAM12-MW2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726621AbfLIAis (ORCPT ); Sun, 8 Dec 2019 19:38:48 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=MvUxVU6d/LuhDkixJ9m/M1XhRm+xmGcRzX9fYFwfmsKvoPN/GpTObtVjaYGNi21NGrFCobg0ByWxNi4prAERUNGQaKL2HIwbRdRcYHJ5UxlevfUIH1tzBz7lPlJtV3C/2tDD8zKsz986aCT1SPtgQWMOhEUBIbY1RKow8Y26JD29metN85trEuifiAjvZPqUIqCazxJ1JhwndU6o57lvHq0d+gnhLdhzo5HAbHx4cQZ1zsMhJ7SZx+kSDJIsWdwQ9QzH8r5aV1Tlk4AC70/DYW8OA5cwuzOJQ1pqGkuKB7FOLp22YOtgLZkMS2GD+W01KVmogY4x9qmgcZG+fonsEw== 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=At2lW2JuUXT4nfQ4KcnVgQLXprZK7C3FlsfjqXt9Hl0=; b=FKf88H0AUOEObEpWUD7bKiv8jBTKclz2wrq+ec/PSHOuQpjdR0wGZ/mDbWuFHSjYjV1ASdqVSFpbzPBGvFzOsuAwMhatP5wxZRiuNHhE8E8CJZ2H1NLASDRxKuNKz98x6JIKceiLXxFtU+AkUI8Y9oZi9C3WCUJ7IcakOzbE8QHWa0PmFaYJBvr7M2PkWUgycwDCfWhDVxpQFb7HBaThlTmdGoWuwiYtZN/87qPReHA0nPmC0rNQUboXrtCptR5nK8pQebQnt4xVIMTs4F1rCuQyTesV63PQy2Kxrv1ZELSWhoQk60pEs2UrHfkt342G24PcIz1fYwg9LC5JFOZVDw== 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=At2lW2JuUXT4nfQ4KcnVgQLXprZK7C3FlsfjqXt9Hl0=; b=Jj9tCTh44v/wWeTstGbNEIkzAcQUfgBrMkD1hpBn6tL4HeCDcKDqZkbQv6MdHqvrXJowwLI+XzsOA1DX5Ujcvc7JR/3PaGp0H/VgzzGgn8N9PpkGWxN7fGOfCa+a1eNj5EL5nR6GlJj3VkMsTVha8aurGHJOq4W5FX6B5H0ZlNQ= Received: from BN7PR08MB5042.namprd08.prod.outlook.com (20.176.177.14) by BN7PR08MB4402.namprd08.prod.outlook.com (52.132.223.26) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2516.12; Mon, 9 Dec 2019 00:38:38 +0000 Received: from BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1]) by BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1%6]) with mapi id 15.20.2516.018; Mon, 9 Dec 2019 00:38:38 +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 v2 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Thread-Topic: [PATCH v2 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Thread-Index: AQHVrij/5ETFITLUl0qPCjPr5iG24Q== Date: Mon, 9 Dec 2019 00:38:38 +0000 Message-ID: <1575851866-18919-6-git-send-email-jeff@labundy.com> References: <1575851866-18919-1-git-send-email-jeff@labundy.com> In-Reply-To: <1575851866-18919-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: SN1PR12CA0082.namprd12.prod.outlook.com (2603:10b6:802:21::17) To BN7PR08MB5042.namprd08.prod.outlook.com (2603:10b6:408:2c::14) 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: 70edba54-83ed-4a83-d017-08d77c4021b2 x-ms-traffictypediagnostic: BN7PR08MB4402: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:7219; x-forefront-prvs: 02462830BE x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(376002)(34096005)(366004)(136003)(396003)(346002)(39830400003)(199004)(189003)(99286004)(76176011)(71200400001)(52116002)(71190400001)(54906003)(4326008)(2906002)(6512007)(38610400001)(66556008)(66476007)(6486002)(66946007)(586005)(64756008)(66446008)(110136005)(107886003)(36756003)(8936002)(102836004)(50226002)(305945005)(2616005)(7416002)(5660300002)(6506007)(81166006)(81156014)(498600001)(26005)(86362001)(186003)(8676002); DIR:OUT; SFP:1101; SCL:1; SRVR:BN7PR08MB4402; H:BN7PR08MB5042.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: df3yyYwG/iDHcM8TO22XDFww0ziy3mBUayyu+CVzs9KF/o8vm0krsyUAoMUeGqldEJ3iAyFfboc1EmI+sIwD6Tl3mI5ygOEbXAJBWpxSYeYhYNo5P1ZJRLlAIPZY2htUKxLSu8ZeJHebTXl6Juirl+Y0Ig+3eYeOQ1fFaHvUbdlDKwJLYUTUejWzUxgrzCdAZSl3QbMJm7eyAu97y+MSdiOjx/x7Pg63s63Zwd8f4ScWCFG0SM9SRvpTLlnF5PXdmHU6QRu+OV4nh+EZOYCQznm4zt/v24Tb9x9qqAX2lOWh0ptXFtPT503yaG1hF0NzKKJNYzvRLg3puqx68Ddbz+RXSLlDM+0OmU5bopAcgvHb3QGAP3e9fmdX0X5sK+C/X/Ezc18bZPRIZVDcLV3T1L1wQnSUB//pB2jcOwFcV8cZ8fh01PSsjf3emUgdSqzy MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: 70edba54-83ed-4a83-d017-08d77c4021b2 X-MS-Exchange-CrossTenant-originalarrivaltime: 09 Dec 2019 00:38:38.3802 (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: UIkHXivHoCCUiEgyRwG5Xxsf3M1cbkCYN2xIHTs4TWGJvVfVT8RP2XRO5ipFZYKgmhH0IIhlWyGmuvGw5mTiMA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: BN7PR08MB4402 Sender: linux-pwm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pwm@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 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 Dec 9 00:38:39 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 1205808 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=labundy.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="S7en5/SS"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47WPSw2yKcz9sPK for ; Mon, 9 Dec 2019 11:38:56 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726874AbfLIAix (ORCPT ); Sun, 8 Dec 2019 19:38:53 -0500 Received: from mail-mw2nam12on2065.outbound.protection.outlook.com ([40.107.244.65]:11831 "EHLO NAM12-MW2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726654AbfLIAix (ORCPT ); Sun, 8 Dec 2019 19:38:53 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=CICb2Kb8O4bI91ON+LZ0IFiOKGMQPCZ/LMbKPu186uVFkzbPHIZwhTHT4IKFt6qx2GBHor0zvqJ1FwD/H9AmKLZTVNHBj02iV/BXy8VCKtW6MoB6IqaHjfKr1hwukJlrOSA2i0AHg1xeBmAyrtXwoba1VHAlNCh4TvoWpLFPvCZdATyRX9nEiWls3XYvyhm+ejdGTmg2jCpm+EnPKXuCD0n7tLydTvgKBwliIcOBfNBUDHhVwomAS1Ai1afZi/j7hfFiFzbS9Oa0+/wEfncGGAlv1dcTdKajOl/YIl46rdfYPmvexIQgFvpwZnMQuIgwWeGTWWT0Kbiol/9VUNxIFg== 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=jFwdE0w2+CLK7yrEgAJOKxhriMFvdTKx9E9/FXZFdIQ=; b=f2C20dJRsgovURr4TbDVfRtp12lg3zMSpBh2HVk5VYoSafpvPZfH6yWHR9l0HLuuGAOVblKkfVLYNYEIzPC0dAC7X/XFyTYcO/09lG9GY6OvtrDKMuEJlctN8h9zfQF08or2qgSTJvFhDNgS7ekMdz7q7Q2aXrt03EjirzvJWUJX/rcZR8c+CrWosVCZhlovOAU4yZE1XUDO67ue7UxhMT6pW9J62+x0n1NcrTJO/RPH0doPHLCL0/eBbqsL4akp/W9SFYhjNQmbPFIQQB8pihleoFfVR+UdUyWxrMpZkRhEmSZ7ifYYrj9JvAuTaJGd75UI03dFffI4nuPhgaFHtA== 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=jFwdE0w2+CLK7yrEgAJOKxhriMFvdTKx9E9/FXZFdIQ=; b=S7en5/SSGzb5jI5TxHfqSJlJW6iXOal3jpGGmiZTzEaCUfXTFB5R3F/V++GXxrVYqQ1eMXAPWG4VoGE2FAdiQiS0LUz1W/9NTjLVoyH3ZJ+kIZDi/XSAdCJ7ZGMt6A5pJZRRaEkad9DfDH5b9XxliSkMW+XGFfwM9ikiLmuo+BY= Received: from BN7PR08MB5042.namprd08.prod.outlook.com (20.176.177.14) by BN7PR08MB4402.namprd08.prod.outlook.com (52.132.223.26) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2516.12; Mon, 9 Dec 2019 00:38:40 +0000 Received: from BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1]) by BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1%6]) with mapi id 15.20.2516.018; Mon, 9 Dec 2019 00:38:40 +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 v2 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Thread-Topic: [PATCH v2 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Thread-Index: AQHVrikANmDZW8nJAEuZ6gO5S9D4pw== Date: Mon, 9 Dec 2019 00:38:39 +0000 Message-ID: <1575851866-18919-7-git-send-email-jeff@labundy.com> References: <1575851866-18919-1-git-send-email-jeff@labundy.com> In-Reply-To: <1575851866-18919-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: SN1PR12CA0082.namprd12.prod.outlook.com (2603:10b6:802:21::17) To BN7PR08MB5042.namprd08.prod.outlook.com (2603:10b6:408:2c::14) 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: cb6d3825-f4fc-4abe-d276-08d77c402286 x-ms-traffictypediagnostic: BN7PR08MB4402: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:10000; x-forefront-prvs: 02462830BE x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(376002)(34096005)(366004)(136003)(396003)(346002)(39830400003)(199004)(189003)(99286004)(76176011)(71200400001)(52116002)(71190400001)(54906003)(4326008)(2906002)(6512007)(38610400001)(66556008)(66476007)(6486002)(66946007)(586005)(64756008)(66446008)(110136005)(107886003)(36756003)(8936002)(102836004)(50226002)(30864003)(305945005)(2616005)(7416002)(5660300002)(6506007)(81166006)(81156014)(498600001)(26005)(86362001)(186003)(8676002); DIR:OUT; SFP:1101; SCL:1; SRVR:BN7PR08MB4402; H:BN7PR08MB5042.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: cYrbnkWj7cl8FGbiiWtEmB8JQssbDMmhVWF5Mz372dN4iJLQDM85MQCgoIxnogV8+2wvGdLHaD/TlQetORddBC7ik76oNKZV3Bz0Fpjru9fNivZhKWc/aIYVW0uQX46XnlbkQ+de06SqgMLkp75h2nKImuid0bjQYUqzOKiHYS6U4ktNGlVcOk3aUkUH6tJ+xDjCb+JagE7HdZgadrs9hnVCZCIv3l6IQXrTvKSrK4rBCk5VWQ8wqo/SybyM2ZzQC42Q/rPC21bHjT0nlJztlyL3nk1iF+VaPPrX0aVFgqrQlcMPMwNz7mk7lGOQL6872+9vdlEF77eE51k4ko/b4YnPOsHBE/V1hhnplFULj+jo30eUGDp0lptw7HVIH/8KxTvGCuD+Bz7fXappU4y7VfHTwkzw2GOE0n3NpR0qPigjFER3ff1w99ONthk3FflN MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: cb6d3825-f4fc-4abe-d276-08d77c402286 X-MS-Exchange-CrossTenant-originalarrivaltime: 09 Dec 2019 00:38:39.8196 (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: zVLYKkfPeHcaSvWWZQ3l7VQLTM1kHPN3ptOjGf2u+ke+9QSQoGkOBkCayGACm8rGJK5F/S0AZ+GLNcpAouq6Zw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: BN7PR08MB4402 Sender: linux-pwm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pwm@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 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 Dec 9 00:38:41 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 1205809 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=labundy.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=NETORG5796793.onmicrosoft.com header.i=@NETORG5796793.onmicrosoft.com header.b="u5T2dFsK"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47WPSw6pW3z9sR4 for ; Mon, 9 Dec 2019 11:38:56 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726863AbfLIAix (ORCPT ); Sun, 8 Dec 2019 19:38:53 -0500 Received: from mail-mw2nam12on2087.outbound.protection.outlook.com ([40.107.244.87]:6163 "EHLO NAM12-MW2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726621AbfLIAix (ORCPT ); Sun, 8 Dec 2019 19:38:53 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=nkedgfZwv8HYw+2gdPa1puUioQ7skThB4C0agz1/CjSfy7Y00XbxIBG0j6hg3HVZ349ygcuyFZc61pyPVKGv0j7xk3UBm1hPzfjdfq85tLuRB7r/6vSi0z7RGNw46qtLu0FiwyKBxNTjj4nR61sNmwjuEZ4soGoOOpg4op/aQREWN4SvPq18dg4AyLPKlxNAKztfqWTi6NbRsDIHeKiw0kIMZozz9dvI5zCHwQadl9rNvOTaMid7kweGBhN8DrSkLT3n95LviOOquUDLcsCmKfwrmH0LgqhgY7s4RISlzJSHQtpudHa5+Lvy1gPlwdCXnleJM6g73cOfmhKU/XQFOg== 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=pOkj6c6u/RdUL1PqguDcQo1awDcZvBLWeY2fVAsEY0I=; b=oTxsLppeqHa+KX1FYg7F6c1A2kVTH2oKlrJkeOfd8rcacEArYo7uXdvnf5Ezgwc4qAOObmWkwYCyLpuKeBjxQrqSnz/BBnT5HtklrQd2MpkFYDYXXpo9SD7oEXjbVJpi9v9QTlqkAzzeOCfouSBbUzR53mbfRtCyfydW2TUnVG88ZRm+Z4KhlG8bCctYCPW+IXx0sdyWpp2pjFxKKySukbxcCAUqDJ+LbdSOof4co6Ceoz/ma/MKi9k9wGf8s+YsqpHYwAdRFsH69GB0erazvTmAys+k2XV4llZSTTeCuPT4jZ865W2A1zvPUpnpzYBp3Xhy/9qDoAJDCocuhC3iCg== 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=pOkj6c6u/RdUL1PqguDcQo1awDcZvBLWeY2fVAsEY0I=; b=u5T2dFsKynFvWnig+POERt5h5nj88s/zGBKyH+NJeayYBmKqRIQ6cF+N0/8JpTWJQ6hB3ZS7B947x4Y/Qyr6XXDO1XXPxSbHnFB6/UfZuIGdyUMhPaTiozWKSpFhFxJcS8RHPky55tgXJUvgXNwhGXXPGZlpFejypWTqI2/kNwA= Received: from BN7PR08MB5042.namprd08.prod.outlook.com (20.176.177.14) by BN7PR08MB4402.namprd08.prod.outlook.com (52.132.223.26) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.2516.12; Mon, 9 Dec 2019 00:38:41 +0000 Received: from BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1]) by BN7PR08MB5042.namprd08.prod.outlook.com ([fe80::48de:9418:73ef:d7c1%6]) with mapi id 15.20.2516.018; Mon, 9 Dec 2019 00:38:41 +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 v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Thread-Topic: [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Thread-Index: AQHVrikBIkVSIrZAc0Gxy9PYoURzHQ== Date: Mon, 9 Dec 2019 00:38:41 +0000 Message-ID: <1575851866-18919-8-git-send-email-jeff@labundy.com> References: <1575851866-18919-1-git-send-email-jeff@labundy.com> In-Reply-To: <1575851866-18919-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: SN1PR12CA0082.namprd12.prod.outlook.com (2603:10b6:802:21::17) To BN7PR08MB5042.namprd08.prod.outlook.com (2603:10b6:408:2c::14) 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: f9e92cfe-17a9-4d45-d142-08d77c402363 x-ms-traffictypediagnostic: BN7PR08MB4402: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:9508; x-forefront-prvs: 02462830BE x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(39840400004)(376002)(34096005)(366004)(136003)(396003)(346002)(448600002)(199004)(189003)(99286004)(76176011)(71200400001)(52116002)(71190400001)(54906003)(4326008)(2906002)(6512007)(38610400001)(66556008)(66476007)(6486002)(66946007)(586005)(64756008)(66446008)(110136005)(107886003)(36756003)(8936002)(102836004)(50226002)(30864003)(305945005)(2616005)(7416002)(5660300002)(6506007)(81166006)(81156014)(26005)(86362001)(186003)(8676002)(102460200005); DIR:OUT; SFP:1101; SCL:1; SRVR:BN7PR08MB4402; H:BN7PR08MB5042.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: Td6Yx8py2UQRlLwJ0OV63/K3qEK/VjfoJCqX/QTewLxMW7j3jYeb56CzBUnGc6bKRkQ54+EL1B/zCKFkxh7d9LJOHxiZt2+cNZBv0uWgNMyQtXhb1LHlkZmzUt68ZkQDPRdsG4hm3UnVDvluzhOzxmXy1OZDqOpqc6yJRipRB5Een0qSD+lW/K+nc92mtcMcqeR7WG4D842Kfdq0BXVR/nFSPmqWx+iEpyfQw5iY52TNDjeHUj808N8KZQHvwkkqVxeGZr9yaYR3FHPDlHktPRyyDJXB/JvWk+eOGV1H8oJ/yAEJ0/rMQtnl9nZ3+lbESHKZ32CBxhyFlwsUAM218iXBOhpmh9D/Y6xuevk0aRvz0d2di6NX8KhtYDvnbEAYXcvogMEn8WGmCZrhoCVEC0d4CJVlmOdxHO+FjgzoYMQGguUtkIaBl+VIcY4fo9HqcLIouN+XA4ZEVL7AScVpHnFNqVkH+BzZVV2lXTJ/aSuY35V3YoDCZ882Qi1+fVIy MIME-Version: 1.0 X-OriginatorOrg: labundy.com X-MS-Exchange-CrossTenant-Network-Message-Id: f9e92cfe-17a9-4d45-d142-08d77c402363 X-MS-Exchange-CrossTenant-originalarrivaltime: 09 Dec 2019 00:38:41.2035 (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: AyzEVIqsPFRFdgQ61SwnZVU6NooeFK0koXvfbFm/GJDwyoYopxB4NsMHAn5oaQk5DaYW5dKK+03tuTsSsQL3Tw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: BN7PR08MB4402 Sender: linux-pwm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pwm@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 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);