From patchwork Mon Oct 21 04:11:16 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11201261 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 932FA1951 for ; Mon, 21 Oct 2019 04:19:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 7B6032053B for ; Mon, 21 Oct 2019 04:19:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726984AbfJUETM (ORCPT ); Mon, 21 Oct 2019 00:19:12 -0400 Received: from p3plsmtpa06-04.prod.phx3.secureserver.net ([173.201.192.105]:59412 "EHLO p3plsmtpa06-04.prod.phx3.secureserver.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725874AbfJUETM (ORCPT ); Mon, 21 Oct 2019 00:19:12 -0400 Received: from localhost.localdomain ([136.49.227.119]) by :SMTPAUTH: with ESMTPSA id MP2oifB4Hr3FgMP2tiDyXo; Sun, 20 Oct 2019 21:11:53 -0700 From: Jeff LaBundy To: lee.jones@linaro.org, dmitry.torokhov@gmail.com, jdelvare@suse.com, linux@roeck-us.net, thierry.reding@gmail.com, jic23@kernel.org, devicetree@vger.kernel.org Cc: linux-input@vger.kernel.org, linux-hwmon@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 1/8] dt-bindings: mfd: iqs62x: Add bindings Date: Sun, 20 Oct 2019 23:11:16 -0500 Message-Id: <1571631083-4962-2-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1571631083-4962-1-git-send-email-jeff@labundy.com> References: <1571631083-4962-1-git-send-email-jeff@labundy.com> X-CMAE-Envelope: MS4wfPM30Nn04H8YI2tC95MVBsoFU3P0bpbVxPKHXFyBpRrB5dpnFBcG7163MgAdu1sp/s1tFOLKaaTBU3Nk6eW3wRzHQm2cXAVMWtiZkYnlOWDpF3yqhfx4 vOl+tVcjMsWVGeyrGBCn/6vci6wWRdZM8qj+A1PD67BZJyxMez7J5QhZErNojP4RxkmvNt9sWmUNPxFWPRMJB3jx3krDGp6RSKwCRlAGobtNe1O/AdB2BeWa +pTxqkfz3rmpKX3hl4S4wQfz7oHsN27FjIXSFrDX7Xo2SMLJ5tiid+Ts1JbC2Ulpjdh5PM2rQuv0P4lDbLn91u8ddTJHpCDIciEy/DJO6w4v83olbrIGSgeI U/gnRRroAvHFvIIujscV1Dy8w5vHfETPbgXE1k7Bda2QfVQ0MBfhrfdhXwDvZOiCKh0x5HcYv93DAWp/fkfsu4INQhohiYfTADIVztj/Ck8a0WvggDmXd0HP R/8faiITTKIrbf68kr9itCdYnsoIX1IXaMKRh5Hn2HuDbR3ZNQe0Gw3ixFLqPrqMlIeKA9yq4hr0Nq1DtLEinM8nWn6eFhCFfVPX9Vp2w3ZvPaDDuDCvlXIi Wg0UF+EdAy7xKitKQywLmHm3k7HDIvS4bpOwUfF/JeED0HjrE5gNlLfe/uOqGzyckgA4I2t53Jg9E9nhaIpfTxx3OrrwZE3IZ1kE3S+dZZqEhIZGBlcL88H5 4KnbFRHE4kb5DSorjfdjqVk27gGTVdUOoVT5NPrun5wV9HGLKetCQA== Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds binding documentation for six-channel members of the Azoteq ProxFusion family of sensor devices. Signed-off-by: Jeff LaBundy --- Documentation/devicetree/bindings/mfd/iqs62x.txt | 242 +++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.txt diff --git a/Documentation/devicetree/bindings/mfd/iqs62x.txt b/Documentation/devicetree/bindings/mfd/iqs62x.txt new file mode 100644 index 0000000..089f567 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/iqs62x.txt @@ -0,0 +1,242 @@ +Azoteq IQS620A/621/622/624/625 ProxFusion Sensor Family + +Required properties: + +- compatible : Must be equal to one of the following: + "azoteq,iqs620a" + "azoteq,iqs621" + "azoteq,iqs622" + "azoteq,iqs624" + "azoteq,iqs625" + +- reg : I2C slave address for the device. + +- interrupts : GPIO to which the device's active-low RDY + output is connected (see [0]). + +Optional properties: + +- linux,fw-file : Specifies the name of the calibration and + configuration file selected by the driver. + If this property is omitted, the filename + is selected based on the device name with + ".bin" as the extension (e.g. iqs620a.bin + for IQS620A). + +All devices accommodate a child node (e.g. "keys") that represents touch key +support. Required properties for the "keys" child node include: + +- compatible : Must be equal to one of the following: + "azoteq,iqs620a-keys" + "azoteq,iqs621-keys" + "azoteq,iqs622-keys" + "azoteq,iqs624-keys" + "azoteq,iqs625-keys" + +- linux,keycodes : Specifies an array of up to 16 numeric key- + codes corresponding to each available touch + or proximity event. An 'x' in the following + table indicates an 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 Proximity* | x | | | | | + ---------------------------------------------------------------------------- + | 2 | CH1 Touch | x | x | x | x | x | + | | Antenna 1 Deep Touch* | x | | | | | + ---------------------------------------------------------------------------- + | 3 | CH1 Proximity | x | x | x | x | x | + ---------------------------------------------------------------------------- + | 4 | CH2 Touch | x | | | | | + ---------------------------------------------------------------------------- + | 5 | CH2 Proximity | x | | | | | + | | Antenna 2 Proximity* | x | | | | | + ---------------------------------------------------------------------------- + | 6 | Metal (+) Touch** | x | x | | | | + | | Antenna 2 Deep Touch* | x | | | | | + ---------------------------------------------------------------------------- + | 7 | Metal (+) Proximity** | x | x | | | | + | | Antenna 2 Touch* | x | | | | | + ---------------------------------------------------------------------------- + | 8 | Metal (-) Touch** | x | x | | | | + ---------------------------------------------------------------------------- + | 9 | Metal (-) Proximity** | x | x | | | | + ---------------------------------------------------------------------------- + | 10 | SAR Active*** | x | | x | | | + ---------------------------------------------------------------------------- + | 11 | SAR Quick Release*** | x | | x | | | + ---------------------------------------------------------------------------- + | 12 | SAR Movement*** | x | | x | | | + ---------------------------------------------------------------------------- + | 13 | SAR Filter Halt*** | x | | x | | | + ---------------------------------------------------------------------------- + | 14 | Wheel Up | | | | x | | + ---------------------------------------------------------------------------- + | 15 | Wheel Down | | | | x | | + ---------------------------------------------------------------------------- + * Dual-channel SAR. Replaces CH0-2 and metal touch and proximity events if + enabled via firmware. + ** "+" and "-" refer to the polarity of the channel's delta (LTA - counts), + where "LTA" is defined as the channel's long-term average. + *** Single-channel SAR. Replaces CH0-2 touch and proximity events if enabled + via firmware. + +The "keys" child node supports "hall_switch_north" and "hall_switch_south" +child nodes that represent north-field and south-field Hall-effect sensor +events, respectively (IQS620A/621/622 only). Required properties include: + +- linux,code : Numeric switch code. + +Optional properties for the "hall_switch_north" and "hall_switch_south" nodes: + +- azoteq,use-prox : Boolean to specify that Hall-effect sensor + reporting must use the device's wide-range + proximity threshold instead of its narrow- + range touch threshold. + +Note: North/south-field orientation is reversed on the IQS620AXzCSR device due + to its flip-chip package. + +The IQS620A supports a PWM controller node; required properties include: + +- compatible : Must be equal to "azoteq,iqs620a-pwm". + +- #pwm-cells : Must be equal to 2 (see [1]). + +The IQS622 supports an additional child node (e.g. "prox") that represents +active IR detection; required properties include: + +- compatible : Must be equal to "azoteq,iqs622-prox". + +Optional properties for the "prox" child node: + +- azoteq,use-prox : Boolean to specify that IR threshold event + reporting must use the device's wide-range + proximity threshold instead of its narrow- + range touch threshold. + +[0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt +[1]: Documentation/devicetree/bindings/pwm/pwm.txt + +Example 1: Dual capacitive buttons with additional "air button," unipolar lid + switch and panel-mounted LED. + + &i2c1 { + /* ... */ + + iqs620a: iqs620a@44 { + compatible = "azoteq,iqs620a"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_LEVEL_LOW>; + + iqs620a_keys: 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>; + }; + }; + +Example 2: Single inductive button with bipolar dock/tablet-mode switch. + + &i2c1 { + /* ... */ + + iqs620a: iqs620a@44 { + compatible = "azoteq,iqs620a"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_LEVEL_LOW>; + + linux,fw-file = "iqs620a_coil.bin"; + + iqs620a_keys: keys { + compatible = "azoteq,iqs620a-keys"; + + linux,keycodes = <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + ; + + hall_switch_north { + linux,code = ; + }; + + hall_switch_south { + linux,code = ; + }; + }; + }; + + /* ... */ + }; + +Example 3: Dual capacitive buttons with volume knob. + + &i2c1 { + /* ... */ + + iqs624: iqs624@44 { + compatible = "azoteq,iqs624"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_LEVEL_LOW>; + + iqs624_keys: keys { + compatible = "azoteq,iqs624-keys"; + + linux,keycodes = , + <0>, + , + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + , + ; + }; + }; + + /* ... */ + }; From patchwork Mon Oct 21 04:11:17 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11201265 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E4D8B2D2C for ; Mon, 21 Oct 2019 04:19:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id C010C2053B for ; Mon, 21 Oct 2019 04:19:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727021AbfJUETN (ORCPT ); Mon, 21 Oct 2019 00:19:13 -0400 Received: from p3plsmtpa06-04.prod.phx3.secureserver.net ([173.201.192.105]:48455 "EHLO p3plsmtpa06-04.prod.phx3.secureserver.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726949AbfJUETN (ORCPT ); Mon, 21 Oct 2019 00:19:13 -0400 Received: from localhost.localdomain ([136.49.227.119]) by :SMTPAUTH: with ESMTPSA id MP2oifB4Hr3FgMP2viDyYA; Sun, 20 Oct 2019 21:11:54 -0700 From: Jeff LaBundy To: lee.jones@linaro.org, dmitry.torokhov@gmail.com, jdelvare@suse.com, linux@roeck-us.net, thierry.reding@gmail.com, jic23@kernel.org, devicetree@vger.kernel.org Cc: linux-input@vger.kernel.org, linux-hwmon@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 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625 Date: Sun, 20 Oct 2019 23:11:17 -0500 Message-Id: <1571631083-4962-3-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1571631083-4962-1-git-send-email-jeff@labundy.com> References: <1571631083-4962-1-git-send-email-jeff@labundy.com> X-CMAE-Envelope: MS4wfGWsfb62NDCWwpS6nClPLMOf1dTmNmUh67rXbPj6ws1wHo4XKp8DPtiJpuWx3ouWu7dN8lkNLC9d23j/uR+vxhImwWqNvMkglsJtsvy3vq9g/sN5pPuA ni0F44YX4BNfvGhDfaFsPV2cJKOphL1WqKwNjSx016oRqIVlKKlk9FxguwfSopf2ySu92ejkTg01yLq9x6Iue/Tcex8zbH5Via/m8SrJoiQH5CdLEHy1xh+D welr9PWimacOcjDl27OOszyrKJXWHyMbMHWR0z0xg9gcvugDMjUdJwSRlVh+fQEo2HIo/qSXsT4zxDmZC7qJKNqFAuz/qPvoCN/sO7/vfHaVd0amzwn4hUTE 1z7flWHjTmmew0adGAcrcEJk4ii6woZC3pGao/lVdokIysF1QVEYNusCu9jOxKmMPBA6rDsF0pQuHnurERtKIYfvaYaE+lEftFX8L5/+u57GrX5He0390L8x d42t4/psHBN6as0TYOSlSDvyc65SodiDyrkf8L1BIOZrYTMfLe2rXWX0fKgJMYB7Rt529bb2bWr0z+V24v9TFr/08U04fIJcHkYebohXXRlb8kzurefvJjZj 52B0DCjhz4EbmuWFOgDtw4WfbJ1HMoffc3v/aLAFOddCPRC0mTApi0cvNKhOBJfs7aIiXRJXgculsHKJjNa8a2njNW9rKzqe6nV7EYY31QNTPJ9s/y35xGv7 HLYlnQTH6C5ji6DFdnv1PE3T1L54FKo031kuad+1mh7layFOd64lZw== Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for core functions common to all six-channel members of the Azoteq ProxFusion family of sensor devices. Signed-off-by: Jeff LaBundy --- drivers/mfd/Kconfig | 13 + drivers/mfd/Makefile | 2 + drivers/mfd/iqs62x-core.c | 638 ++++++++++++++++++++++++++++++++++++++++++++ drivers/mfd/iqs62x-tables.c | 424 +++++++++++++++++++++++++++++ include/linux/mfd/iqs62x.h | 148 ++++++++++ 5 files changed, 1225 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 diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index ae24d3e..df391f7 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 support for six-channel members of + the Azoteq ProxFusion family of sensor devices. 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 c1067ea..23dd71c6 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -256,3 +256,5 @@ obj-$(CONFIG_MFD_ROHM_BD70528) += rohm-bd70528.o obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o obj-$(CONFIG_MFD_STMFX) += stmfx.o +iqs62x-objs := iqs62x-core.o iqs62x-tables.o +obj-$(CONFIG_MFD_IQS62X) += iqs62x.o diff --git a/drivers/mfd/iqs62x-core.c b/drivers/mfd/iqs62x-core.c new file mode 100644 index 0000000..e2200c8 --- /dev/null +++ b/drivers/mfd/iqs62x-core.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A/621/622/624/625 ProxFusion Sensor Family + * + * Copyright (C) 2019 + * Author: 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/products/proxfusion/ + * + * 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_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 0x0F + +#define IQS62X_ATI_TIMEOUT 10 + +#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 error, i; + + list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) { + if (fw_blk->mask) + error = regmap_update_bits(iqs62x->map, fw_blk->addr, + fw_blk->mask, *fw_blk->data); + else + error = regmap_raw_write(iqs62x->map, fw_blk->addr, + fw_blk->data, fw_blk->len); + if (error) + return error; + } + + switch (iqs62x->dev_desc->prod_num) { + case IQS620_PROD_NUM: + case IQS622_PROD_NUM: + error = regmap_read(iqs62x->map, + iqs62x->dev_desc->prod_num == + IQS620_PROD_NUM ? IQS620_PROX_SETTINGS_4 : + IQS622_PROX_SETTINGS_4, + &val); + if (error) + return error; + + if (val & IQS620_PROX_SETTINGS_4_SAR_EN) + iqs62x->ui_sel = IQS62X_UI_SAR1; + /* fall through */ + + case IQS621_PROD_NUM: + error = 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 (error) + return error; + break; + + default: + error = regmap_write(iqs62x->map, IQS624_HALL_UI, + IQS624_HALL_UI_WHL_EVENT | + IQS624_HALL_UI_INT_EVENT | + IQS624_HALL_UI_AUTO_CAL); + if (error) + return error; + + error = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV, &val); + if (error) + return error; + + if (val >= iqs62x->dev_desc->interval_div) + break; + + error = regmap_write(iqs62x->map, IQS624_INTERVAL_DIV, + iqs62x->dev_desc->interval_div); + if (error) + return error; + } + + error = regmap_update_bits(iqs62x->map, IQS62X_SYS_SETTINGS, + IQS62X_SYS_SETTINGS_ACK_RESET | + IQS62X_SYS_SETTINGS_EVENT_MODE | + IQS62X_SYS_SETTINGS_REDO_ATI, 0xFF); + if (error) + return error; + + for (i = 0; i < IQS62X_ATI_TIMEOUT; i++) { + msleep(50); + + error = regmap_read(iqs62x->map, IQS62X_SYS_FLAGS, &val); + if (error) + return error; + + if (!(val & IQS62X_SYS_FLAGS_IN_ATI)) + break; + } + + if (i == IQS62X_ATI_TIMEOUT) + return -ETIME; + + /* + * The following delay accommodates the post-ATI stabilization time + * specified in the data sheet (with additional margin). + */ + msleep(150); + + return 0; +} + +static int iqs62x_fw_parse(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 hall_cal_index = 0; + size_t pos = 0; + int error = 0; + u8 mask, len; + u8 *data; + + while (pos < fw->size) { + if (pos + sizeof(*fw_rec) > fw->size) { + error = -EINVAL; + break; + } + fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos); + pos += sizeof(*fw_rec); + + if (pos + fw_rec->len - 1 > fw->size) { + error = -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); + error = -EINVAL; + break; + + case IQS62X_FW_REC_TYPE_HALL: + if (!hall_cal_index) { + error = regmap_write(iqs62x->map, + IQS62X_OTP_CMD, + IQS62X_OTP_CMD_FG3); + if (error) + break; + + error = regmap_read(iqs62x->map, + IQS62X_OTP_DATA, + &hall_cal_index); + if (error) + break; + + hall_cal_index &= IQS62X_HALL_CAL_MASK; + if (!hall_cal_index) { + dev_err(&client->dev, + "Uncalibrated device\n"); + error = -ENODATA; + break; + } + } + + if (hall_cal_index > fw_rec->len) { + error = -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))) { + error = -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); + error = -EINVAL; + } + + if (error) + break; + + fw_blk = devm_kzalloc(&client->dev, + struct_size(fw_blk, data, len), + GFP_KERNEL); + if (!fw_blk) { + error = -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 error; +} + +static irqreturn_t iqs62x_irq(int irq, void *context) +{ + struct iqs62x_core *iqs62x = context; + struct iqs62x_event_data event_data; + struct iqs62x_event_desc event_desc; + enum iqs62x_event_reg event_reg; + unsigned long event_flags = 0; + int error, 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. + */ + error = regmap_raw_read(iqs62x->map, IQS62X_SYS_FLAGS, + event_map, sizeof(event_map)); + if (error) + 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: + case IQS62X_EVENT_GLBL: + continue; + + case IQS62X_EVENT_TEMP: + event_data.temp_flags = event_map[i]; + 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 beyond a 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(&iqs62x->client->dev, "Unexpected device reset\n"); + + error = iqs62x_dev_init(iqs62x); + if (error) { + dev_err(&iqs62x->client->dev, + "Failed to re-initialize device: %d\n", error); + return IRQ_NONE; + } + } + + error = blocking_notifier_call_chain(&iqs62x->nh, event_flags, + &event_data); + if (error & 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_load(const struct firmware *fw, void *context) +{ + struct iqs62x_core *iqs62x = context; + struct i2c_client *client = iqs62x->client; + int error; + + if (fw) { + error = iqs62x_fw_parse(iqs62x, fw); + if (error) { + dev_err(&client->dev, "Failed to parse firmware: %d\n", + error); + goto err_out; + } + } + + error = iqs62x_dev_init(iqs62x); + if (error) { + dev_err(&client->dev, "Failed to initialize device: %d\n", + error); + goto err_out; + } + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, iqs62x_irq, IRQF_ONESHOT, + client->name, iqs62x); + if (error) { + dev_err(&client->dev, "Failed to request IRQ: %d\n", error); + goto err_out; + } + + error = devm_mfd_add_devices(&client->dev, -1, + iqs62x->dev_desc->sub_devs, + iqs62x->dev_desc->num_sub_devs, + NULL, 0, NULL); + if (error) + dev_err(&client->dev, "Failed to add devices: %d\n", error); + +err_out: + complete_all(&iqs62x->fw_done); +} + +static int __maybe_unused iqs62x_suspend(struct device *dev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(dev); + int error; + + 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. + */ + error = regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_DIS_AUTO, + IQS62X_PWR_SETTINGS_DIS_AUTO); + if (error) + return error; + + 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 error; + + error = regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_PWR_MODE_MASK, + IQS62X_PWR_SETTINGS_PWR_MODE_NORM); + if (error) + return error; + + 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 regmap_config iqs62x_map_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = IQS62X_MAX_REG, +}; + +static int iqs62x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iqs62x_core *iqs62x; + struct iqs62x_info info; + unsigned int val; + int error, i, j; + const char *fw_file = 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)) { + error = PTR_ERR(iqs62x->map); + dev_err(&client->dev, "Failed to initialize register map: %d\n", + error); + return error; + } + + error = regmap_raw_read(iqs62x->map, IQS62X_PROD_NUM, &info, + sizeof(info)); + if (error) + return error; + + for (i = 0; i < IQS62X_NUM_DEV; i++) { + if (info.prod_num == iqs62x_devs[i].prod_num) + iqs62x->dev_desc = &iqs62x_devs[i]; + else + continue; + + if (info.sw_num >= iqs62x->dev_desc->sw_num) + iqs62x->sw_num = info.sw_num; + else + continue; + + for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) { + error = regmap_read(iqs62x->map, + iqs62x->dev_desc->cal_regs[j], + &val); + if (error) + return error; + + if (!val) + break; + } + + 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 (!iqs62x->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; + } + + error = regmap_write(iqs62x->map, IQS62X_SYS_SETTINGS, + IQS62X_SYS_SETTINGS_SOFT_RESET); + if (error) + return error; + usleep_range(10000, 10100); + + device_property_read_string(&client->dev, "linux,fw-file", &fw_file); + + error = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fw_file ? : iqs62x->dev_desc->fw_file, + &client->dev, GFP_KERNEL, iqs62x, + iqs62x_fw_load); + if (error) + dev_err(&client->dev, "Failed to request firmware: %d\n", + error); + + return error; +} + +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 const struct i2c_device_id iqs62x_id[] = { + { "iqs620a", 0 }, + { "iqs621", 1 }, + { "iqs622", 2 }, + { "iqs624", 3 }, + { "iqs625", 4 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, iqs62x_id); + +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, + }, + .id_table = iqs62x_id, + .probe = iqs62x_probe, + .remove = iqs62x_remove, +}; +module_i2c_driver(iqs62x_i2c_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 ProxFusion Sensor Family"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/iqs62x-tables.c b/drivers/mfd/iqs62x-tables.c new file mode 100644 index 0000000..12300b7 --- /dev/null +++ b/drivers/mfd/iqs62x-tables.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A/621/622/624/625 ProxFusion Sensor Family + * + * Copyright (C) 2019 + * Author: Jeff LaBundy + */ + +#include +#include +#include + +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 = IQS622_DRV_NAME_PROX, + .of_compatible = "azoteq,iqs622-prox", + }, +}; + +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[] = { 0xC2, 0xC3, 0xC4, }; +static const u8 iqs621_cal_regs[] = { 0x82, 0x83, }; + +static const enum iqs62x_event_reg iqs620a_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_GLBL, /* 0x11 */ + 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_TEMP, /* 0x19 */ + IQS62X_EVENT_UI_LO, /* 0x1A */ + IQS62X_EVENT_UI_HI, /* 0x1B */ + }, + [IQS62X_UI_SAR1] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_GLBL, /* 0x11 */ + 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_TEMP, /* 0x19 */ + IQS62X_EVENT_UI_LO, /* 0x1A */ + IQS62X_EVENT_UI_HI, /* 0x1B */ + }, +}; + +static const enum iqs62x_event_reg iqs621_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_GLBL, /* 0x11 */ + 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 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, +}; + +static const enum iqs62x_event_reg iqs622_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_GLBL, /* 0x11 */ + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_IR, /* 0x16 */ + IQS62X_EVENT_UI_LO, /* 0x17 */ + IQS62X_EVENT_UI_HI, /* 0x18 */ + IQS62X_EVENT_HALL, /* 0x19 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, + [IQS62X_UI_SAR1] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_GLBL, /* 0x11 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_IR, /* 0x16 */ + IQS62X_EVENT_UI_LO, /* 0x17 */ + IQS62X_EVENT_UI_HI, /* 0x18 */ + IQS62X_EVENT_HALL, /* 0x19 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, +}; + +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, + IQS62X_EVENT_NONE, + 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, + 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 = 0x16, + + .fw_file = "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 = 0x16, + + .fw_file = "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), + + .hall_flags = 0x19, + .hyst_shift = 5, + + .fw_file = "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), + + .hall_flags = 0x19, + + .fw_file = "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 = 0x18, + .interval_div = 3, + + .fw_file = "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 = 0x12, + .interval_div = 10, + + .fw_file = "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..01d0f5b --- /dev/null +++ b/include/linux/mfd/iqs62x.h @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Azoteq IQS620A/621/622/624/625 ProxFusion Sensor Family + * + * Copyright (C) 2019 + * Author: 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 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 12 + +#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 IQS622_DRV_NAME_PROX "iqs622-prox" +#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_GLBL, + IQS62X_EVENT_PROX, + IQS62X_EVENT_HYST, + IQS62X_EVENT_HALL, + IQS62X_EVENT_TEMP, + 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; + union { + u8 temp_flags; + 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 hall_flags; + u8 hyst_shift; + u8 interval; + u8 interval_div; + + const char *fw_file; + 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; + u8 sw_num; +}; + +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 Oct 21 04:11:18 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11201267 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 10CFE13BD for ; Mon, 21 Oct 2019 04:19:19 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id EC69A205ED for ; Mon, 21 Oct 2019 04:19:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726964AbfJUETO (ORCPT ); Mon, 21 Oct 2019 00:19:14 -0400 Received: from p3plsmtpa06-04.prod.phx3.secureserver.net ([173.201.192.105]:37476 "EHLO p3plsmtpa06-04.prod.phx3.secureserver.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726991AbfJUETN (ORCPT ); Mon, 21 Oct 2019 00:19:13 -0400 Received: from localhost.localdomain ([136.49.227.119]) by :SMTPAUTH: with ESMTPSA id MP2oifB4Hr3FgMP2wiDyYZ; Sun, 20 Oct 2019 21:11:55 -0700 From: Jeff LaBundy To: lee.jones@linaro.org, dmitry.torokhov@gmail.com, jdelvare@suse.com, linux@roeck-us.net, thierry.reding@gmail.com, jic23@kernel.org, devicetree@vger.kernel.org Cc: linux-input@vger.kernel.org, linux-hwmon@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 3/8] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625 Date: Sun, 20 Oct 2019 23:11:18 -0500 Message-Id: <1571631083-4962-4-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1571631083-4962-1-git-send-email-jeff@labundy.com> References: <1571631083-4962-1-git-send-email-jeff@labundy.com> X-CMAE-Envelope: MS4wfHgxhpxOVl0sALoZoA5HsFO3L9Mj3gSzZVCLSbFPlJqMRwR8ooEuKlOzqivzxzqXzaiokgLTt9eKu+jn/l2HytxV5V1di+q19UWLTKvkHB7IqpT/P+ic plZ/Jz32pOBr955CqXKo2WrAQQ+wDeB3zAlW73sJyxpC2iHwMXLM+MDjh1eobGL2H8r9XxppN7/84zmDYiZvka5GgrkRId069DskElr6YuK9XHGiA4IhgZho QRDQ83tzp/zep0K5olNEIRZ68d5rigb0EOnqEjVzu7f/6DHDZfHgtf2PDCkoLyWPR0cMCiganLvXlBqHuJoQdHrUVlklz2Ag0+JPRTuVKMxV+AIcqt2f1Urb du44eEo/L9w8RbGrcM2i8zygqlcEYe7SMcLMRUqSXsejoAuI97XR9erXQaRASzFxED2qpVfbc+5bnhhOmu5/N9oMmoOGLufh+zbl4FYTjXxbKatzMJSIgjOA XO/QYvKyxueUe6Nw91t6eIVQpd2ZET4Y1zTN1PkUCwfrAg89pY+NPPPMoysv46LTwZJ+dImLMePGj3Rk8sreoqporqi/b69RUS4iXNA8GFEWpVfznRWSpSPR bzAKjO1euOk17MHIxCfkQeKcmQkXVIKNUwM96vUR2NhE5LX3vNX9O7/UlHGb+5rcm2KgUYK06xGQS9rF3NLCK3IHMeYovJP2XDe+Qj8Xfe/g4AlINAghHn8K URDrlhPx0ZztWlVtK4JNsaqCdplhoo10eE1YIJncUNe3Ma8kwT4Fjg== Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds touch key support for six-channel members of the Azoteq ProxFusion family of sensor devices. Signed-off-by: Jeff LaBundy --- 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 diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 8911bc2..ab10aff 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -657,6 +657,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 touch keys" + depends on MFD_IQS62X + help + Say Y here to enable touch-key support for six-channel members of + the Azoteq ProxFusion family of sensor devices. + + 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 9510325..ee85b7f 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_HP6XX) += jornada680_kbd.o obj-$(CONFIG_KEYBOARD_HP7XX) += jornada720_kbd.o diff --git a/drivers/input/keyboard/iqs62x-keys.c b/drivers/input/keyboard/iqs62x-keys.c new file mode 100644 index 0000000..9d929f1 --- /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 Touch Keys + * + * Copyright (C) 2019 + * Author: 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 device_node *keys_node = pdev->dev.of_node; + struct device_node *hall_node; + unsigned int val; + int ret, i; + + if (!keys_node) + return 0; + + ret = of_property_read_variable_u32_array(keys_node, "linux,keycodes", + iqs62x_keys->keycode, 0, + IQS62X_NUM_KEYS); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read keycodes: %d\n", ret); + return ret; + } + iqs62x_keys->keycodemax = ret; + + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) { + hall_node = of_get_child_by_name(keys_node, + iqs62x_switch_names[i]); + if (!hall_node) + continue; + + ret = of_property_read_u32(hall_node, "linux,code", &val); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read switch code: %d\n", + ret); + of_node_put(hall_node); + return ret; + } + + if (of_property_read_bool(hall_node, "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); + + iqs62x_keys->switches[i].code = val; + iqs62x_keys->switches[i].enabled = true; + + of_node_put(hall_node); + } + + 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 error, 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); + } + + error = regmap_read(iqs62x->map, iqs62x->dev_desc->hall_flags, + &val); + if (error) + return error; + + /* + * 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; + + error = regmap_read(iqs62x->map, iqs62x->dev_desc->interval, + &val); + if (error) + return error; + + 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 error, i; + + iqs62x_keys = container_of(notifier, struct iqs62x_keys_private, + notifier); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + error = iqs62x_keys_init(iqs62x_keys); + if (error) { + dev_err(iqs62x_keys->input->dev.parent, + "Failed to re-initialize device: %d\n", error); + 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 full keystroke 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 error, i; + + iqs62x_keys = devm_kzalloc(&pdev->dev, sizeof(*iqs62x_keys), + GFP_KERNEL); + if (!iqs62x_keys) + return -ENOMEM; + + platform_set_drvdata(pdev, iqs62x_keys); + + error = iqs62x_keys_parse_prop(pdev, iqs62x_keys); + if (error) + return error; + + 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; + + error = iqs62x_keys_init(iqs62x_keys); + if (error) { + dev_err(&pdev->dev, "Failed to initialize device: %d\n", error); + return error; + } + + error = input_register_device(iqs62x_keys->input); + if (error) { + dev_err(&pdev->dev, "Failed to register device: %d\n", error); + return error; + } + + iqs62x_keys->notifier.notifier_call = iqs62x_keys_notifier; + error = blocking_notifier_chain_register(&iqs62x_keys->iqs62x->nh, + &iqs62x_keys->notifier); + if (error) + dev_err(&pdev->dev, "Failed to register notifier: %d\n", error); + + return error; +} + +static int iqs62x_keys_remove(struct platform_device *pdev) +{ + struct iqs62x_keys_private *iqs62x_keys = platform_get_drvdata(pdev); + int error; + + error = blocking_notifier_chain_unregister(&iqs62x_keys->iqs62x->nh, + &iqs62x_keys->notifier); + if (error) + dev_err(&pdev->dev, + "Failed to unregister notifier: %d\n", error); + + return error; +} + +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 Touch Keys"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS62X_DRV_NAME_KEYS); From patchwork Mon Oct 21 04:11:19 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11201279 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id EE02E1920 for ; Mon, 21 Oct 2019 04:19:19 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id D67742053B for ; Mon, 21 Oct 2019 04:19:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726994AbfJUETS (ORCPT ); Mon, 21 Oct 2019 00:19:18 -0400 Received: from p3plsmtpa06-04.prod.phx3.secureserver.net ([173.201.192.105]:33777 "EHLO p3plsmtpa06-04.prod.phx3.secureserver.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727034AbfJUETR (ORCPT ); Mon, 21 Oct 2019 00:19:17 -0400 Received: from localhost.localdomain ([136.49.227.119]) by :SMTPAUTH: with ESMTPSA id MP2oifB4Hr3FgMP2xiDyZ4; Sun, 20 Oct 2019 21:11:59 -0700 From: Jeff LaBundy To: lee.jones@linaro.org, dmitry.torokhov@gmail.com, jdelvare@suse.com, linux@roeck-us.net, thierry.reding@gmail.com, jic23@kernel.org, devicetree@vger.kernel.org Cc: linux-input@vger.kernel.org, linux-hwmon@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 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor Date: Sun, 20 Oct 2019 23:11:19 -0500 Message-Id: <1571631083-4962-5-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1571631083-4962-1-git-send-email-jeff@labundy.com> References: <1571631083-4962-1-git-send-email-jeff@labundy.com> X-CMAE-Envelope: MS4wfCTkk/bvHPNeSN2+Q+jpjtr2J/0Ob93zwQWi+DTPZgwKCpLlK69iByoBdRDSs5xHJnke3tH9pqvEGLI687VXIo7aSQUfMvNm/Xod0m0GRk2sndpDSbuJ cmnM8rLkjgczHq3LIsYZ92SBmpXxiOfPML+jfsHBqp+JYgjS01hJGpi8dus/a0T7UOupRPLaExTouw3YORAzu55EMbsfARZ61xDrai9LgbDSHPpvPMBNPDK1 wMtjXSHOMVz+Xhq+HNMHtvjJ1O2v2oeLm9u6DxMCf5+sc8nWzLF5X/nsi9/GplypmlLWOcQPwPg4g9QuNNYLxHXPU8b2PzilQXpNOtzozS5uGh3gAychES72 HYVMfe4TOnkhhWYFY0ciupauqA5ueoeJEBJ4C1ZlvvvCMadtCtJHVv0i9551dJc81m8CVudIk4M1DoBd/6eS6mTwkMTADV+FxxPwDQn9+NZo4kW1ZsrzF25Z wEocQGo+9cYQ5bJXWLXUlT5emrs1+Ox17/XC9az77LTUwlJdHNzDEFFkon065RTzQ7qJAIZTEnipXOntQ0Y047F2IjMo10RFJiqSkqHqDMv9IwKXLjfWtjVS L4hrzXQ0KqCF3CIj3f/j2Ur4vFdBDbShAqxB3IKSBnwyxL6ci5ATSCqisXCStaB7eLxX/ccaakrvuoJG0lL0GWROKBtrmseLMK1wXn6LgHupMf7JdB/EVJT8 AaN7pvwfD0TH3Sgp2pwFavCoXh3X74t2NiJlfmPA5NSx8P8pKLYRfQ== Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS620AT temperature sensor, capable of reporting its absolute die temperature. Signed-off-by: Jeff LaBundy --- drivers/hwmon/Kconfig | 12 +++++- drivers/hwmon/Makefile | 1 + drivers/hwmon/iqs620at-temp.c | 96 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 drivers/hwmon/iqs620at-temp.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 13a6b4a..3e56be6 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -385,6 +385,16 @@ config SENSORS_ATXP1 This driver can also be built as a module. If so, the module will be called atxp1. +config SENSORS_IQS620AT + tristate "Azoteq IQS620AT temperature sensor" + depends on MFD_IQS62X + 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 SENSORS_DS620 tristate "Dallas Semiconductor DS620" depends on I2C @@ -1593,7 +1603,7 @@ config SENSORS_ADS7871 config SENSORS_AMC6821 tristate "Texas Instruments AMC6821" - depends on I2C + depends on I2C help If you say yes here you get support for the Texas Instruments AMC6821 hardware monitoring chips. diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 40c036e..2360add 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -83,6 +83,7 @@ obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o obj-$(CONFIG_SENSORS_INA209) += ina209.o obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o obj-$(CONFIG_SENSORS_INA3221) += ina3221.o +obj-$(CONFIG_SENSORS_IQS620AT) += iqs620at-temp.o obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o diff --git a/drivers/hwmon/iqs620at-temp.c b/drivers/hwmon/iqs620at-temp.c new file mode 100644 index 0000000..0af49b6 --- /dev/null +++ b/drivers/hwmon/iqs620at-temp.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620AT Temperature Sensor + * + * Copyright (C) 2019 + * Author: Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS620_TEMP_UI_OUT 0x1A + +static umode_t iqs620_temp_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type != hwmon_temp || attr != hwmon_temp_input) + return 0; + + return 0444; +} + +static int iqs620_temp_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(dev); + int error; + __le16 val_buf; + + if (type != hwmon_temp || attr != hwmon_temp_input) + return -EINVAL; + + error = regmap_raw_read(iqs62x->map, IQS620_TEMP_UI_OUT, &val_buf, + sizeof(val_buf)); + if (error) + return error; + + *val = (le16_to_cpu(val_buf) - 100) * 1000; + + return 0; +} + +static const struct hwmon_ops iqs620_temp_ops = { + .is_visible = iqs620_temp_is_visible, + .read = iqs620_temp_read, +}; + +static const struct hwmon_channel_info *iqs620_temp_channel_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_chip_info iqs620_temp_chip_info = { + .ops = &iqs620_temp_ops, + .info = iqs620_temp_channel_info, +}; + +static int iqs620_temp_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct device *hdev; + int error = 0; + + hdev = devm_hwmon_device_register_with_info(&pdev->dev, + iqs62x->dev_desc->dev_name, + iqs62x, + &iqs620_temp_chip_info, + NULL); + if (IS_ERR(hdev)) { + error = PTR_ERR(hdev); + dev_err(&pdev->dev, "Failed to register device: %d\n", error); + } + + return error; +} + +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 Oct 21 04:11:20 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11201285 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id F18B313BD for ; Mon, 21 Oct 2019 04:19:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id D9D4B205ED for ; Mon, 21 Oct 2019 04:19:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727005AbfJUETU (ORCPT ); Mon, 21 Oct 2019 00:19:20 -0400 Received: from p3plsmtpa06-04.prod.phx3.secureserver.net ([173.201.192.105]:33493 "EHLO p3plsmtpa06-04.prod.phx3.secureserver.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727043AbfJUETU (ORCPT ); Mon, 21 Oct 2019 00:19:20 -0400 Received: from localhost.localdomain ([136.49.227.119]) by :SMTPAUTH: with ESMTPSA id MP2oifB4Hr3FgMP31iDyZZ; Sun, 20 Oct 2019 21:12:01 -0700 From: Jeff LaBundy To: lee.jones@linaro.org, dmitry.torokhov@gmail.com, jdelvare@suse.com, linux@roeck-us.net, thierry.reding@gmail.com, jic23@kernel.org, devicetree@vger.kernel.org Cc: linux-input@vger.kernel.org, linux-hwmon@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 5/8] pwm: Add support for Azoteq IQS620A PWM generator Date: Sun, 20 Oct 2019 23:11:20 -0500 Message-Id: <1571631083-4962-6-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1571631083-4962-1-git-send-email-jeff@labundy.com> References: <1571631083-4962-1-git-send-email-jeff@labundy.com> X-CMAE-Envelope: MS4wfGPUAnpyF199msi0obUD/Jk/09GyevQCOvn6Cx2BDnanBTgXPTJTeVLyFoYEnqpzsyFDMDhjwUA3PzDLvNg1ydU8y8zgJN8RNgMLGZm1OO/eVoePP9wE XT18nXQyAULKNyqeyvft2ORSsflpjdUNTF1PGV8gooM6bZbLMHs10pxqnR47vHri0evjQtVFDfNjnFsW5iP4kGkfczCui6OeGTWCO9MmBzaonxGjv+1Psrjg b3reozalY8VrtXHc8iArd4uAxcZY+I4V7jSaN9eKOi/1NNClk4VJbpjeXXp9bqscdGQSDmRfZsSocySPLNUu/v4R2zB8hZkXodvz/AjucHhxxsYp0y+v0Asd 9g64AX8BggwnMJKxHeawqStlOsgK75cgnUOwRsW/SzJxwffdnexAJIGtb7nMkAq6dONB5W5RojAYuuF2fkQMtOzJsLwEa5ZGQ7uHB+eGT3gTcrwupQ2tJODm jdK8IoJ+8+fdJXR9WQuaJDeF74qv4Yv1mnjN4Uc9lsj4zoJ7YeI8Ua3n6kM6cEYEUkblR57vomNQxQ+VWIDwDSYgw8DjdLrQ61bhL5MxbF+wMoJl40FVyOy9 /eiHUna7RDss7Ve8a0ghBh7bHo6yYZz4bSEI0T9D40AKEZLbpKwR6p+do4UJnEEUNYNNap13TkfdgtcZdG0zUcpY0gSOMuxcy5U/gyMFaBJRPGwqTDRDE0KY uxQDuRBzRWii0z7zGvjld8aRvdiF/YDm4qpj+6cJvx+olyhyV8FANg== Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS620A, capable of generating a 1-kHz PWM output with duty cycle between 0.4% and 100% (inclusive). Signed-off-by: Jeff LaBundy --- drivers/pwm/Kconfig | 10 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-iqs620a.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 drivers/pwm/pwm-iqs620a.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index e3a2518..712445e 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 + 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 26326ad..27c9bfa 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..6451eb1 --- /dev/null +++ b/drivers/pwm/pwm-iqs620a.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A PWM Generator + * + * Copyright (C) 2019 + * Author: Jeff LaBundy + */ + +#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; + bool ready; +}; + +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct iqs620_pwm_private *iqs620_pwm; + struct iqs62x_core *iqs62x; + int error; + int duty_calc = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS - 1; + u8 duty_clamp = clamp(duty_calc, 0, 0xFF); + + iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip); + iqs62x = iqs620_pwm->iqs62x; + + error = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE, duty_clamp); + if (error) + return error; + + state->period = IQS620_PWM_PERIOD_NS; + state->duty_cycle = (duty_clamp + 1) * IQS620_PWM_PERIOD_NS / 256; + + return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, + state->enabled ? 0xFF : 0); +} + +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 error; + + iqs620_pwm = container_of(notifier, struct iqs620_pwm_private, + notifier); + + if (!iqs620_pwm->ready || !(event_flags & BIT(IQS62X_EVENT_SYS_RESET))) + return NOTIFY_DONE; + + pwm_get_state(&iqs620_pwm->chip.pwms[0], &state); + + error = iqs620_pwm_apply(&iqs620_pwm->chip, + &iqs620_pwm->chip.pwms[0], &state); + if (error) { + dev_err(iqs620_pwm->chip.dev, + "Failed to re-initialize device: %d\n", error); + return NOTIFY_BAD; + } + + return NOTIFY_OK; +} + +static void iqs620_pwm_notifier_unregister(void *context) +{ + struct iqs620_pwm_private *iqs620_pwm = context; + int error; + + error = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (error) + dev_err(iqs620_pwm->chip.dev, + "Failed to unregister notifier: %d\n", error); +} + +static const struct pwm_ops iqs620_pwm_ops = { + .apply = iqs620_pwm_apply, + .owner = THIS_MODULE, +}; + +static int iqs620_pwm_probe(struct platform_device *pdev) +{ + struct iqs620_pwm_private *iqs620_pwm; + int error; + + 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; + + iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier; + error = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (error) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", error); + return error; + } + + error = devm_add_action_or_reset(&pdev->dev, + iqs620_pwm_notifier_unregister, + iqs620_pwm); + if (error) { + dev_err(&pdev->dev, "Failed to add action: %d\n", error); + return error; + } + + error = pwmchip_add(&iqs620_pwm->chip); + if (error) { + dev_err(&pdev->dev, "Failed to add device: %d\n", error); + return error; + } + + iqs620_pwm->ready = true; + + return 0; +} + +static int iqs620_pwm_remove(struct platform_device *pdev) +{ + struct iqs620_pwm_private *iqs620_pwm = platform_get_drvdata(pdev); + int error; + + error = pwmchip_remove(&iqs620_pwm->chip); + if (error) + dev_err(&pdev->dev, "Failed to remove device: %d\n", error); + + return error; +} + +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 Oct 21 04:11:21 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11201291 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id F128D13BD for ; Mon, 21 Oct 2019 04:19:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id C5E24205ED for ; Mon, 21 Oct 2019 04:19:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727096AbfJUETW (ORCPT ); Mon, 21 Oct 2019 00:19:22 -0400 Received: from p3plsmtpa06-04.prod.phx3.secureserver.net ([173.201.192.105]:48455 "EHLO p3plsmtpa06-04.prod.phx3.secureserver.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727078AbfJUETV (ORCPT ); Mon, 21 Oct 2019 00:19:21 -0400 Received: from localhost.localdomain ([136.49.227.119]) by :SMTPAUTH: with ESMTPSA id MP2oifB4Hr3FgMP33iDya4; Sun, 20 Oct 2019 21:12:02 -0700 From: Jeff LaBundy To: lee.jones@linaro.org, dmitry.torokhov@gmail.com, jdelvare@suse.com, linux@roeck-us.net, thierry.reding@gmail.com, jic23@kernel.org, devicetree@vger.kernel.org Cc: linux-input@vger.kernel.org, linux-hwmon@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 6/8] iio: light: Add support for Azoteq IQS621 ambient light sensor Date: Sun, 20 Oct 2019 23:11:21 -0500 Message-Id: <1571631083-4962-7-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1571631083-4962-1-git-send-email-jeff@labundy.com> References: <1571631083-4962-1-git-send-email-jeff@labundy.com> X-CMAE-Envelope: MS4wfDNBRBXQUqGeHFA34QH49iQWu6Gjin5dX159Rr/RinU6vZFqRQCmRNDvJsSlymlHj/k2sOSptwUSSK9WILmJGWYz5OpHObHbVHtfXbZn8/INHzeO4+ka 9n+ZbVsovARLHXO1N4K85cyuBko89QLumAw7/mYjUm4FP4wULNyHkrV1YgluFTfmVfLBk8am08Ft+szjXiU1oCtfRB03kzYjW/nspNiQmNo++yqAxUohtRfR Y11RTC1+j7ddQkEGAjMyeLfFLOFJM2xYQ/NKAZ70vKTcZADC8WlQH69Dr9gFQVuW3btGp7qC5iqhkhUf94bTqsyFoSGis/9Dz0lHTxgOFKjVk8bsv3QxMozn Yk31J1Iaw3TsWO0MIcG6Sw88ssuro7+1HxliqO7ZVQ4KfFKEyfTvHkrnPi4RkElmiLT8eS//Ae3zxH2i8Z8j0IzJ59nPprrUiFeQKI1zrz0+6+tDBIKSoi0r 9I6Ew3xT52r5dClaKtFbCdq1K+I4zFDNYRi3OsGMSYvXHLLrl9EmxiyZxmngnMsc8fwlifVtBwGAClZhhiyTh0U4K8tDpBnTZ3gu8BbdDEHSXtOvBHXirQLz AtLdGCxdPdz2ZcHVJmgTrWz1gzohMxaKxKkUa9Y4W4+sq3A3dY5yRhffaT1opH2tQZWIY/vZkeSEN6ulujOZmamVBnTWj1Bh+rETnHd93aHYoiaLrUUqx9DH Wx24T/CjYKNc//Li32Dmfu14mc40GcxO8jy7wVcKabCVn4wJprqdPQ== Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS621 ambient light sensor, capable of reporting intensity directly in units of lux. Signed-off-by: Jeff LaBundy Acked-by: Jonathan Cameron --- drivers/iio/light/Kconfig | 10 ++ drivers/iio/light/Makefile | 1 + drivers/iio/light/iqs621-als.c | 361 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 drivers/iio/light/iqs621-als.c diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 4a1a883..aad26dc 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -162,6 +162,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 ambient light sensor" + depends on MFD_IQS62X + help + Say Y here if you want to build support for the Azoteq IQS621 + ambient light sensor. + + 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 00d1f9b..aa34358 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -20,6 +20,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..92a6173 --- /dev/null +++ b/drivers/iio/light/iqs621-als.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS621 Ambient Light Sensor + * + * Copyright (C) 2019 + * Author: Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS621_ALS_FLAGS 0x16 +#define IQS621_ALS_FLAGS_LIGHT BIT(7) + +#define IQS621_ALS_UI_OUT 0x17 + +#define IQS621_ALS_THRESH_DARK 0x80 +#define IQS621_ALS_THRESH_DARK_MAX 1020 +#define IQS621_ALS_THRESH_DARK_SHIFT 2 + +#define IQS621_ALS_THRESH_LIGHT 0x81 +#define IQS621_ALS_THRESH_LIGHT_MAX 4080 +#define IQS621_ALS_THRESH_LIGHT_SHIFT 4 + +struct iqs621_als_private { + struct iqs62x_core *iqs62x; + struct notifier_block notifier; + struct mutex lock; + bool event_en; + u8 thresh_light; + u8 thresh_dark; + u8 flags; +}; + +static int iqs621_als_init(struct iqs621_als_private *iqs621_als) +{ + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int val; + int error; + + mutex_lock(&iqs621_als->lock); + + error = regmap_write(iqs62x->map, IQS621_ALS_THRESH_LIGHT, + iqs621_als->thresh_light); + if (error) + goto err_mutex; + + error = regmap_write(iqs62x->map, IQS621_ALS_THRESH_DARK, + iqs621_als->thresh_dark); + if (error) + goto err_mutex; + + error = regmap_read(iqs62x->map, IQS621_ALS_FLAGS, &val); + if (error) + goto err_mutex; + iqs621_als->flags = val; + + error = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->event_en ? 0 : 0xFF); + +err_mutex: + mutex_unlock(&iqs621_als->lock); + + return error; +} + +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; + enum iio_event_direction dir; + int error; + + iqs621_als = container_of(notifier, struct iqs621_als_private, + notifier); + indio_dev = iio_priv_to_dev(iqs621_als); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + error = iqs621_als_init(iqs621_als); + if (error) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", error); + return NOTIFY_BAD; + } + + return NOTIFY_OK; + } + + if (!((event_data->als_flags ^ iqs621_als->flags) & + IQS621_ALS_FLAGS_LIGHT)) + return NOTIFY_DONE; + + iqs621_als->flags = event_data->als_flags; + + if (!iqs621_als->event_en) + return NOTIFY_OK; + + dir = iqs621_als->flags & IQS621_ALS_FLAGS_LIGHT ? IIO_EV_DIR_RISING : + IIO_EV_DIR_FALLING; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, dir), + iio_get_time_ns(indio_dev)); + + return NOTIFY_OK; +} + +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 error; + + error = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (error) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", error); +} + +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 error; + __le16 val_buf; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + error = regmap_raw_read(iqs62x->map, IQS621_ALS_UI_OUT, + &val_buf, sizeof(val_buf)); + if (error) + return error; + + *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); + + return iqs621_als->event_en; +} + +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; + int error; + + mutex_lock(&iqs621_als->lock); + + error = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + state ? 0 : 0xFF); + if (!error) + iqs621_als->event_en = state; + + mutex_unlock(&iqs621_als->lock); + + return error; +} + +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); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = iqs621_als->thresh_light; + *val <<= IQS621_ALS_THRESH_LIGHT_SHIFT; + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + *val = iqs621_als->thresh_dark; + *val <<= IQS621_ALS_THRESH_DARK_SHIFT; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +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; + int error = -EINVAL; + + mutex_lock(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + if (val > IQS621_ALS_THRESH_LIGHT_MAX) + break; + val >>= IQS621_ALS_THRESH_LIGHT_SHIFT; + + error = regmap_write(iqs62x->map, IQS621_ALS_THRESH_LIGHT, val); + if (!error) + iqs621_als->thresh_light = val; + break; + + case IIO_EV_DIR_FALLING: + if (val > IQS621_ALS_THRESH_DARK) + break; + val >>= IQS621_ALS_THRESH_DARK_SHIFT; + + error = regmap_write(iqs62x->map, IQS621_ALS_THRESH_DARK, val); + if (!error) + iqs621_als->thresh_dark = val; + break; + + default: + break; + } + + mutex_unlock(&iqs621_als->lock); + + return error; +} + +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_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_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_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 error; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->channels = iqs621_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels); + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs621_als_info; + + iqs621_als = iio_priv(indio_dev); + iqs621_als->iqs62x = iqs62x; + + error = regmap_read(iqs62x->map, IQS621_ALS_THRESH_LIGHT, &val); + if (error) + return error; + iqs621_als->thresh_light = val; + + error = regmap_read(iqs62x->map, IQS621_ALS_THRESH_DARK, &val); + if (error) + return error; + iqs621_als->thresh_dark = val; + + mutex_init(&iqs621_als->lock); + + error = iqs621_als_init(iqs621_als); + if (error) + return error; + + iqs621_als->notifier.notifier_call = iqs621_als_notifier; + error = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (error) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", error); + return error; + } + + error = devm_add_action_or_reset(&pdev->dev, + iqs621_als_notifier_unregister, + iqs621_als); + if (error) { + dev_err(&pdev->dev, "Failed to add action: %d\n", error); + return error; + } + + 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 Ambient Light Sensor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS621_DRV_NAME_ALS); From patchwork Mon Oct 21 04:11:22 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11201303 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 4E80E1515 for ; Mon, 21 Oct 2019 04:19:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 36DF0205ED for ; Mon, 21 Oct 2019 04:19:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727088AbfJUETY (ORCPT ); Mon, 21 Oct 2019 00:19:24 -0400 Received: from p3plsmtpa06-04.prod.phx3.secureserver.net ([173.201.192.105]:37476 "EHLO p3plsmtpa06-04.prod.phx3.secureserver.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727090AbfJUETX (ORCPT ); Mon, 21 Oct 2019 00:19:23 -0400 Received: from localhost.localdomain ([136.49.227.119]) by :SMTPAUTH: with ESMTPSA id MP2oifB4Hr3FgMP34iDyaS; Sun, 20 Oct 2019 21:12:03 -0700 From: Jeff LaBundy To: lee.jones@linaro.org, dmitry.torokhov@gmail.com, jdelvare@suse.com, linux@roeck-us.net, thierry.reding@gmail.com, jic23@kernel.org, devicetree@vger.kernel.org Cc: linux-input@vger.kernel.org, linux-hwmon@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 7/8] iio: proximity: Add support for Azoteq IQS622 proximity sensor Date: Sun, 20 Oct 2019 23:11:22 -0500 Message-Id: <1571631083-4962-8-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1571631083-4962-1-git-send-email-jeff@labundy.com> References: <1571631083-4962-1-git-send-email-jeff@labundy.com> X-CMAE-Envelope: MS4wfIkzzZVfj8/JXJzazJ5RoIxcPinmvXvFinYNcuYnwgpjVBaXpXfmn/3/u9YQhHWXgyLs5yTeootfZ6rrohzedVYQrVI43psTSpVY9Ie/3ioIQiBIxoZH NT0ePzifatp81btnudgMVeY5e5xuNx4OnrBXsc6aSsQbUqOYlc0Gp6nyejsVfKwLVbI0/73Hsuaf6uGzr7emTAsos5llshZbClELOCogPEd68p3usp1Sedv0 h8RnrlExl13vv9aPnuZ+iICYx+LSZ1YPz2NQl9DkROXhgE1Yc/7PwhAO6Yic9NfiiOoqn2mWxWGDFpjJoMdWdpVhEztKyyU5oWpXDKpDS6yxhjhtE0SPz+hp 5m1CrTPIcmSikqYj2z1Rk3v9K1svV2iPfUkqVRufLY5ItuL+g5LeYe67mjsR3NRnPnSt6S34fm/YejpRwZrHpoPzMirr3/yL5bVy4Mn3AtYtcbnhYWkrRx9I bMjNQZYcVS8lv5ytYu7Wbn3AeHuFkLHPuPEzC3v4qMkXItSTbgYiN38DQGEK5rDrpug7E99LIyV4lsCacLCvo8NLt6I2yMiCEXqDZNazRY8Zq2+8xtFWd2Hi DafRzh2ezMV+5dS2veYmhKbwRLp9kaihMDndkHb4QoX8GEqkiDg3lRENhSGEsk6n6JyvclEd9uk1m3MjfiNFGGQVHFjnJ4AToMlfKEGu/QfVRgskH9IcIMVZ e8hU7JR3KaIYXTy4F4zUtM9IelfoEN8/Wx6q1fIiT5lQdqV/+cj4nw== Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS622 proximity sensor, capable of reporting a unitless measurement of a target's prox- imity to the sensor. Signed-off-by: Jeff LaBundy --- drivers/iio/proximity/Kconfig | 10 ++ drivers/iio/proximity/Makefile | 1 + drivers/iio/proximity/iqs622-prox.c | 334 ++++++++++++++++++++++++++++++++++++ 3 files changed, 345 insertions(+) create mode 100644 drivers/iio/proximity/iqs622-prox.c diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig index d536014..2366fd7 100644 --- a/drivers/iio/proximity/Kconfig +++ b/drivers/iio/proximity/Kconfig @@ -21,6 +21,16 @@ endmenu menu "Proximity and distance sensors" +config IQS622_PROX + tristate "Azoteq IQS622 proximity sensor" + depends on MFD_IQS62X + help + Say Y here if you want to build support for the Azoteq IQS622 + proximity sensor. + + To compile this driver as a module, choose M here: the module + will be called iqs622-prox. + config ISL29501 tristate "Intersil ISL29501 Time Of Flight sensor" depends on I2C diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile index 0bb5f9d..802ba9d 100644 --- a/drivers/iio/proximity/Makefile +++ b/drivers/iio/proximity/Makefile @@ -5,6 +5,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AS3935) += as3935.o +obj-$(CONFIG_IQS622_PROX) += iqs622-prox.o obj-$(CONFIG_ISL29501) += isl29501.o obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o obj-$(CONFIG_MB1232) += mb1232.o diff --git a/drivers/iio/proximity/iqs622-prox.c b/drivers/iio/proximity/iqs622-prox.c new file mode 100644 index 0000000..a805fb21 --- /dev/null +++ b/drivers/iio/proximity/iqs622-prox.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS622 Proximity Sensor + * + * Copyright (C) 2019 + * Author: Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_PROX_MAX 255 +#define IQS622_IR_THRESH_PROX_SHIFT 0 + +#define IQS622_IR_THRESH_TOUCH 0x92 +#define IQS622_IR_THRESH_TOUCH_MAX 1020 +#define IQS622_IR_THRESH_TOUCH_SHIFT 2 + +struct iqs622_prox_private { + struct iqs62x_core *iqs62x; + struct notifier_block notifier; + struct mutex lock; + bool thresh_prox; + bool event_en; + u8 thresh; + u8 flags; +}; + +static int iqs622_prox_init(struct iqs622_prox_private *iqs622_prox) +{ + struct iqs62x_core *iqs62x = iqs622_prox->iqs62x; + unsigned int val; + int error; + + mutex_lock(&iqs622_prox->lock); + + error = regmap_write(iqs62x->map, + iqs622_prox->thresh_prox ? IQS622_IR_THRESH_PROX : + IQS622_IR_THRESH_TOUCH, + iqs622_prox->thresh); + if (error) + goto err_mutex; + + error = regmap_read(iqs62x->map, IQS622_IR_FLAGS, &val); + if (error) + goto err_mutex; + iqs622_prox->flags = val; + + error = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->ir_mask, + iqs622_prox->event_en ? 0 : 0xFF); + +err_mutex: + mutex_unlock(&iqs622_prox->lock); + + return error; +} + +static int iqs622_prox_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs622_prox_private *iqs622_prox; + struct iio_dev *indio_dev; + enum iio_event_direction dir; + int error; + u8 flags_mask; + + iqs622_prox = container_of(notifier, struct iqs622_prox_private, + notifier); + indio_dev = iio_priv_to_dev(iqs622_prox); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + error = iqs622_prox_init(iqs622_prox); + if (error) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", error); + return NOTIFY_BAD; + } + + return NOTIFY_OK; + } + + flags_mask = iqs622_prox->thresh_prox ? IQS622_IR_FLAGS_PROX : + IQS622_IR_FLAGS_TOUCH; + + if (!((event_data->ir_flags ^ iqs622_prox->flags) & flags_mask)) + return NOTIFY_DONE; + + iqs622_prox->flags = event_data->ir_flags; + + if (!iqs622_prox->event_en) + return NOTIFY_OK; + + dir = iqs622_prox->flags & flags_mask ? IIO_EV_DIR_RISING : + IIO_EV_DIR_FALLING; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, dir), + iio_get_time_ns(indio_dev)); + + return NOTIFY_OK; +} + +static void iqs622_prox_notifier_unregister(void *context) +{ + struct iqs622_prox_private *iqs622_prox = context; + struct iio_dev *indio_dev = iio_priv_to_dev(iqs622_prox); + int error; + + error = blocking_notifier_chain_unregister(&iqs622_prox->iqs62x->nh, + &iqs622_prox->notifier); + if (error) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", error); +} + +static int iqs622_prox_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs622_prox_private *iqs622_prox = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs622_prox->iqs62x; + int error; + __le16 val_buf; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + error = regmap_raw_read(iqs62x->map, IQS622_IR_UI_OUT, + &val_buf, sizeof(val_buf)); + if (error) + return error; + + *val = le16_to_cpu(val_buf); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int iqs622_prox_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 iqs622_prox_private *iqs622_prox = iio_priv(indio_dev); + + return iqs622_prox->event_en; +} + +static int iqs622_prox_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 iqs622_prox_private *iqs622_prox = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs622_prox->iqs62x; + int error; + + mutex_lock(&iqs622_prox->lock); + + error = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->ir_mask, state ? 0 : 0xFF); + if (!error) + iqs622_prox->event_en = state; + + mutex_unlock(&iqs622_prox->lock); + + return error; +} + +static int iqs622_prox_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 iqs622_prox_private *iqs622_prox = iio_priv(indio_dev); + + *val = iqs622_prox->thresh; + *val <<= (iqs622_prox->thresh_prox ? IQS622_IR_THRESH_PROX_SHIFT : + IQS622_IR_THRESH_TOUCH_SHIFT); + + return IIO_VAL_INT; +} + +static int iqs622_prox_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 iqs622_prox_private *iqs622_prox = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs622_prox->iqs62x; + int error = -EINVAL; + + mutex_lock(&iqs622_prox->lock); + + if (val > (iqs622_prox->thresh_prox ? IQS622_IR_THRESH_PROX_MAX : + IQS622_IR_THRESH_TOUCH_MAX)) + goto err_mutex; + val >>= (iqs622_prox->thresh_prox ? IQS622_IR_THRESH_PROX_SHIFT : + IQS622_IR_THRESH_TOUCH_SHIFT); + + error = regmap_write(iqs62x->map, + iqs622_prox->thresh_prox ? IQS622_IR_THRESH_PROX : + IQS622_IR_THRESH_TOUCH, + val); + if (!error) + iqs622_prox->thresh = val; + +err_mutex: + mutex_unlock(&iqs622_prox->lock); + + return error; +} + +static const struct iio_info iqs622_prox_info = { + .read_raw = &iqs622_prox_read_raw, + .read_event_config = iqs622_prox_read_event_config, + .write_event_config = iqs622_prox_write_event_config, + .read_event_value = iqs622_prox_read_event_value, + .write_event_value = iqs622_prox_write_event_value, +}; + +static const struct iio_event_spec iqs622_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_prox_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs622_prox_events, + .num_event_specs = ARRAY_SIZE(iqs622_prox_events), + }, +}; + +static int iqs622_prox_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs622_prox_private *iqs622_prox; + struct iio_dev *indio_dev; + unsigned int val; + int error; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs622_prox)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->channels = iqs622_prox_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs622_prox_channels); + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs622_prox_info; + + iqs622_prox = iio_priv(indio_dev); + iqs622_prox->iqs62x = iqs62x; + + iqs622_prox->thresh_prox = device_property_read_bool(&pdev->dev, + "azoteq,use-prox"); + + error = regmap_read(iqs62x->map, + iqs622_prox->thresh_prox ? IQS622_IR_THRESH_PROX : + IQS622_IR_THRESH_TOUCH, + &val); + if (error) + return error; + iqs622_prox->thresh = val; + + mutex_init(&iqs622_prox->lock); + + error = iqs622_prox_init(iqs622_prox); + if (error) + return error; + + iqs622_prox->notifier.notifier_call = iqs622_prox_notifier; + error = blocking_notifier_chain_register(&iqs622_prox->iqs62x->nh, + &iqs622_prox->notifier); + if (error) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", error); + return error; + } + + error = devm_add_action_or_reset(&pdev->dev, + iqs622_prox_notifier_unregister, + iqs622_prox); + if (error) { + dev_err(&pdev->dev, "Failed to add action: %d\n", error); + return error; + } + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs622_prox_platform_driver = { + .driver = { + .name = IQS622_DRV_NAME_PROX, + }, + .probe = iqs622_prox_probe, +}; +module_platform_driver(iqs622_prox_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS622 Proximity Sensor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS622_DRV_NAME_PROX); From patchwork Mon Oct 21 04:11:23 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff LaBundy X-Patchwork-Id: 11201299 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 0BB221951 for ; Mon, 21 Oct 2019 04:19:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id D3C3F214D9 for ; Mon, 21 Oct 2019 04:19:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727101AbfJUETX (ORCPT ); Mon, 21 Oct 2019 00:19:23 -0400 Received: from p3plsmtpa06-04.prod.phx3.secureserver.net ([173.201.192.105]:56740 "EHLO p3plsmtpa06-04.prod.phx3.secureserver.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727088AbfJUETX (ORCPT ); Mon, 21 Oct 2019 00:19:23 -0400 Received: from localhost.localdomain ([136.49.227.119]) by :SMTPAUTH: with ESMTPSA id MP2oifB4Hr3FgMP35iDyat; Sun, 20 Oct 2019 21:12:04 -0700 From: Jeff LaBundy To: lee.jones@linaro.org, dmitry.torokhov@gmail.com, jdelvare@suse.com, linux@roeck-us.net, thierry.reding@gmail.com, jic23@kernel.org, devicetree@vger.kernel.org Cc: linux-input@vger.kernel.org, linux-hwmon@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 8/8] iio: position: Add support for Azoteq IQS624/625 angle sensor Date: Sun, 20 Oct 2019 23:11:23 -0500 Message-Id: <1571631083-4962-9-git-send-email-jeff@labundy.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1571631083-4962-1-git-send-email-jeff@labundy.com> References: <1571631083-4962-1-git-send-email-jeff@labundy.com> X-CMAE-Envelope: MS4wfCrKAUq7cK63kIZBAVZ3DeYhkNHems3Cp8/Ghwy/Higeru/SSzta8+3iOHp8rgw2EL6mOdAYGmCVCo3KMkZrRHYyGgOU1+gIzUF+JSDZb5KMdjFLS8B0 a1lp6pTrN9N3/WmMHpWIxtOCg1CeMyf9fkSmOPVjHH3FUWDxJ3sdUiNJouyYnoOqON+IyWnhS6wrNDXKunN8erVI3fMoHmXXs7x+suFUXtG6IZrVS290crSM zTwpYDScAVj2s+indksHrJOkWQ0uU6sIStabUDkEfC/mYBZ+SE0E2V/M/OMyF+2KQkV4n2gtrikdPa9PJ2ixWHgy2wszRyyn9wq+2QdlqY+O2f9hWhBR9GfP fY5r0ArZGa8AajBfH8HoT2pRfeDaHaQ0TaFL1UUeb64Qf2LIGjzP1ASLOMwbPQGNAlBrazyIRd6MFcGHAFXvwWlXG+urrZlwCh2pEWkZpaikmkv3k2sJLcTN j1VQJLBO0RS6DIqfMZyDlTGNCVT98mMuInqqfzStp9S+IrETNvLeOngZ0cB/no4tUgfYwoKDGMEaqnTdrTILHe+Y6gRbumAg7kieN2YMovWkKiiYpjl+ZY7P jGdOZ7gmQgHcKgNRzx6WDvpqetI2RjSGiGNfjUuA3kK9heqKUiOGowetCDogwzPNiZUr3HS2vfipcr7rm0hERsmKOYyBQhBVtoa1LA9V320RRbBtsD9WTNCv oRpAh5TE4XWqnsAULuPqiNrL5G4T3fNixSKj2Tb8TCySty231k4jBQ== Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org This patch adds support for the Azoteq IQS624 and IQS625 angular position sensors, capable of reporting the angle of a rotating shaft down to 1 and 10 degrees of accuracy, respectively. This patch also introduces a home for linear and angular position sensors. Unlike resolvers, they are typically contactless and use the Hall effect. Signed-off-by: Jeff LaBundy Acked-by: Jonathan Cameron --- drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/position/Kconfig | 19 +++ drivers/iio/position/Makefile | 7 + drivers/iio/position/iqs624-pos.c | 302 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 330 insertions(+) create mode 100644 drivers/iio/position/Kconfig create mode 100644 drivers/iio/position/Makefile create mode 100644 drivers/iio/position/iqs624-pos.c 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..ed9f975 --- /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 sensor" + depends on MFD_IQS62X + 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..d975065 --- /dev/null +++ b/drivers/iio/position/iqs624-pos.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS624/625 Angular Position Sensor + * + * Copyright (C) 2019 + * Author: 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 event_en; + union { + u16 angle; + u8 interval; + }; +}; + +static int iqs624_pos_init(struct iqs624_pos_private *iqs624_pos) +{ + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; + unsigned int val; + int error; + __le16 val_buf; + + if (iqs62x->dev_desc->prod_num == IQS624_PROD_NUM) { + error = regmap_raw_read(iqs62x->map, IQS624_POS_DEG_OUT, + &val_buf, sizeof(val_buf)); + if (error) + return error; + + iqs624_pos->angle = le16_to_cpu(val_buf); + } else { + error = regmap_read(iqs62x->map, iqs62x->dev_desc->interval, + &val); + if (error) + return error; + + iqs624_pos->interval = val; + } + + mutex_lock(&iqs624_pos->lock); + + /* + * 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. + */ + error = regmap_update_bits(iqs62x->map, IQS624_HALL_UI, + iqs62x->dev_desc->prod_num == + IQS624_PROD_NUM ? IQS624_HALL_UI_WHL_EVENT : + IQS624_HALL_UI_INT_EVENT, + iqs624_pos->event_en ? 0 : 0xFF); + + mutex_unlock(&iqs624_pos->lock); + + return error; +} + +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; + int error; + + iqs624_pos = container_of(notifier, struct iqs624_pos_private, + notifier); + indio_dev = iio_priv_to_dev(iqs624_pos); + iqs62x = iqs624_pos->iqs62x; + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + error = iqs624_pos_init(iqs624_pos); + if (error) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", error); + return NOTIFY_BAD; + } + + return NOTIFY_OK; + } + + if (iqs62x->dev_desc->prod_num == IQS624_PROD_NUM) { + if (event_data->ui_data == iqs624_pos->angle) + return NOTIFY_DONE; + + iqs624_pos->angle = event_data->ui_data; + } else { + if (event_data->interval == iqs624_pos->interval) + return NOTIFY_DONE; + + iqs624_pos->interval = event_data->interval; + } + + if (!iqs624_pos->event_en) + return NOTIFY_OK; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + iio_get_time_ns(indio_dev)); + + return NOTIFY_OK; +} + +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 error; + + error = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); + if (error) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", error); +} + +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; + int error; + __le16 val_buf; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iqs62x->dev_desc->prod_num == IQS624_PROD_NUM) { + error = regmap_raw_read(iqs62x->map, IQS624_POS_DEG_OUT, + &val_buf, sizeof(val_buf)); + if (error) + return error; + + *val = le16_to_cpu(val_buf); + } else { + error = regmap_read(iqs62x->map, + iqs62x->dev_desc->interval, val); + if (error) + return error; + } + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (iqs62x->dev_desc->prod_num == IQS624_PROD_NUM) { + *val = IQS624_POS_SCALE1; + } else { + error = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV, + val); + if (error) + return error; + + *val *= 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); + + return iqs624_pos->event_en; +} + +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; + int error; + + mutex_lock(&iqs624_pos->lock); + + error = regmap_update_bits(iqs62x->map, IQS624_HALL_UI, + iqs62x->dev_desc->prod_num == + IQS624_PROD_NUM ? IQS624_HALL_UI_WHL_EVENT : + IQS624_HALL_UI_INT_EVENT, + state ? 0 : 0xFF); + if (!error) + iqs624_pos->event_en = state; + + mutex_unlock(&iqs624_pos->lock); + + return error; +} + +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 error; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos)); + if (!indio_dev) + return -ENOMEM; + + 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; + + iqs624_pos = iio_priv(indio_dev); + iqs624_pos->iqs62x = iqs62x; + + mutex_init(&iqs624_pos->lock); + + error = iqs624_pos_init(iqs624_pos); + if (error) + return error; + + iqs624_pos->notifier.notifier_call = iqs624_pos_notifier; + error = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); + if (error) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", error); + return error; + } + + error = devm_add_action_or_reset(&pdev->dev, + iqs624_pos_notifier_unregister, + iqs624_pos); + if (error) { + dev_err(&pdev->dev, "Failed to add action: %d\n", error); + return error; + } + + 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 Sensor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" IQS624_DRV_NAME_POS);