Linux-IIO Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625
@ 2019-10-21  4:11 Jeff LaBundy
  2019-10-21  4:11 ` [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings Jeff LaBundy
                   ` (7 more replies)
  0 siblings, 8 replies; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

This series adds support for six-channel members of the Azoteq ProxFusion
family of sensor devices: IQS620A, IQS621, IQS622, IQS624 and IQS625. Each
device integrates multiple sensor technologies in a single package.

A multi-function device (MFD) driver supports core functions common to all
devices, including device identification, firmware, interrupt handling and
runtime power management. The MFD driver is also responsible for adding all
product-specific sub-devices.

Each device supports self-capacitive, Hall-effect, and (in some cases) mutual-
inductive sensing. These functions represent keys or switches and are supported
by an input driver that covers all five devices. An assortment of pwm, hwmon
and iio drivers support device-specific functions.

This series was tested using the following development hardware: IQS620AEV04,
IQS621EV04, IQS622EV04 and IQS624/5EV04.

Jeff LaBundy (8):
  dt-bindings: mfd: iqs62x: Add bindings
  mfd: Add support for Azoteq IQS620A/621/622/624/625
  input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
  hwmon: Add support for Azoteq IQS620AT temperature sensor
  pwm: Add support for Azoteq IQS620A PWM generator
  iio: light: Add support for Azoteq IQS621 ambient light sensor
  iio: proximity: Add support for Azoteq IQS622 proximity sensor
  iio: position: Add support for Azoteq IQS624/625 angle sensor

 Documentation/devicetree/bindings/mfd/iqs62x.txt | 242 +++++++++
 drivers/hwmon/Kconfig                            |  12 +-
 drivers/hwmon/Makefile                           |   1 +
 drivers/hwmon/iqs620at-temp.c                    |  96 ++++
 drivers/iio/Kconfig                              |   1 +
 drivers/iio/Makefile                             |   1 +
 drivers/iio/light/Kconfig                        |  10 +
 drivers/iio/light/Makefile                       |   1 +
 drivers/iio/light/iqs621-als.c                   | 361 +++++++++++++
 drivers/iio/position/Kconfig                     |  19 +
 drivers/iio/position/Makefile                    |   7 +
 drivers/iio/position/iqs624-pos.c                | 302 +++++++++++
 drivers/iio/proximity/Kconfig                    |  10 +
 drivers/iio/proximity/Makefile                   |   1 +
 drivers/iio/proximity/iqs622-prox.c              | 334 ++++++++++++
 drivers/input/keyboard/Kconfig                   |  10 +
 drivers/input/keyboard/Makefile                  |   1 +
 drivers/input/keyboard/iqs62x-keys.c             | 340 ++++++++++++
 drivers/mfd/Kconfig                              |  13 +
 drivers/mfd/Makefile                             |   2 +
 drivers/mfd/iqs62x-core.c                        | 638 +++++++++++++++++++++++
 drivers/mfd/iqs62x-tables.c                      | 424 +++++++++++++++
 drivers/pwm/Kconfig                              |  10 +
 drivers/pwm/Makefile                             |   1 +
 drivers/pwm/pwm-iqs620a.c                        | 167 ++++++
 include/linux/mfd/iqs62x.h                       | 148 ++++++
 26 files changed, 3151 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.txt
 create mode 100644 drivers/hwmon/iqs620at-temp.c
 create mode 100644 drivers/iio/light/iqs621-als.c
 create mode 100644 drivers/iio/position/Kconfig
 create mode 100644 drivers/iio/position/Makefile
 create mode 100644 drivers/iio/position/iqs624-pos.c
 create mode 100644 drivers/iio/proximity/iqs622-prox.c
 create mode 100644 drivers/input/keyboard/iqs62x-keys.c
 create mode 100644 drivers/mfd/iqs62x-core.c
 create mode 100644 drivers/mfd/iqs62x-tables.c
 create mode 100644 drivers/pwm/pwm-iqs620a.c
 create mode 100644 include/linux/mfd/iqs62x.h

--
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings
  2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
@ 2019-10-21  4:11 ` Jeff LaBundy
  2019-10-22 11:00   ` Jonathan Cameron
  2019-10-21  4:11 ` [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

This patch adds binding documentation for six-channel members of the
Azoteq ProxFusion family of sensor devices.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
---
 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 = <KEY_SELECT>,
+						 <KEY_MENU>,
+						 <KEY_OK>,
+						 <KEY_MENU>;
+
+				hall_switch_south {
+					linux,code = <SW_LID>;
+					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>,
+						 <KEY_MUTE>;
+
+				hall_switch_north {
+					linux,code = <SW_DOCK>;
+				};
+
+				hall_switch_south {
+					linux,code = <SW_TABLET_MODE>;
+				};
+			};
+		};
+
+		/* ... */
+	};
+
+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 = <BTN_0>,
+						 <0>,
+						 <BTN_1>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <0>,
+						 <KEY_VOLUMEUP>,
+						 <KEY_VOLUMEDOWN>;
+			};
+		};
+
+		/* ... */
+	};
-- 
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625
  2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
  2019-10-21  4:11 ` [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings Jeff LaBundy
@ 2019-10-21  4:11 ` Jeff LaBundy
  2019-10-31 13:44   ` Lee Jones
  2019-10-21  4:11 ` [PATCH 3/8] input: keyboard: " Jeff LaBundy
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

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 <jeff@labundy.com>
---
 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 <jeff@labundy.com>
+ *
+ * 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 <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#include <linux/mfd/iqs62x.h>
+
+#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 <jeff@labundy.com>");
+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 <jeff@labundy.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/iqs62x.h>
+
+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 <jeff@labundy.com>
+ */
+
+#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 */
-- 
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 3/8] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
  2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
  2019-10-21  4:11 ` [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings Jeff LaBundy
  2019-10-21  4:11 ` [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
@ 2019-10-21  4:11 ` " Jeff LaBundy
  2019-10-23  0:22   ` Dmitry Torokhov
  2019-10-21  4:11 ` [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

This patch adds touch key support for six-channel members of the
Azoteq ProxFusion family of sensor devices.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
---
 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 <jeff@labundy.com>
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/mfd/iqs62x.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+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 <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Touch Keys");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS62X_DRV_NAME_KEYS);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor
  2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (2 preceding siblings ...)
  2019-10-21  4:11 ` [PATCH 3/8] input: keyboard: " Jeff LaBundy
@ 2019-10-21  4:11 ` Jeff LaBundy
  2019-10-21 15:38   ` Guenter Roeck
  2019-10-21  4:11 ` [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

This patch adds support for the Azoteq IQS620AT temperature sensor,
capable of reporting its absolute die temperature.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
---
 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 <jeff@labundy.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/iqs62x.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#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 <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS620AT Temperature Sensor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS620_DRV_NAME_TEMP);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator
  2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (3 preceding siblings ...)
  2019-10-21  4:11 ` [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
@ 2019-10-21  4:11 ` Jeff LaBundy
  2019-10-21  7:34   ` Uwe Kleine-König
  2019-10-21  4:11 ` [PATCH 6/8] iio: light: Add support for Azoteq IQS621 ambient light sensor Jeff LaBundy
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

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 <jeff@labundy.com>
---
 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 <jeff@labundy.com>
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mfd/iqs62x.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#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 <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS620A PWM Generator");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS620_DRV_NAME_PWM);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 6/8] iio: light: Add support for Azoteq IQS621 ambient light sensor
  2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (4 preceding siblings ...)
  2019-10-21  4:11 ` [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
@ 2019-10-21  4:11 ` Jeff LaBundy
  2019-10-22 11:23   ` Jonathan Cameron
  2019-10-21  4:11 ` [PATCH 7/8] iio: proximity: Add support for Azoteq IQS622 proximity sensor Jeff LaBundy
  2019-10-21  4:11 ` [PATCH 8/8] iio: position: Add support for Azoteq IQS624/625 angle sensor Jeff LaBundy
  7 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

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 <jeff@labundy.com>
---
 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 <jeff@labundy.com>
+ */
+
+#include <linux/device.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/kernel.h>
+#include <linux/mfd/iqs62x.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#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 <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS621 Ambient Light Sensor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS621_DRV_NAME_ALS);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 7/8] iio: proximity: Add support for Azoteq IQS622 proximity sensor
  2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (5 preceding siblings ...)
  2019-10-21  4:11 ` [PATCH 6/8] iio: light: Add support for Azoteq IQS621 ambient light sensor Jeff LaBundy
@ 2019-10-21  4:11 ` Jeff LaBundy
  2019-10-22 11:23   ` Jonathan Cameron
  2019-10-21  4:11 ` [PATCH 8/8] iio: position: Add support for Azoteq IQS624/625 angle sensor Jeff LaBundy
  7 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

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 <jeff@labundy.com>
---
 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 <jeff@labundy.com>
+ */
+
+#include <linux/device.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/kernel.h>
+#include <linux/mfd/iqs62x.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#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 <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS622 Proximity Sensor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS622_DRV_NAME_PROX);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 8/8] iio: position: Add support for Azoteq IQS624/625 angle sensor
  2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (6 preceding siblings ...)
  2019-10-21  4:11 ` [PATCH 7/8] iio: proximity: Add support for Azoteq IQS622 proximity sensor Jeff LaBundy
@ 2019-10-21  4:11 ` Jeff LaBundy
  2019-10-22 11:28   ` Jonathan Cameron
  7 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-21  4:11 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree
  Cc: linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland, Jeff LaBundy

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 <jeff@labundy.com>
---
 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 <jeff@labundy.com>
+ */
+
+#include <linux/device.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/kernel.h>
+#include <linux/mfd/iqs62x.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#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 <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS624_DRV_NAME_POS);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator
  2019-10-21  4:11 ` [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
@ 2019-10-21  7:34   ` Uwe Kleine-König
  2019-10-22  4:36     ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Uwe Kleine-König @ 2019-10-21  7:34 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree, linux-input, linux-hwmon, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hello Jeff,

On Sun, Oct 20, 2019 at 11:11:20PM -0500, Jeff LaBundy wrote:
> 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 <jeff@labundy.com>
> ---
>  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

This is only a runtime dependency if I'm not mistaken, so it would be
great to have

	depends on MFD_IQS62X || COMPILE_TEST
	depends on REGMAP

here.

> +	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 <jeff@labundy.com>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/iqs62x.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#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;

This is always true, so you can drop it.

> +};
> +
> +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			    struct pwm_state *state)

Since

	71523d1812ac ("pwm: Ensure pwm_apply_state() doesn't modify the state argument")

this isn't the right prototype.

> +{
> +	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;

This suggests that if the value in the IQS620_PWM_DUTY_CYCLE is 0 the
duty cycle is 1/256 ms with a period of 1 ms and the output cannot be
constant inactive. If this is right please add a paragraph in the
driver's comment at the top:

	* Limitations:
	* - The hardware cannot generate a 0% duty cycle

(Please stick to this format, other drivers use it, too.)

How does the hardware behave on changes? For example you're first
committing the duty cycle and then on/off. Can it happen that between

	pwm_apply_state(pwm, { .duty_cycle = 3900, .period = 1000000, .enabled = true)
	...
	pwm_apply_state(pwm, { .duty_cycle = 1000000, .period = 1000000, .enabled = false)

the output is active for longer than 4 µs because the iqs620_pwm_apply
function is preempted between the two register writes and so we already
have .duty_cycle = 1000000 but still .enabled = true in the hardware?

Does a change complete the currently running period? Does disabling
complete the currently running period? If so, does regmap_update_bits
block until the new setting is active?

The .apply function fails to check for .pwm_polarity. You want something
like:

	if (state->polarity != PWM_POLARITY_NORMAL)
		return -ENOTSUPP;

(That's what pwm-rcar and the core (in the absence of .set_polarity for
old-style drivers) are using. @Thierry: It would be great to fix the
vaule that should be returned in this case. pwm-lpss and sifive use
-EINVAL.)

> +	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;

So the PWM can loose it's state sometimes? When does that happen?

> +}
> +
> +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,

Please implement a .get_state callback.

> +	.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);

I wonder if this is safe. If in iqs620_pwm_notifier_unregister()
unregistering of the notifier goes wrong (not sure when this can happen)
the memory behind iqs620_pwm goes away. Then later iqs620_pwm_notifier
might be called trying to use *iqs620_pwm ...

> +	if (error) {
> +		dev_err(&pdev->dev, "Failed to add action: %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,
> +};

I'm not a big fan of aligning the = in struct initializers. The downside
is that if you later add

	.prevent_deferred_probe = true,

you either have to touch all (otherwise unrelated) lines to realign
which adds churn, or the structure is only partially aligned which looks
ugly. That's why I stick to a single space before the =.

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor
  2019-10-21  4:11 ` [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
@ 2019-10-21 15:38   ` Guenter Roeck
  2019-10-22  2:26     ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Guenter Roeck @ 2019-10-21 15:38 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, thierry.reding, jic23,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Sun, Oct 20, 2019 at 11:11:19PM -0500, Jeff LaBundy wrote:
> This patch adds support for the Azoteq IQS620AT temperature sensor,
> capable of reporting its absolute die temperature.
> 
> Signed-off-by: Jeff LaBundy <jeff@labundy.com>

Seems to me this might be more feasible as iio driver.
Jonathan, what do you think ?

> ---
>  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

No unrelated changes, please, and most definitely no
unrelated whitespace changes.

>  	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 <jeff@labundy.com>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/iqs62x.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#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;

			-EOPNOTSUPP
> +
> +	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);

Such an error would either be static, caused by bad attributes,
or a bad name, which is already logged, or a memory allocation
failure, which is also already logged. The error message does
therefore not add any value.

> +	}
> +
> +	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 <jeff@labundy.com>");
> +MODULE_DESCRIPTION("Azoteq IQS620AT Temperature Sensor");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" IQS620_DRV_NAME_TEMP);
> -- 
> 2.7.4
> 

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor
  2019-10-21 15:38   ` Guenter Roeck
@ 2019-10-22  2:26     ` Jeff LaBundy
  2019-10-22  3:22       ` Guenter Roeck
  0 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-22  2:26 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: lee.jones, dmitry.torokhov, jdelvare, thierry.reding, jic23,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Guenter,

Thank you for your prompt review.

On Mon, Oct 21, 2019 at 08:38:25AM -0700, Guenter Roeck wrote:
> On Sun, Oct 20, 2019 at 11:11:19PM -0500, Jeff LaBundy wrote:
> > This patch adds support for the Azoteq IQS620AT temperature sensor,
> > capable of reporting its absolute die temperature.
> > 
> > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> 
> Seems to me this might be more feasible as iio driver.
> Jonathan, what do you think ?
> 

Interestingly enough, this actually started as an iio driver; however the
"When to Use" slide of [0] made me suspect that conventional devices with
the temperature sensing element integrated on the die belong in hwmon.

I then found the highly similar ad7314, which Jonathan himself appears to
have converted from iio to hwmon. Therefore, I placed this where existing
drivers seemed to match the most, especially since the temperature sensors
in iio generally use IR or a thermocouple.

That being said, I would be happy to move this into iio so long as Jonathan
does not mind, as it would limit the blast radius of this patch series.

> > ---
> >  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
> 
> No unrelated changes, please, and most definitely no
> unrelated whitespace changes.
> 

Sorry about that; it seems the original file had some trailing whitespace
here and my editor cropped it automatically. Unfortunately I didn't catch
it until after I sent out the series.

> >  	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 <jeff@labundy.com>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/err.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/iqs62x.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/regmap.h>
> > +
> > +#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;
> 
> 			-EOPNOTSUPP

Sure thing; will do.

> > +
> > +	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);
> 
> Such an error would either be static, caused by bad attributes,
> or a bad name, which is already logged, or a memory allocation
> failure, which is also already logged. The error message does
> therefore not add any value.
> 

Sure thing; I'll remove the error message.

> > +	}
> > +
> > +	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 <jeff@labundy.com>");
> > +MODULE_DESCRIPTION("Azoteq IQS620AT Temperature Sensor");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" IQS620_DRV_NAME_TEMP);
> > -- 
> > 2.7.4
> > 
> 

Kind regards,
Jeff LaBundy

[0]: https://elinux.org/images/b/ba/ELC_2017_-_Industrial_IO_and_You-_Nonsense_Hacks%21.pdf

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor
  2019-10-22  2:26     ` Jeff LaBundy
@ 2019-10-22  3:22       ` Guenter Roeck
  2019-10-22 11:38         ` Jonathan Cameron
  0 siblings, 1 reply; 37+ messages in thread
From: Guenter Roeck @ 2019-10-22  3:22 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, thierry.reding, jic23,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

On 10/21/19 7:26 PM, Jeff LaBundy wrote:
> Hi Guenter,
> 
> Thank you for your prompt review.
> 
> On Mon, Oct 21, 2019 at 08:38:25AM -0700, Guenter Roeck wrote:
>> On Sun, Oct 20, 2019 at 11:11:19PM -0500, Jeff LaBundy wrote:
>>> This patch adds support for the Azoteq IQS620AT temperature sensor,
>>> capable of reporting its absolute die temperature.
>>>
>>> Signed-off-by: Jeff LaBundy <jeff@labundy.com>
>>
>> Seems to me this might be more feasible as iio driver.
>> Jonathan, what do you think ?
>>
> 
> Interestingly enough, this actually started as an iio driver; however the
> "When to Use" slide of [0] made me suspect that conventional devices with
> the temperature sensing element integrated on the die belong in hwmon.
> 
> I then found the highly similar ad7314, which Jonathan himself appears to
> have converted from iio to hwmon. Therefore, I placed this where existing
> drivers seemed to match the most, especially since the temperature sensors
> in iio generally use IR or a thermocouple.
> 
> That being said, I would be happy to move this into iio so long as Jonathan
> does not mind, as it would limit the blast radius of this patch series.
> 

I don't recall why the ad7314 driver was moved. With a conversion time of 40uS
it is most definitely not a typical use case for a hwmon sensor.

Anyway, not worth arguing about. Just don't complain later. There is an
iio->hwmon bridge, but no hwmon->iio bridge, so the decision does have some
impact. Specifically, userspace will have to implement both hwmon and iio
access to handle the chip.

Guenter

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator
  2019-10-21  7:34   ` Uwe Kleine-König
@ 2019-10-22  4:36     ` Jeff LaBundy
  2019-10-22  6:54       ` Uwe Kleine-König
  0 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-22  4:36 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree, linux-input, linux-hwmon, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Uwe,

Thank you for your prompt review.

On Mon, Oct 21, 2019 at 09:34:19AM +0200, Uwe Kleine-König wrote:
> Hello Jeff,
> 
> On Sun, Oct 20, 2019 at 11:11:20PM -0500, Jeff LaBundy wrote:
> > 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 <jeff@labundy.com>
> > ---
> >  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
> 
> This is only a runtime dependency if I'm not mistaken, so it would be
> great to have
> 
> 	depends on MFD_IQS62X || COMPILE_TEST
> 	depends on REGMAP
> 
> here.
> 

Sure thing; will do. Actually, it seems I can add this to all but the input
driver, as that one relies on iqs62x_events exported from the MFD.

> > +	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 <jeff@labundy.com>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/iqs62x.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pwm.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +
> > +#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;
> 
> This is always true, so you can drop it.
> 

This is here because iqs620_pwm_notifier references chip.pwms, which is
not allocated until after the notifier is registered and pwmchip_add is
called. So it protects against this (albeit unlikely) race condition:

1. iqs620_pwm_notifier is registered
2. Device immediately suffers an asynchronous reset and notifier chain
   is called (more on that in a bit)
3. iqs620_pwm_notifier evaluates chips.pwms (NULL)

I felt this was simpler than calling pwmchip_add before registering the
notifier and adding an error/tear-down path in iqs620_pwm_probe in case
of failure. I would be happy to add a comment or two to explain the not-
so-obvious purpose of this flag.

> > +};
> > +
> > +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > +			    struct pwm_state *state)
> 
> Since
> 
> 	71523d1812ac ("pwm: Ensure pwm_apply_state() doesn't modify the state argument")
> 
> this isn't the right prototype.
> 

Sure thing; I will add the 'const' qualifier and remove the two changes
to the state argument.

> > +{
> > +	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;
> 
> This suggests that if the value in the IQS620_PWM_DUTY_CYCLE is 0 the
> duty cycle is 1/256 ms with a period of 1 ms and the output cannot be
> constant inactive. If this is right please add a paragraph in the
> driver's comment at the top:
> 
> 	* Limitations:
> 	* - The hardware cannot generate a 0% duty cycle
> 
> (Please stick to this format, other drivers use it, too.)
> 

That's correct; the lowest duty cycle that can be achieved using only the
IQS620_PWM_DUTY_CYCLE register is 0.4%. We can, however, generate 0% duty
cycle by disabling the output altogether using a separate register. Would
that be better than flat-out saying it's impossible?

> How does the hardware behave on changes? For example you're first
> committing the duty cycle and then on/off. Can it happen that between
> 
> 	pwm_apply_state(pwm, { .duty_cycle = 3900, .period = 1000000, .enabled = true)
> 	...
> 	pwm_apply_state(pwm, { .duty_cycle = 1000000, .period = 1000000, .enabled = false)
> 
> the output is active for longer than 4 µs because the iqs620_pwm_apply
> function is preempted between the two register writes and so we already
> have .duty_cycle = 1000000 but still .enabled = true in the hardware?
> 

My results show that it is possible to generate up to two irregular periods
by changing the duty cycle while the output is active.

Depending on the ratio of old-to-new duty cycle and the position of the I2C
write relative to the asynchronous output, the device may produce one pulse
for which the width represents neither the old nor the new duty cycle.

> Does a change complete the currently running period? Does disabling
> complete the currently running period? If so, does regmap_update_bits
> block until the new setting is active?
> 

A quick test reveals the following:

* Duty cycle changes may interrupt a running period, i.e., the output may
  transition in the middle of the period to accommodate the new duty cycle.
* Disabling the output drives it to zero immediately, i.e., the period does
  does not run to completion.

I will add a 'Limitations' section at the top as other drivers do, and call
these points out specifically.

> The .apply function fails to check for .pwm_polarity. You want something
> like:
> 
> 	if (state->polarity != PWM_POLARITY_NORMAL)
> 		return -ENOTSUPP;
> 
> (That's what pwm-rcar and the core (in the absence of .set_polarity for
> old-style drivers) are using. @Thierry: It would be great to fix the
> vaule that should be returned in this case. pwm-lpss and sifive use
> -EINVAL.)
> 

Sure thing; I'll return -ENOTSUPP in this case.

> > +	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;
> 
> So the PWM can loose it's state sometimes? When does that happen?
> 

That's correct. The device performs an internal soft reset in the presence
of what it considers to be an I2C timeout error; in this case all registers
are restored to their default values.

The data sheet goes so far as to recommend monitoring for this interrupt and
restoring the device on-the-fly. I have added some comments in iqs62x_irq in
patch [2/8] which provides some further detail.

> > +}
> > +
> > +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,
> 
> Please implement a .get_state callback.
> 

Sure thing; will do.

> > +	.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);
> 
> I wonder if this is safe. If in iqs620_pwm_notifier_unregister()
> unregistering of the notifier goes wrong (not sure when this can happen)
> the memory behind iqs620_pwm goes away. Then later iqs620_pwm_notifier
> might be called trying to use *iqs620_pwm ...

I think this is purely theoretical, as blocking_notifier_chain_unregister
only fails if the notifier is not found in the chain. If for some reason
blocking_notifier_chain_register fails (which currently cannot happen, as
it always returns zero), the driver will fail to probe before the action
could be added.

This of course means the error message in iqs620_pwm_notifier_unregister
is unnecessary; it is simply provided for debug/visibility.

> 
> > +	if (error) {
> > +		dev_err(&pdev->dev, "Failed to add action: %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,
> > +};
> 
> I'm not a big fan of aligning the = in struct initializers. The downside
> is that if you later add
> 
> 	.prevent_deferred_probe = true,
> 
> you either have to touch all (otherwise unrelated) lines to realign
> which adds churn, or the structure is only partially aligned which looks
> ugly. That's why I stick to a single space before the =.
> 

Sure thing; your argument is valid and I will reduce to a single space.

> Best regards
> Uwe
> 
> -- 
> Pengutronix e.K.                           | Uwe Kleine-König            |
> Industrial Linux Solutions                 | http://www.pengutronix.de/  |
> 

Kind regards,
Jeff LaBundy


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator
  2019-10-22  4:36     ` Jeff LaBundy
@ 2019-10-22  6:54       ` Uwe Kleine-König
  2019-10-23  2:45         ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Uwe Kleine-König @ 2019-10-22  6:54 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree, linux-input, linux-hwmon, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hello Jeff,

On Mon, Oct 21, 2019 at 11:36:49PM -0500, Jeff LaBundy wrote:
> On Mon, Oct 21, 2019 at 09:34:19AM +0200, Uwe Kleine-König wrote:
> > > +struct iqs620_pwm_private {
> > > +	struct iqs62x_core *iqs62x;
> > > +	struct pwm_chip chip;
> > > +	struct notifier_block notifier;
> > > +	bool ready;
> > 
> > This is always true, so you can drop it.
> > 
> 
> This is here because iqs620_pwm_notifier references chip.pwms, which is
> not allocated until after the notifier is registered and pwmchip_add is
> called. So it protects against this (albeit unlikely) race condition:
> 
> 1. iqs620_pwm_notifier is registered
> 2. Device immediately suffers an asynchronous reset and notifier chain
>    is called (more on that in a bit)
> 3. iqs620_pwm_notifier evaluates chips.pwms (NULL)
> 
> I felt this was simpler than calling pwmchip_add before registering the
> notifier and adding an error/tear-down path in iqs620_pwm_probe in case
> of failure. I would be happy to add a comment or two to explain the not-
> so-obvious purpose of this flag.

Ah, understood. A comment is definitively necessary here.

> > > +};
> > > +
> > > +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > +			    struct pwm_state *state)
> > 
> > Since
> > 
> > 	71523d1812ac ("pwm: Ensure pwm_apply_state() doesn't modify the state argument")
> > 
> > this isn't the right prototype.
> > 
> 
> Sure thing; I will add the 'const' qualifier and remove the two changes
> to the state argument.
> 
> > > +{
> > > +	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);

Another problem that we have here is that the period is fixed to 1 ms
and if a consumer requests for example:

	.period = 5000000,
	.duty_cycle = 1000000,

the hardware is actually configured for

	.period = 1000000,
	.duty_cycle = 1000000,

. I don't have a good suggestion how to fix this. We'd need to
draw a line somewhere and decline a request that is too far from the
result. But where this line should be is not obvious, it should
definitively not be implemented in the driver itself IMHO.

(The only halfway sane approach would be to let lowlevel drivers
implement a .round_state callback and then let the framework judge. But
we're a long way from having that, so that's not a solution for today.)

> > > +	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;
> > 
> > This suggests that if the value in the IQS620_PWM_DUTY_CYCLE is 0 the
> > duty cycle is 1/256 ms with a period of 1 ms and the output cannot be
> > constant inactive. If this is right please add a paragraph in the
> > driver's comment at the top:
> > 
> > 	* Limitations:
> > 	* - The hardware cannot generate a 0% duty cycle
> > 
> > (Please stick to this format, other drivers use it, too.)
> 
> That's correct; the lowest duty cycle that can be achieved using only the
> IQS620_PWM_DUTY_CYCLE register is 0.4%. We can, however, generate 0% duty
> cycle by disabling the output altogether using a separate register. Would
> that be better than flat-out saying it's impossible?

There is (maybe) a small difference between disabled and 0% duty cycle,
at least from the framework's POV: If you do:

	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
	pwm_apply_state(pwm, { .enabled = false, .period = $DC, .duty_cycle = $DC, });
	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });

and compare it to the expected result of

	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 0, });
	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });

the difference is that the duration of the inactive phase in the latter
case is a multiple of 1 ms.

There is no policy for lowlevel drivers what to do, but disabling when
0% is requested is at least not unseen and probably more what consumers
expect.

> > How does the hardware behave on changes? For example you're first
> > committing the duty cycle and then on/off. Can it happen that between
> > 
> > 	pwm_apply_state(pwm, { .duty_cycle = 3900, .period = 1000000, .enabled = true)
> > 	...
> > 	pwm_apply_state(pwm, { .duty_cycle = 1000000, .period = 1000000, .enabled = false)
> > 
> > the output is active for longer than 4 µs because the iqs620_pwm_apply
> > function is preempted between the two register writes and so we already
> > have .duty_cycle = 1000000 but still .enabled = true in the hardware?
> > 
> 
> My results show that it is possible to generate up to two irregular periods
> by changing the duty cycle while the output is active.
> 
> Depending on the ratio of old-to-new duty cycle and the position of the I2C
> write relative to the asynchronous output, the device may produce one pulse
> for which the width represents neither the old nor the new duty cycle.
> 
> > Does a change complete the currently running period? Does disabling
> > complete the currently running period? If so, does regmap_update_bits
> > block until the new setting is active?
> > 
> 
> A quick test reveals the following:
> 
> * Duty cycle changes may interrupt a running period, i.e., the output may
>   transition in the middle of the period to accommodate the new duty cycle.
> * Disabling the output drives it to zero immediately, i.e., the period does
>   does not run to completion.
> 
> I will add a 'Limitations' section at the top as other drivers do, and call
> these points out specifically.

Great. Thanks.

> > > +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;
> > 
> > So the PWM can loose it's state sometimes? When does that happen?
> 
> That's correct. The device performs an internal soft reset in the presence
> of what it considers to be an I2C timeout error; in this case all registers
> are restored to their default values.

Is this a theoretic problem or does that happen from time to time?
 
> The data sheet goes so far as to recommend monitoring for this interrupt and
> restoring the device on-the-fly. I have added some comments in iqs62x_irq in
> patch [2/8] which provides some further detail.

Monitoring that interrupt seems reasonable.
 
> > > +	error = devm_add_action_or_reset(&pdev->dev,
> > > +					 iqs620_pwm_notifier_unregister,
> > > +					 iqs620_pwm);
> > 
> > I wonder if this is safe. If in iqs620_pwm_notifier_unregister()
> > unregistering of the notifier goes wrong (not sure when this can happen)
> > the memory behind iqs620_pwm goes away. Then later iqs620_pwm_notifier
> > might be called trying to use *iqs620_pwm ...
> 
> I think this is purely theoretical, as blocking_notifier_chain_unregister
> only fails if the notifier is not found in the chain. If for some reason
> blocking_notifier_chain_register fails (which currently cannot happen, as
> it always returns zero), the driver will fail to probe before the action
> could be added.
> 
> This of course means the error message in iqs620_pwm_notifier_unregister
> is unnecessary; it is simply provided for debug/visibility.

I'd suggest to do the unregister call in the remove callback which you
have for pwm unregistration anyhow. Or alternatively implement a devm_
variant of the notifier registration that explains in the comments that
it is safe.

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings
  2019-10-21  4:11 ` [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings Jeff LaBundy
@ 2019-10-22 11:00   ` Jonathan Cameron
  2019-10-23  3:36     ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Jonathan Cameron @ 2019-10-22 11:00 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Sun, 20 Oct 2019 23:11:16 -0500
Jeff LaBundy <jeff@labundy.com> wrote:

> This patch adds binding documentation for six-channel members of the
> Azoteq ProxFusion family of sensor devices.
> 
> Signed-off-by: Jeff LaBundy <jeff@labundy.com>

I'm not sure if Lee has made the switch for mfd entirely yet, but
mostly new dt bindings need to be in yaml format as it allows
automated parsing of the examples + bindings using them for
correctness.

One comment inline.  I'm far from an expert on most of the stuff here
so will leave it for others!

Jonathan


> ---
>  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.
This one is certainly interesting.  Does it always make sense to
set this only at boot?  Of could we control this from userspace?

It sits somewhere between a hardware requirement that we should
put in DT and a policy decision.  I can conceive of devices where both
options make sense, but also ones where only one does.

> +
> +[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 = <KEY_SELECT>,
> +						 <KEY_MENU>,
> +						 <KEY_OK>,
> +						 <KEY_MENU>;
> +
> +				hall_switch_south {
> +					linux,code = <SW_LID>;
> +					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>,
> +						 <KEY_MUTE>;
> +
> +				hall_switch_north {
> +					linux,code = <SW_DOCK>;
> +				};
> +
> +				hall_switch_south {
> +					linux,code = <SW_TABLET_MODE>;
> +				};
> +			};
> +		};
> +
> +		/* ... */
> +	};
> +
> +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 = <BTN_0>,
> +						 <0>,
> +						 <BTN_1>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <0>,
> +						 <KEY_VOLUMEUP>,
> +						 <KEY_VOLUMEDOWN>;
> +			};
> +		};
> +
> +		/* ... */
> +	};


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 7/8] iio: proximity: Add support for Azoteq IQS622 proximity sensor
  2019-10-21  4:11 ` [PATCH 7/8] iio: proximity: Add support for Azoteq IQS622 proximity sensor Jeff LaBundy
@ 2019-10-22 11:23   ` Jonathan Cameron
  2019-10-23  3:09     ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Jonathan Cameron @ 2019-10-22 11:23 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Sun, 20 Oct 2019 23:11:22 -0500
Jeff LaBundy <jeff@labundy.com> wrote:

> 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 <jeff@labundy.com>
A few trivial bits inline + that question on the dt binding and
whether it is something we ought to be deciding at device build
time or whether there are devices where it should be configurable.

Thanks,

Jonathan

> ---
>  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 <jeff@labundy.com>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/iqs62x.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +
> +#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);

The ternary operators in here are bit messy, perhaps better to just
have an if else block and some local variables?

> +
> +	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");

Outstanding question on this in the binding patch.

> +
> +	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) {

As in previous, feels a little verbose.

> +		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 <jeff@labundy.com>");
> +MODULE_DESCRIPTION("Azoteq IQS622 Proximity Sensor");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" IQS622_DRV_NAME_PROX);


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 6/8] iio: light: Add support for Azoteq IQS621 ambient light sensor
  2019-10-21  4:11 ` [PATCH 6/8] iio: light: Add support for Azoteq IQS621 ambient light sensor Jeff LaBundy
@ 2019-10-22 11:23   ` Jonathan Cameron
  2019-10-23  2:59     ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Jonathan Cameron @ 2019-10-22 11:23 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Sun, 20 Oct 2019 23:11:21 -0500
Jeff LaBundy <jeff@labundy.com> wrote:

> This patch adds support for the Azoteq IQS621 ambient light sensor,
> capable of reporting intensity directly in units of lux.

If they are in lux, should be using IIO_CHAN_INFO_PROCESSED to indicate
that.  I was wondering why we had no scale then noticed this comment.

Other than that, this looks good to me.  So with that tidied up in V2.
Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

One trivial comment to perhaps drop an error print as overly verbose inline.

> 
> Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> ---
>  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 <jeff@labundy.com>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/iqs62x.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#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) {

This one can only fail if a memory allocation fails (IIRC) so it feels
a bit paranoid to worry about this little corner case when you are
fairly (correctly in my view) sparse in error message for other cases..

> +		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 <jeff@labundy.com>");
> +MODULE_DESCRIPTION("Azoteq IQS621 Ambient Light Sensor");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" IQS621_DRV_NAME_ALS);


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 8/8] iio: position: Add support for Azoteq IQS624/625 angle sensor
  2019-10-21  4:11 ` [PATCH 8/8] iio: position: Add support for Azoteq IQS624/625 angle sensor Jeff LaBundy
@ 2019-10-22 11:28   ` Jonathan Cameron
  0 siblings, 0 replies; 37+ messages in thread
From: Jonathan Cameron @ 2019-10-22 11:28 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Sun, 20 Oct 2019 23:11:23 -0500
Jeff LaBundy <jeff@labundy.com> wrote:

> 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.
Hmm. I wonder if it makes sense to just move the resolvers into this directory
and not maintain the distinction.  Guess we can do that in the future if we
feel like it! :)

This one looks good to me.

Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

I'm assuming these will all go through mfd and an immutable branch once
everyone is happy.

Thanks,

Jonathan

> 
> Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> ---
>  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 <jeff@labundy.com>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/iqs62x.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#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 <jeff@labundy.com>");
> +MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensor");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" IQS624_DRV_NAME_POS);


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor
  2019-10-22  3:22       ` Guenter Roeck
@ 2019-10-22 11:38         ` Jonathan Cameron
  2019-10-23  2:04           ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Jonathan Cameron @ 2019-10-22 11:38 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Jeff LaBundy, lee.jones, dmitry.torokhov, jdelvare,
	thierry.reding, devicetree, linux-input, linux-hwmon,
	u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw, linux-iio,
	robh+dt, mark.rutland

On Mon, 21 Oct 2019 20:22:44 -0700
Guenter Roeck <linux@roeck-us.net> wrote:

> On 10/21/19 7:26 PM, Jeff LaBundy wrote:
> > Hi Guenter,
> > 
> > Thank you for your prompt review.
> > 
> > On Mon, Oct 21, 2019 at 08:38:25AM -0700, Guenter Roeck wrote:  
> >> On Sun, Oct 20, 2019 at 11:11:19PM -0500, Jeff LaBundy wrote:  
> >>> This patch adds support for the Azoteq IQS620AT temperature sensor,
> >>> capable of reporting its absolute die temperature.
> >>>
> >>> Signed-off-by: Jeff LaBundy <jeff@labundy.com>  
> >>
> >> Seems to me this might be more feasible as iio driver.
> >> Jonathan, what do you think ?
> >>  
> > 
> > Interestingly enough, this actually started as an iio driver; however the
> > "When to Use" slide of [0] made me suspect that conventional devices with
> > the temperature sensing element integrated on the die belong in hwmon.
> > 
> > I then found the highly similar ad7314, which Jonathan himself appears to
> > have converted from iio to hwmon. Therefore, I placed this where existing
> > drivers seemed to match the most, especially since the temperature sensors
> > in iio generally use IR or a thermocouple.
> > 
> > That being said, I would be happy to move this into iio so long as Jonathan
> > does not mind, as it would limit the blast radius of this patch series.
> >   
> 
> I don't recall why the ad7314 driver was moved. With a conversion time of 40uS
> it is most definitely not a typical use case for a hwmon sensor.

I'll be honest, I can't remember either ;)
> 
> Anyway, not worth arguing about. Just don't complain later. There is an
> iio->hwmon bridge, but no hwmon->iio bridge, so the decision does have some
> impact. Specifically, userspace will have to implement both hwmon and iio
> access to handle the chip.

So I had a very quick look at one of the data sheets.  The temperature sensor
here is described as: 

"The IQS620(A) provides temperature monitoring capabilities which can be used for temperature
change detection in order to ensure the integrity of other sensing technology".

Superficially this sounds like it's probably inappropriate for any sort
of system temperature monitoring.  It's really just there to allow
for clever compensation algorithms for the other bits on this chip
(much like the temperature sensors we almost always get on a decent
IMU).

Normally we'd just tack an extra channel for the temperature sensor on
to the the the sensor it is integrated with.  This is a bit more
complex though as we have 3 different IIO sensors that are present
in particular part numbers and for some cases we have no IIO device
at all, but do have a temperature sensor.

So if people are going to actually use this to compensate outputs
(not sure which ones are actually temperature sensitive btw ;)
then if those are IIO supported devices, then probably makes sense
for this to be an IIO device.  It may make sense anyway if there
is any chance of adding temperature compensation to the drivers
in kernel.  I suspect the only use that would actually be made
is as a trip point if something has gone horribly wrong, but
I might be wrong!

Conclusion. I also don't feel strongly on this one as it kind of
sits between IIO and hwmon, but probably ever so slightly on the
IIO side as monitoring a sensor chip, not some other device.

Thanks,

Jonathan

> 
> Guenter


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 3/8] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
  2019-10-21  4:11 ` [PATCH 3/8] input: keyboard: " Jeff LaBundy
@ 2019-10-23  0:22   ` Dmitry Torokhov
  2019-10-23  1:29     ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Dmitry Torokhov @ 2019-10-23  0:22 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, jdelvare, linux, thierry.reding, jic23, devicetree,
	linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Jeff,

On Sun, Oct 20, 2019 at 11:11:18PM -0500, Jeff LaBundy wrote:
> This patch adds touch key support for six-channel members of the
> Azoteq ProxFusion family of sensor devices.
> 
> Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> ---
>  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 <jeff@labundy.com>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/input.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/iqs62x.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +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);

I do not think this has to be OF-specific, so please use
device_property_*() API.

> +	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);

Not '1'?

> +		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 <jeff@labundy.com>");
> +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Touch Keys");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" IQS62X_DRV_NAME_KEYS);
> -- 
> 2.7.4
> 

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 3/8] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
  2019-10-23  0:22   ` Dmitry Torokhov
@ 2019-10-23  1:29     ` Jeff LaBundy
  2019-10-23 23:08       ` Dmitry Torokhov
  0 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-23  1:29 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: lee.jones, jdelvare, linux, thierry.reding, jic23, devicetree,
	linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Dmitry,

Thank you for your prompt review.

On Tue, Oct 22, 2019 at 05:22:54PM -0700, Dmitry Torokhov wrote:
> Hi Jeff,
> 
> On Sun, Oct 20, 2019 at 11:11:18PM -0500, Jeff LaBundy wrote:
> > This patch adds touch key support for six-channel members of the
> > Azoteq ProxFusion family of sensor devices.
> > 
> > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > ---
> >  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 <jeff@labundy.com>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/input.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/iqs62x.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +
> > +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);
> 
> I do not think this has to be OF-specific, so please use
> device_property_*() API.
> 

Sure thing; will do.

> > +	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);
> 
> Not '1'?
> 

We pick up the '1' in the first of the two for loops above so long as the wheel
moved "enough." In this case (and this case only), a subsequent '0' is sent to
emulate a full press/release cycle (2 * {EV_KEY + EV_SYN}) for wheel "ticks."

I will update the comment to say "...in which case a complementary release cycle
is emulated." If I have misunderstood your concern, please let me know.

> > +		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 <jeff@labundy.com>");
> > +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Touch Keys");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" IQS62X_DRV_NAME_KEYS);
> > -- 
> > 2.7.4
> > 
> 
> Thanks.
> 
> -- 
> Dmitry
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor
  2019-10-22 11:38         ` Jonathan Cameron
@ 2019-10-23  2:04           ` Jeff LaBundy
  0 siblings, 0 replies; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-23  2:04 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Guenter Roeck, lee.jones, dmitry.torokhov, jdelvare,
	thierry.reding, devicetree, linux-input, linux-hwmon,
	u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw, linux-iio,
	robh+dt, mark.rutland

Hi Jonathan and Guenter,

On Tue, Oct 22, 2019 at 12:38:38PM +0100, Jonathan Cameron wrote:
> On Mon, 21 Oct 2019 20:22:44 -0700
> Guenter Roeck <linux@roeck-us.net> wrote:
> 
> > On 10/21/19 7:26 PM, Jeff LaBundy wrote:
> > > Hi Guenter,
> > > 
> > > Thank you for your prompt review.
> > > 
> > > On Mon, Oct 21, 2019 at 08:38:25AM -0700, Guenter Roeck wrote:  
> > >> On Sun, Oct 20, 2019 at 11:11:19PM -0500, Jeff LaBundy wrote:  
> > >>> This patch adds support for the Azoteq IQS620AT temperature sensor,
> > >>> capable of reporting its absolute die temperature.
> > >>>
> > >>> Signed-off-by: Jeff LaBundy <jeff@labundy.com>  
> > >>
> > >> Seems to me this might be more feasible as iio driver.
> > >> Jonathan, what do you think ?
> > >>  
> > > 
> > > Interestingly enough, this actually started as an iio driver; however the
> > > "When to Use" slide of [0] made me suspect that conventional devices with
> > > the temperature sensing element integrated on the die belong in hwmon.
> > > 
> > > I then found the highly similar ad7314, which Jonathan himself appears to
> > > have converted from iio to hwmon. Therefore, I placed this where existing
> > > drivers seemed to match the most, especially since the temperature sensors
> > > in iio generally use IR or a thermocouple.
> > > 
> > > That being said, I would be happy to move this into iio so long as Jonathan
> > > does not mind, as it would limit the blast radius of this patch series.
> > >   
> > 
> > I don't recall why the ad7314 driver was moved. With a conversion time of 40uS
> > it is most definitely not a typical use case for a hwmon sensor.
> 
> I'll be honest, I can't remember either ;)
> > 
> > Anyway, not worth arguing about. Just don't complain later. There is an
> > iio->hwmon bridge, but no hwmon->iio bridge, so the decision does have some
> > impact. Specifically, userspace will have to implement both hwmon and iio
> > access to handle the chip.
> 
> So I had a very quick look at one of the data sheets.  The temperature sensor
> here is described as: 
> 
> "The IQS620(A) provides temperature monitoring capabilities which can be used for temperature
> change detection in order to ensure the integrity of other sensing technology".
> 
> Superficially this sounds like it's probably inappropriate for any sort
> of system temperature monitoring.  It's really just there to allow
> for clever compensation algorithms for the other bits on this chip
> (much like the temperature sensors we almost always get on a decent
> IMU).
> 

Correct on all counts. The "charge transfer" sensing mechanism employed by these
devices is sensitive to temperature, and they employ a compensation algorithm to
account for drift.

Of the five devices in the series, the IQS620A and IQS621 expose the output of
the temperature monitoring network to the outside world. However, the values are
purely relative. The IQS620AT, however, is calibrated at the factory such that
it can apply an internal scaling factor and offset in order to provide absolute
measurements.

The MFD driver checks these calibration values to determine if this driver can
be loaded, or if the device is a plain IQS620A (no 'T') in which case only the
input and PWM drivers are loaded.

> Normally we'd just tack an extra channel for the temperature sensor on
> to the the the sensor it is integrated with.  This is a bit more
> complex though as we have 3 different IIO sensors that are present
> in particular part numbers and for some cases we have no IIO device
> at all, but do have a temperature sensor.
> 
> So if people are going to actually use this to compensate outputs
> (not sure which ones are actually temperature sensitive btw ;)
> then if those are IIO supported devices, then probably makes sense
> for this to be an IIO device.  It may make sense anyway if there
> is any chance of adding temperature compensation to the drivers
> in kernel.  I suspect the only use that would actually be made
> is as a trip point if something has gone horribly wrong, but
> I might be wrong!
> 

Correct again; in my opinion this device is unlikely to be chosen for any sort
of system-level temperature monitoring. It's really meant for contactless key/
button/switch sensing and PWM control; a subset of the devices simply offer the
internal temperature measurement to the outside world as a bonus in case it is
useful. Hence, this patch.

> Conclusion. I also don't feel strongly on this one as it kind of
> sits between IIO and hwmon, but probably ever so slightly on the
> IIO side as monitoring a sensor chip, not some other device.
> 

Agreed on all counts; I'll move this to iio. Thank you both for the discussion.

> Thanks,
> 
> Jonathan
> 
> > 
> > Guenter
> 
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator
  2019-10-22  6:54       ` Uwe Kleine-König
@ 2019-10-23  2:45         ` Jeff LaBundy
  2019-10-23  7:23           ` Uwe Kleine-König
  0 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-23  2:45 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree, linux-input, linux-hwmon, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Uwe,

On Tue, Oct 22, 2019 at 08:54:15AM +0200, Uwe Kleine-König wrote:
> Hello Jeff,
> 
> On Mon, Oct 21, 2019 at 11:36:49PM -0500, Jeff LaBundy wrote:
> > On Mon, Oct 21, 2019 at 09:34:19AM +0200, Uwe Kleine-König wrote:
> > > > +struct iqs620_pwm_private {
> > > > +	struct iqs62x_core *iqs62x;
> > > > +	struct pwm_chip chip;
> > > > +	struct notifier_block notifier;
> > > > +	bool ready;
> > > 
> > > This is always true, so you can drop it.
> > > 
> > 
> > This is here because iqs620_pwm_notifier references chip.pwms, which is
> > not allocated until after the notifier is registered and pwmchip_add is
> > called. So it protects against this (albeit unlikely) race condition:
> > 
> > 1. iqs620_pwm_notifier is registered
> > 2. Device immediately suffers an asynchronous reset and notifier chain
> >    is called (more on that in a bit)
> > 3. iqs620_pwm_notifier evaluates chips.pwms (NULL)
> > 
> > I felt this was simpler than calling pwmchip_add before registering the
> > notifier and adding an error/tear-down path in iqs620_pwm_probe in case
> > of failure. I would be happy to add a comment or two to explain the not-
> > so-obvious purpose of this flag.
> 
> Ah, understood. A comment is definitively necessary here.
> 

Sure thing; will do.

> > > > +};
> > > > +
> > > > +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > > +			    struct pwm_state *state)
> > > 
> > > Since
> > > 
> > > 	71523d1812ac ("pwm: Ensure pwm_apply_state() doesn't modify the state argument")
> > > 
> > > this isn't the right prototype.
> > > 
> > 
> > Sure thing; I will add the 'const' qualifier and remove the two changes
> > to the state argument.
> > 
> > > > +{
> > > > +	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);
> 
> Another problem that we have here is that the period is fixed to 1 ms
> and if a consumer requests for example:
> 
> 	.period = 5000000,
> 	.duty_cycle = 1000000,
> 
> the hardware is actually configured for
> 
> 	.period = 1000000,
> 	.duty_cycle = 1000000,
> 
> . I don't have a good suggestion how to fix this. We'd need to
> draw a line somewhere and decline a request that is too far from the
> result. But where this line should be is not obvious, it should
> definitively not be implemented in the driver itself IMHO.
> 
> (The only halfway sane approach would be to let lowlevel drivers
> implement a .round_state callback and then let the framework judge. But
> we're a long way from having that, so that's not a solution for today.)
> 

Agreed on all counts. For now, I will mention in the 'Limitations' heading that
the period cannot be adjusted.

> > > > +	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;
> > > 
> > > This suggests that if the value in the IQS620_PWM_DUTY_CYCLE is 0 the
> > > duty cycle is 1/256 ms with a period of 1 ms and the output cannot be
> > > constant inactive. If this is right please add a paragraph in the
> > > driver's comment at the top:
> > > 
> > > 	* Limitations:
> > > 	* - The hardware cannot generate a 0% duty cycle
> > > 
> > > (Please stick to this format, other drivers use it, too.)
> > 
> > That's correct; the lowest duty cycle that can be achieved using only the
> > IQS620_PWM_DUTY_CYCLE register is 0.4%. We can, however, generate 0% duty
> > cycle by disabling the output altogether using a separate register. Would
> > that be better than flat-out saying it's impossible?
> 
> There is (maybe) a small difference between disabled and 0% duty cycle,
> at least from the framework's POV: If you do:
> 
> 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> 	pwm_apply_state(pwm, { .enabled = false, .period = $DC, .duty_cycle = $DC, });
> 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> 
> and compare it to the expected result of
> 
> 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 0, });
> 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> 
> the difference is that the duration of the inactive phase in the latter
> case is a multiple of 1 ms.
> 
> There is no policy for lowlevel drivers what to do, but disabling when
> 0% is requested is at least not unseen and probably more what consumers
> expect.
> 

With the change I am proposing, the output will be driven to zero if enabled = false
OR duty_cycle < 4000 ns. Stated another way:

enable duty_cycle IQS620_PWR_SETTINGS[7] IQS620_PWM_DUTY_CYCLE
------ ---------- ---------------------- ---------------------
  0    don't care           0                  don't care
  1    0 ... 3999           0                  don't care
  1    4000 ... x           1                      0
  1    x+1  ... y           1                      1

...and so on. For context, if IQS620_PWR_SETTINGS[7] = 0 then the output is held to
zero. If IQS620_PWR_SETTINGS[7] = 1 then the output toggles at a duty cycle between
0.4% and 100% as a function of IQS620_PWM_DUTY_CYCLE.

Based on how the device behaves in response to its two available registers, I think
your two examples will appear equal, but please let me know if I have understood.

> > > How does the hardware behave on changes? For example you're first
> > > committing the duty cycle and then on/off. Can it happen that between
> > > 
> > > 	pwm_apply_state(pwm, { .duty_cycle = 3900, .period = 1000000, .enabled = true)
> > > 	...
> > > 	pwm_apply_state(pwm, { .duty_cycle = 1000000, .period = 1000000, .enabled = false)
> > > 
> > > the output is active for longer than 4 µs because the iqs620_pwm_apply
> > > function is preempted between the two register writes and so we already
> > > have .duty_cycle = 1000000 but still .enabled = true in the hardware?
> > > 
> > 
> > My results show that it is possible to generate up to two irregular periods
> > by changing the duty cycle while the output is active.
> > 
> > Depending on the ratio of old-to-new duty cycle and the position of the I2C
> > write relative to the asynchronous output, the device may produce one pulse
> > for which the width represents neither the old nor the new duty cycle.
> > 
> > > Does a change complete the currently running period? Does disabling
> > > complete the currently running period? If so, does regmap_update_bits
> > > block until the new setting is active?
> > > 
> > 
> > A quick test reveals the following:
> > 
> > * Duty cycle changes may interrupt a running period, i.e., the output may
> >   transition in the middle of the period to accommodate the new duty cycle.
> > * Disabling the output drives it to zero immediately, i.e., the period does
> >   does not run to completion.
> > 
> > I will add a 'Limitations' section at the top as other drivers do, and call
> > these points out specifically.
> 
> Great. Thanks.
> 
> > > > +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;
> > > 
> > > So the PWM can loose it's state sometimes? When does that happen?
> > 
> > That's correct. The device performs an internal soft reset in the presence
> > of what it considers to be an I2C timeout error; in this case all registers
> > are restored to their default values.
> 
> Is this a theoretic problem or does that happen from time to time?
>  

This event can occur if the I2C master stalls a transaction for 10's of ms. It's
not a theoretical problem, but it should not happen during normal circumstances.

> > The data sheet goes so far as to recommend monitoring for this interrupt and
> > restoring the device on-the-fly. I have added some comments in iqs62x_irq in
> > patch [2/8] which provides some further detail.
> 
> Monitoring that interrupt seems reasonable.
>  
> > > > +	error = devm_add_action_or_reset(&pdev->dev,
> > > > +					 iqs620_pwm_notifier_unregister,
> > > > +					 iqs620_pwm);
> > > 
> > > I wonder if this is safe. If in iqs620_pwm_notifier_unregister()
> > > unregistering of the notifier goes wrong (not sure when this can happen)
> > > the memory behind iqs620_pwm goes away. Then later iqs620_pwm_notifier
> > > might be called trying to use *iqs620_pwm ...
> > 
> > I think this is purely theoretical, as blocking_notifier_chain_unregister
> > only fails if the notifier is not found in the chain. If for some reason
> > blocking_notifier_chain_register fails (which currently cannot happen, as
> > it always returns zero), the driver will fail to probe before the action
> > could be added.
> > 
> > This of course means the error message in iqs620_pwm_notifier_unregister
> > is unnecessary; it is simply provided for debug/visibility.
> 
> I'd suggest to do the unregister call in the remove callback which you
> have for pwm unregistration anyhow. Or alternatively implement a devm_
> variant of the notifier registration that explains in the comments that
> it is safe.

Sure thing; I'll unregister the notifier in iqs620_pwm_remove.

> 
> Best regards
> Uwe
> 
> -- 
> Pengutronix e.K.                           | Uwe Kleine-König            |
> Industrial Linux Solutions                 | http://www.pengutronix.de/  |
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 6/8] iio: light: Add support for Azoteq IQS621 ambient light sensor
  2019-10-22 11:23   ` Jonathan Cameron
@ 2019-10-23  2:59     ` Jeff LaBundy
  0 siblings, 0 replies; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-23  2:59 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Jonathan,

Thank you for your prompt review.

On Tue, Oct 22, 2019 at 12:23:49PM +0100, Jonathan Cameron wrote:
> On Sun, 20 Oct 2019 23:11:21 -0500
> Jeff LaBundy <jeff@labundy.com> wrote:
> 
> > This patch adds support for the Azoteq IQS621 ambient light sensor,
> > capable of reporting intensity directly in units of lux.
> 
> If they are in lux, should be using IIO_CHAN_INFO_PROCESSED to indicate
> that.  I was wondering why we had no scale then noticed this comment.
> 

Sure thing; will do.

> Other than that, this looks good to me.  So with that tidied up in V2.
> Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> 
> One trivial comment to perhaps drop an error print as overly verbose inline.
> 
> > 
> > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > ---
> >  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 <jeff@labundy.com>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/iio/events.h>
> > +#include <linux/iio/iio.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/iqs62x.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/regmap.h>
> > +
> > +#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) {
> 
> This one can only fail if a memory allocation fails (IIRC) so it feels
> a bit paranoid to worry about this little corner case when you are
> fairly (correctly in my view) sparse in error message for other cases..
> 

Sure thing; I'll drop the error message.

> > +		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 <jeff@labundy.com>");
> > +MODULE_DESCRIPTION("Azoteq IQS621 Ambient Light Sensor");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" IQS621_DRV_NAME_ALS);
> 
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 7/8] iio: proximity: Add support for Azoteq IQS622 proximity sensor
  2019-10-22 11:23   ` Jonathan Cameron
@ 2019-10-23  3:09     ` Jeff LaBundy
  0 siblings, 0 replies; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-23  3:09 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Jonathan,

Thanks again for your prompt review here and the rest of the series.

On Tue, Oct 22, 2019 at 12:23:46PM +0100, Jonathan Cameron wrote:
> On Sun, 20 Oct 2019 23:11:22 -0500
> Jeff LaBundy <jeff@labundy.com> wrote:
> 
> > 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 <jeff@labundy.com>
> A few trivial bits inline + that question on the dt binding and
> whether it is something we ought to be deciding at device build
> time or whether there are devices where it should be configurable.
> 
> Thanks,
> 
> Jonathan
> 
> > ---
> >  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 <jeff@labundy.com>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/iio/events.h>
> > +#include <linux/iio/iio.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/iqs62x.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/property.h>
> > +#include <linux/regmap.h>
> > +
> > +#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);
> 
> The ternary operators in here are bit messy, perhaps better to just
> have an if else block and some local variables?
> 

Sure thing; will do.

> > +
> > +	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");
> 
> Outstanding question on this in the binding patch.
> 

After thinking about this some more, I think the control in question needs to be
configurable from user space at runtime. I'll provide more detail in the binding
patch [1/8].

> > +
> > +	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) {
> 
> As in previous, feels a little verbose.
> 

Sure thing; I'll drop the error message.

> > +		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 <jeff@labundy.com>");
> > +MODULE_DESCRIPTION("Azoteq IQS622 Proximity Sensor");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" IQS622_DRV_NAME_PROX);
> 
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings
  2019-10-22 11:00   ` Jonathan Cameron
@ 2019-10-23  3:36     ` Jeff LaBundy
  2019-10-23  9:30       ` Lee Jones
  0 siblings, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-23  3:36 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Jonathan,

On Tue, Oct 22, 2019 at 12:00:51PM +0100, Jonathan Cameron wrote:
> On Sun, 20 Oct 2019 23:11:16 -0500
> Jeff LaBundy <jeff@labundy.com> wrote:
> 
> > This patch adds binding documentation for six-channel members of the
> > Azoteq ProxFusion family of sensor devices.
> > 
> > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> 
> I'm not sure if Lee has made the switch for mfd entirely yet, but
> mostly new dt bindings need to be in yaml format as it allows
> automated parsing of the examples + bindings using them for
> correctness.
> 

I'll wait for Lee or Rob's cue, but I'm happy to move to yaml if it's time
to make the switch here.

> One comment inline.  I'm far from an expert on most of the stuff here
> so will leave it for others!
> 
> Jonathan
> 
> 
> > ---
> >  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.
> This one is certainly interesting.  Does it always make sense to
> set this only at boot?  Of could we control this from userspace?
> 
> It sits somewhere between a hardware requirement that we should
> put in DT and a policy decision.  I can conceive of devices where both
> options make sense, but also ones where only one does.
> 

I've given this some thought, and I think this needs to move to a runtime control
that can be adjusted from user space.

I originally made this a binding simply because a similar decision is offered for
Hall-effect switches in the input driver (lid switch, dock switch, etc.). In that
case, the decision is based on industrial design (distance from magnet, enclosure
thickness, etc.). so that one makes sense as a binding.

IR proximity reporting represents a different use case (in-ear detection, etc.) and
I can forsee instances where narrow vs. wide range decision needs to be dynamically
controlled.

Most importantly, the corresponding iio driver already allows the selected detection
threshold to be adjusted from user space. So it only makes sense that the particular
threshold to be used can be selected from user space as well.

I'll drop the binding and add a "touch" vs. "proximity" attribute to the iio driver.

> > +
> > +[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 = <KEY_SELECT>,
> > +						 <KEY_MENU>,
> > +						 <KEY_OK>,
> > +						 <KEY_MENU>;
> > +
> > +				hall_switch_south {
> > +					linux,code = <SW_LID>;
> > +					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>,
> > +						 <KEY_MUTE>;
> > +
> > +				hall_switch_north {
> > +					linux,code = <SW_DOCK>;
> > +				};
> > +
> > +				hall_switch_south {
> > +					linux,code = <SW_TABLET_MODE>;
> > +				};
> > +			};
> > +		};
> > +
> > +		/* ... */
> > +	};
> > +
> > +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 = <BTN_0>,
> > +						 <0>,
> > +						 <BTN_1>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <0>,
> > +						 <KEY_VOLUMEUP>,
> > +						 <KEY_VOLUMEDOWN>;
> > +			};
> > +		};
> > +
> > +		/* ... */
> > +	};
> 
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator
  2019-10-23  2:45         ` Jeff LaBundy
@ 2019-10-23  7:23           ` Uwe Kleine-König
  2019-10-24  3:02             ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Uwe Kleine-König @ 2019-10-23  7:23 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree, linux-input, linux-hwmon, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hello Jeff,

On Tue, Oct 22, 2019 at 09:45:25PM -0500, Jeff LaBundy wrote:
> On Tue, Oct 22, 2019 at 08:54:15AM +0200, Uwe Kleine-König wrote:
> > On Mon, Oct 21, 2019 at 11:36:49PM -0500, Jeff LaBundy wrote:
> > > On Mon, Oct 21, 2019 at 09:34:19AM +0200, Uwe Kleine-König wrote:
> > > > > +{
> > > > > +	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);
> > 
> > Another problem that we have here is that the period is fixed to 1 ms
> > and if a consumer requests for example:
> > 
> > 	.period = 5000000,
> > 	.duty_cycle = 1000000,
> > 
> > the hardware is actually configured for
> > 
> > 	.period = 1000000,
> > 	.duty_cycle = 1000000,
> > 
> > . I don't have a good suggestion how to fix this. We'd need to
> > draw a line somewhere and decline a request that is too far from the
> > result. But where this line should be is not obvious, it should
> > definitively not be implemented in the driver itself IMHO.
> > 
> > (The only halfway sane approach would be to let lowlevel drivers
> > implement a .round_state callback and then let the framework judge. But
> > we're a long way from having that, so that's not a solution for today.)
> > 
> 
> Agreed on all counts. For now, I will mention in the 'Limitations' heading that
> the period cannot be adjusted.

Ack. My longterm plan is to require .apply_state() to round down both
.period and .duty_cycle. This isn't wrong already today, so I suggest
you decline a request to set the period to something smaller than 1 ms
with an error code. (I think most drivers use -EINVAL here, conceptually
-EDOM might be sensible. I'd stick to EINVAL for now.)

> > > > > +	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;
> > > > 
> > > > This suggests that if the value in the IQS620_PWM_DUTY_CYCLE is 0 the
> > > > duty cycle is 1/256 ms with a period of 1 ms and the output cannot be
> > > > constant inactive. If this is right please add a paragraph in the
> > > > driver's comment at the top:
> > > > 
> > > > 	* Limitations:
> > > > 	* - The hardware cannot generate a 0% duty cycle
> > > > 
> > > > (Please stick to this format, other drivers use it, too.)
> > > 
> > > That's correct; the lowest duty cycle that can be achieved using only the
> > > IQS620_PWM_DUTY_CYCLE register is 0.4%. We can, however, generate 0% duty
> > > cycle by disabling the output altogether using a separate register. Would
> > > that be better than flat-out saying it's impossible?
> > 
> > There is (maybe) a small difference between disabled and 0% duty cycle,
> > at least from the framework's POV: If you do:
> > 
> > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> > 	pwm_apply_state(pwm, { .enabled = false, .period = $DC, .duty_cycle = $DC, });
> > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> > 
> > and compare it to the expected result of
> > 
> > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 0, });
> > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> > 
> > the difference is that the duration of the inactive phase in the latter
> > case is a multiple of 1 ms.
> > 
> > There is no policy for lowlevel drivers what to do, but disabling when
> > 0% is requested is at least not unseen and probably more what consumers
> > expect.
> > 
> 
> With the change I am proposing, the output will be driven to zero if enabled = false
> OR duty_cycle < 4000 ns. Stated another way:
> 
> enable duty_cycle IQS620_PWR_SETTINGS[7] IQS620_PWM_DUTY_CYCLE
> ------ ---------- ---------------------- ---------------------
>   0    don't care           0                  don't care
>   1    0 ... 3999           0                  don't care
>   1    4000 ... x           1                      0
>   1    x+1  ... y           1                      1
> 
> ...and so on. For context, if IQS620_PWR_SETTINGS[7] = 0 then the output is held to
> zero. If IQS620_PWR_SETTINGS[7] = 1 then the output toggles at a duty cycle between
> 0.4% and 100% as a function of IQS620_PWM_DUTY_CYCLE.

Your table isn't accurate. IQS620_PWM_DUTY_CYCLE=0 results in a
duty_cycle of 3906.25 ns so the table should look as follows:

enable  duty_cycle  IQS620_PWR_SETTINGS[7] IQS620_PWM_DUTY_CYCLE
------ ------------ ---------------------- ---------------------
  0     don't care           0                  don't care
  1       [0, 3906]          0                  don't care
  1    [3907, 7812]          1                      0
  1    [7813,11718]          1                      1

In general:

	dc = state->duty_cycle * 256 / 1000000
	if state->enabled == false or dc == 0:
	    IQS620_PWR_SETTINGS[7] = 0

	else:
	    IQS620_PWM_DUTY_CYCLE = min(dc - 1, 0xff)
	    IQS620_PWR_SETTINGS[7] = 1

> Based on how the device behaves in response to its two available
> registers, I think your two examples will appear equal, but please let
> me know if I have understood.

Yeah, that's the expectation.

With the rounding as I suggested above this yields strange effects like
if

	.period = 1 s, .duty_cycle = 0.5 s

is requested you end up in

	.period = 1 ms, .duty_cycle = 1 ms

but I think there is nothing we can reasonably do about this.

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings
  2019-10-23  3:36     ` Jeff LaBundy
@ 2019-10-23  9:30       ` Lee Jones
  2019-10-24  2:38         ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Lee Jones @ 2019-10-23  9:30 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: Jonathan Cameron, dmitry.torokhov, jdelvare, linux,
	thierry.reding, devicetree, linux-input, linux-hwmon,
	u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw, linux-iio,
	robh+dt, mark.rutland

On Tue, 22 Oct 2019, Jeff LaBundy wrote:

> Hi Jonathan,
> 
> On Tue, Oct 22, 2019 at 12:00:51PM +0100, Jonathan Cameron wrote:
> > On Sun, 20 Oct 2019 23:11:16 -0500
> > Jeff LaBundy <jeff@labundy.com> wrote:
> > 
> > > This patch adds binding documentation for six-channel members of the
> > > Azoteq ProxFusion family of sensor devices.
> > > 
> > > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > 
> > I'm not sure if Lee has made the switch for mfd entirely yet, but
> > mostly new dt bindings need to be in yaml format as it allows
> > automated parsing of the examples + bindings using them for
> > correctness.
> > 
> 
> I'll wait for Lee or Rob's cue, but I'm happy to move to yaml if it's time
> to make the switch here.
> 
> > One comment inline.  I'm far from an expert on most of the stuff here
> > so will leave it for others!

It would make sense.

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 3/8] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
  2019-10-23  1:29     ` Jeff LaBundy
@ 2019-10-23 23:08       ` Dmitry Torokhov
  0 siblings, 0 replies; 37+ messages in thread
From: Dmitry Torokhov @ 2019-10-23 23:08 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, jdelvare, linux, thierry.reding, jic23, devicetree,
	linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Tue, Oct 22, 2019 at 08:29:13PM -0500, Jeff LaBundy wrote:
> Hi Dmitry,
> 
> Thank you for your prompt review.
> 
> On Tue, Oct 22, 2019 at 05:22:54PM -0700, Dmitry Torokhov wrote:
> > Hi Jeff,
> > 
> > On Sun, Oct 20, 2019 at 11:11:18PM -0500, Jeff LaBundy wrote:
> > > +	/*
> > > +	 * 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);
> > 
> > Not '1'?
> > 
> 
> We pick up the '1' in the first of the two for loops above so long as the wheel
> moved "enough." In this case (and this case only), a subsequent '0' is sent to
> emulate a full press/release cycle (2 * {EV_KEY + EV_SYN}) for wheel "ticks."
> 
> I will update the comment to say "...in which case a complementary release cycle
> is emulated." If I have misunderstood your concern, please let me know.

Ah, sorry, no, I misread the code.

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings
  2019-10-23  9:30       ` Lee Jones
@ 2019-10-24  2:38         ` Jeff LaBundy
  0 siblings, 0 replies; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-24  2:38 UTC (permalink / raw)
  To: Lee Jones
  Cc: Jonathan Cameron, dmitry.torokhov, jdelvare, linux,
	thierry.reding, devicetree, linux-input, linux-hwmon,
	u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw, linux-iio,
	robh+dt, mark.rutland

Hi Lee,

On Wed, Oct 23, 2019 at 10:30:58AM +0100, Lee Jones wrote:
> On Tue, 22 Oct 2019, Jeff LaBundy wrote:
> 
> > Hi Jonathan,
> > 
> > On Tue, Oct 22, 2019 at 12:00:51PM +0100, Jonathan Cameron wrote:
> > > On Sun, 20 Oct 2019 23:11:16 -0500
> > > Jeff LaBundy <jeff@labundy.com> wrote:
> > > 
> > > > This patch adds binding documentation for six-channel members of the
> > > > Azoteq ProxFusion family of sensor devices.
> > > > 
> > > > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > > 
> > > I'm not sure if Lee has made the switch for mfd entirely yet, but
> > > mostly new dt bindings need to be in yaml format as it allows
> > > automated parsing of the examples + bindings using them for
> > > correctness.
> > > 
> > 
> > I'll wait for Lee or Rob's cue, but I'm happy to move to yaml if it's time
> > to make the switch here.
> > 
> > > One comment inline.  I'm far from an expert on most of the stuff here
> > > so will leave it for others!
> 
> It would make sense.
> 

Sure thing; will do.

> -- 
> Lee Jones [李琼斯]
> Linaro Services Technical Lead
> Linaro.org │ Open source software for ARM SoCs
> Follow Linaro: Facebook | Twitter | Blog
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator
  2019-10-23  7:23           ` Uwe Kleine-König
@ 2019-10-24  3:02             ` Jeff LaBundy
  0 siblings, 0 replies; 37+ messages in thread
From: Jeff LaBundy @ 2019-10-24  3:02 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: lee.jones, dmitry.torokhov, jdelvare, linux, thierry.reding,
	jic23, devicetree, linux-input, linux-hwmon, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Uwe,

On Wed, Oct 23, 2019 at 09:23:04AM +0200, Uwe Kleine-König wrote:
> Hello Jeff,
> 
> On Tue, Oct 22, 2019 at 09:45:25PM -0500, Jeff LaBundy wrote:
> > On Tue, Oct 22, 2019 at 08:54:15AM +0200, Uwe Kleine-König wrote:
> > > On Mon, Oct 21, 2019 at 11:36:49PM -0500, Jeff LaBundy wrote:
> > > > On Mon, Oct 21, 2019 at 09:34:19AM +0200, Uwe Kleine-König wrote:
> > > > > > +{
> > > > > > +	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);
> > > 
> > > Another problem that we have here is that the period is fixed to 1 ms
> > > and if a consumer requests for example:
> > > 
> > > 	.period = 5000000,
> > > 	.duty_cycle = 1000000,
> > > 
> > > the hardware is actually configured for
> > > 
> > > 	.period = 1000000,
> > > 	.duty_cycle = 1000000,
> > > 
> > > . I don't have a good suggestion how to fix this. We'd need to
> > > draw a line somewhere and decline a request that is too far from the
> > > result. But where this line should be is not obvious, it should
> > > definitively not be implemented in the driver itself IMHO.
> > > 
> > > (The only halfway sane approach would be to let lowlevel drivers
> > > implement a .round_state callback and then let the framework judge. But
> > > we're a long way from having that, so that's not a solution for today.)
> > > 
> > 
> > Agreed on all counts. For now, I will mention in the 'Limitations' heading that
> > the period cannot be adjusted.
> 
> Ack. My longterm plan is to require .apply_state() to round down both
> .period and .duty_cycle. This isn't wrong already today, so I suggest
> you decline a request to set the period to something smaller than 1 ms
> with an error code. (I think most drivers use -EINVAL here, conceptually
> -EDOM might be sensible. I'd stick to EINVAL for now.)
> 

Sure thing; will do.

> > > > > > +	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;
> > > > > 
> > > > > This suggests that if the value in the IQS620_PWM_DUTY_CYCLE is 0 the
> > > > > duty cycle is 1/256 ms with a period of 1 ms and the output cannot be
> > > > > constant inactive. If this is right please add a paragraph in the
> > > > > driver's comment at the top:
> > > > > 
> > > > > 	* Limitations:
> > > > > 	* - The hardware cannot generate a 0% duty cycle
> > > > > 
> > > > > (Please stick to this format, other drivers use it, too.)
> > > > 
> > > > That's correct; the lowest duty cycle that can be achieved using only the
> > > > IQS620_PWM_DUTY_CYCLE register is 0.4%. We can, however, generate 0% duty
> > > > cycle by disabling the output altogether using a separate register. Would
> > > > that be better than flat-out saying it's impossible?
> > > 
> > > There is (maybe) a small difference between disabled and 0% duty cycle,
> > > at least from the framework's POV: If you do:
> > > 
> > > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> > > 	pwm_apply_state(pwm, { .enabled = false, .period = $DC, .duty_cycle = $DC, });
> > > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> > > 
> > > and compare it to the expected result of
> > > 
> > > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> > > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 0, });
> > > 	pwm_apply_state(pwm, { .enabled = true, .period = 1000000, .duty_cycle = 1000000, });
> > > 
> > > the difference is that the duration of the inactive phase in the latter
> > > case is a multiple of 1 ms.
> > > 
> > > There is no policy for lowlevel drivers what to do, but disabling when
> > > 0% is requested is at least not unseen and probably more what consumers
> > > expect.
> > > 
> > 
> > With the change I am proposing, the output will be driven to zero if enabled = false
> > OR duty_cycle < 4000 ns. Stated another way:
> > 
> > enable duty_cycle IQS620_PWR_SETTINGS[7] IQS620_PWM_DUTY_CYCLE
> > ------ ---------- ---------------------- ---------------------
> >   0    don't care           0                  don't care
> >   1    0 ... 3999           0                  don't care
> >   1    4000 ... x           1                      0
> >   1    x+1  ... y           1                      1
> > 
> > ...and so on. For context, if IQS620_PWR_SETTINGS[7] = 0 then the output is held to
> > zero. If IQS620_PWR_SETTINGS[7] = 1 then the output toggles at a duty cycle between
> > 0.4% and 100% as a function of IQS620_PWM_DUTY_CYCLE.
> 
> Your table isn't accurate. IQS620_PWM_DUTY_CYCLE=0 results in a
> duty_cycle of 3906.25 ns so the table should look as follows:
> 
> enable  duty_cycle  IQS620_PWR_SETTINGS[7] IQS620_PWM_DUTY_CYCLE
> ------ ------------ ---------------------- ---------------------
>   0     don't care           0                  don't care
>   1       [0, 3906]          0                  don't care
>   1    [3907, 7812]          1                      0
>   1    [7813,11718]          1                      1
> 
> In general:
> 
> 	dc = state->duty_cycle * 256 / 1000000
> 	if state->enabled == false or dc == 0:
> 	    IQS620_PWR_SETTINGS[7] = 0
> 
> 	else:
> 	    IQS620_PWM_DUTY_CYCLE = min(dc - 1, 0xff)
> 	    IQS620_PWR_SETTINGS[7] = 1
> 

Sure thing; will do. Thank you for catching that!

> > Based on how the device behaves in response to its two available
> > registers, I think your two examples will appear equal, but please let
> > me know if I have understood.
> 
> Yeah, that's the expectation.
> 
> With the rounding as I suggested above this yields strange effects like
> if
> 
> 	.period = 1 s, .duty_cycle = 0.5 s
> 
> is requested you end up in
> 
> 	.period = 1 ms, .duty_cycle = 1 ms
> 
> but I think there is nothing we can reasonably do about this.
> 

Acknowledged on all counts. FWIW, I expect the most common consumer of this PWM
to be leds-pwm. That is to say, I think the limitations in this case are pretty
harmless. Users will typically pin the period to 1000000 ns like the example in
patch [1/8].

> Best regards
> Uwe
> 
> -- 
> Pengutronix e.K.                           | Uwe Kleine-König            |
> Industrial Linux Solutions                 | http://www.pengutronix.de/  |
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625
  2019-10-21  4:11 ` [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
@ 2019-10-31 13:44   ` Lee Jones
  2019-10-31 18:42     ` Dmitry Torokhov
  2019-11-01  4:59     ` Jeff LaBundy
  0 siblings, 2 replies; 37+ messages in thread
From: Lee Jones @ 2019-10-31 13:44 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: dmitry.torokhov, jdelvare, linux, thierry.reding, jic23,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Sun, 20 Oct 2019, Jeff LaBundy wrote:

> 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 <jeff@labundy.com>
> ---
>  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

Needs a company or person's name.

> + * Author: Jeff LaBundy <jeff@labundy.com>
> + *
> + * 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.

This troubles me somewhat. So here we take a C header file which is
the output of a vendor supplied configuration tool, convert it to a
bespoke file format using a Python script authored by yourself, which
we masquerade as Linux firmware in order to set-up the hardware.

Is that correct?

What is preventing a very naughty person from providing their own
register map (firmware) in order to read/write unsuitable registers
from kernel context for their own gains; simply by swapping out a file
contained in userspace?

It would probably be a better idea to compile the register definitions
with the kernel/module to be safe. You can use Device Tree for
run-time configuration changes.

> + * Link to data sheets and GUIs: https://www.azoteq.com/products/proxfusion/
> + *
> + * Link to conversion tool: https://github.com/jlabundy/iqs62x-h2bin.git

This is unlikely to stand the test of time. Probably best to just list
the name of the tool in the description above.

> + */
> +
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/firmware.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/list.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <asm/unaligned.h>

[...]

> +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);

Is it safe to re-initialise the entire device in IRQ context?

> +		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 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;

Nit: It's more common to use 'ret' or 'err' - my preference is 'ret'.

> +	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;

Reads better without the else:

		if (info.prod_num != iqs62x_devs[i].prod_num)
			continue;

> +		if (info.sw_num >= iqs62x->dev_desc->sw_num)
> +			iqs62x->sw_num = info.sw_num;
> +		else
> +			continue;

Same as above.

Do you still need to be in this loop at this point?

> +		for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) {

What are you doing here? Please provide a comment.

> +			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;

Is there a reason not to break here? If the product number matched
once, can it match for a second time?

> +	}
> +
> +	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;
> +}

Please move the suspend/resume calls down to here.

> +static const struct i2c_device_id iqs62x_id[] = {
> +	{ "iqs620a", 0 },
> +	{ "iqs621", 1 },
> +	{ "iqs622", 2 },
> +	{ "iqs624", 3 },
> +	{ "iqs625", 4 },

Better to define these. In fact, do you even use the .ids?

If not, you can use the new I2C probe function and drop this table.

> +	{ }
> +};
> +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 <jeff@labundy.com>");
> +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 <jeff@labundy.com>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/iqs62x.h>
> +
> +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,
> +	},
> +};

These should be moved into the core driver.

> +static const u8 iqs620at_cal_regs[] = { 0xC2, 0xC3, 0xC4, };
> +static const u8 iqs621_cal_regs[] = { 0x82, 0x83, };

What do these mean?

Probably best of do define them, so people can read them.

[...]

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625
  2019-10-31 13:44   ` Lee Jones
@ 2019-10-31 18:42     ` Dmitry Torokhov
  2019-11-01  4:59     ` Jeff LaBundy
  1 sibling, 0 replies; 37+ messages in thread
From: Dmitry Torokhov @ 2019-10-31 18:42 UTC (permalink / raw)
  To: Lee Jones
  Cc: Jeff LaBundy, jdelvare, linux, thierry.reding, jic23, devicetree,
	linux-input, linux-hwmon, u.kleine-koenig, linux-pwm, knaack.h,
	lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Thu, Oct 31, 2019 at 01:44:10PM +0000, Lee Jones wrote:
> On Sun, 20 Oct 2019, Jeff LaBundy wrote:
> > + * Author: Jeff LaBundy <jeff@labundy.com>
> > + *
> > + * 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.
> 
> This troubles me somewhat. So here we take a C header file which is
> the output of a vendor supplied configuration tool, convert it to a
> bespoke file format using a Python script authored by yourself, which
> we masquerade as Linux firmware in order to set-up the hardware.
> 
> Is that correct?
> 
> What is preventing a very naughty person from providing their own
> register map (firmware) in order to read/write unsuitable registers
> from kernel context for their own gains; simply by swapping out a file
> contained in userspace?

How is it different from, let's say, Intel WiFi firmware that you load
into the kernel and then into the card every time you boot up your
laptop?

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625
  2019-10-31 13:44   ` Lee Jones
  2019-10-31 18:42     ` Dmitry Torokhov
@ 2019-11-01  4:59     ` Jeff LaBundy
  2019-11-01  8:56       ` Lee Jones
  1 sibling, 1 reply; 37+ messages in thread
From: Jeff LaBundy @ 2019-11-01  4:59 UTC (permalink / raw)
  To: Lee Jones
  Cc: dmitry.torokhov, jdelvare, linux, thierry.reding, jic23,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Lee,

Thank you for your thorough review.

On Thu, Oct 31, 2019 at 01:44:10PM +0000, Lee Jones wrote:
> On Sun, 20 Oct 2019, Jeff LaBundy wrote:
> 
> > 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 <jeff@labundy.com>
> > ---
> >  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
> 
> Needs a company or person's name.
> 

Sure thing; will do.

> > + * Author: Jeff LaBundy <jeff@labundy.com>
> > + *
> > + * 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.
> 
> This troubles me somewhat. So here we take a C header file which is
> the output of a vendor supplied configuration tool, convert it to a
> bespoke file format using a Python script authored by yourself, which
> we masquerade as Linux firmware in order to set-up the hardware.
> 
> Is that correct?

Correct on all counts.

> 
> What is preventing a very naughty person from providing their own
> register map (firmware) in order to read/write unsuitable registers
> from kernel context for their own gains; simply by swapping out a file
> contained in userspace?
> 

I would argue that if someone is willing to go that length, they likely understand
that their dock switch sensitivity may change or their ambient light sensor may no
longer function properly.

> It would probably be a better idea to compile the register definitions
> with the kernel/module to be safe. You can use Device Tree for
> run-time configuration changes.
> 

Taking the IQS620A as an example, there are over 100 individual fields that need
configured. Forcing customers to manually transfer the values derived within the
GUI to a corresponding collection of device tree bindings would be prohibitively
complex.

To complicate matters, many registers change meaning or restrict their available
values based on the values stored in other registers. Duplicating all of the de-
pendencies and restrictions comprehended by the vendor's tool in the driver and/
or the bindings document would not be practical.

Just to clarify, we're not storing register definitions (i.e. addresses) in this
"firmware"; rather, we're storing application-specific register values that don't
belong in the driver source. The C header file that comes from the vendor's tool
comprises dozens of #define's that map a register name to a value written to the
address bearing said arbitrary name, e.g.:

#define HYSTERESIS_UI_SETTINGS_0                        0xA2

My tool associates the address of HYSTERESIS_UI_SETTINGS_0 with its physical ad-
dress (0x80) which will be written with 0xA2. Note that common registers may oc-
cupy different addresses for each device, and my tool comprehends this.

The file may also contain one or more byte arrays that represent calibration data
that the driver selectively applies based on values in the device's OTP memory. We
simply package all of these elements as address/data "records" for the driver to
apply as efficiently as possible.

This "register map as firmware" concept is not new. The widely used wm_adsp frame-
work [0] does the exact same thing, where .bin (coefficient) files represent data
to be written directly to register-mapped DSP memory. Dmitry has highlighted some
valid examples as well.

The intent here is to offer a customer-friendly solution to a complex problem. And
although it's a Python tool I wrote myself, I've been fastidious about its documen-
tation [1], and I'm committed to maintaining it as with anything else.

> > + * Link to data sheets and GUIs: https://www.azoteq.com/products/proxfusion/
> > + *
> > + * Link to conversion tool: https://github.com/jlabundy/iqs62x-h2bin.git
> 
> This is unlikely to stand the test of time. Probably best to just list
> the name of the tool in the description above.
> 

My only concern here is that doing so forces customers to hunt for it using a web
search, or to email me. And while I'm happy to field such emails, an email address
is subject to the same risk of changing over time.

I think the onus is simply on me to send a patch if the host or owner of this tool
changes, just as I would if my email address changes. Please let me know if you do
not agree.

> > + */
> > +
> > +#include <linux/completion.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/err.h>
> > +#include <linux/firmware.h>
> > +#include <linux/i2c.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/kernel.h>
> > +#include <linux/list.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of_device.h>
> > +#include <linux/property.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +#include <asm/unaligned.h>
> 
> [...]
> 
> > +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);
> 
> Is it safe to re-initialise the entire device in IRQ context?
> 

Here, we are simply re-writing several registers from memory. This is a threaded
interrupt handler, so it should be safe to do so. But if I've misunderstood your
concern, please let me know.

> > +		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 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;
> 
> Nit: It's more common to use 'ret' or 'err' - my preference is 'ret'.
> 

I think there are valid arguments both ways, but in my experience, the preference
is not consistent across the audience of this patch series. Unless this is a deal
breaker, I'd like to leave it as 'error' simply for consistency.

> > +	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;
> 
> Reads better without the else:
> 
> 		if (info.prod_num != iqs62x_devs[i].prod_num)
> 			continue;
> 

Sure thing; will do.

> > +		if (info.sw_num >= iqs62x->dev_desc->sw_num)
> > +			iqs62x->sw_num = info.sw_num;
> > +		else
> > +			continue;
> 
> Same as above.
> 

Sure thing; will do.

> Do you still need to be in this loop at this point?
> 

Yes, that's because we need to determine whether the newly identified device
is calibrated or not (more on that next).

> > +		for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) {
> 
> What are you doing here? Please provide a comment.
> 

The search process here is as follows:

1. Check if the product number (device ID) is recognized, and if so:
2. Check that the software number (FW revision) is valid, and if so:
3. Check that the device's calibration (OTP) registers are non-zero (i.e.
   programmed) in which case some additional functionality is awarded, or
   the device is bad.

For example, the IQS620A device can report its absolute die temperature if
its scale/offset registers (0xC2 through 0xC4) have been programmed at the
factory. In that case, we're actually talking to an IQS620AT device and we
load an additional hwmon (soon to be iio) driver. However if they're blank,
we're talking to a plain IQS620A device and stick to input and pwm drivers.

In another example, the IQS621 (which includes an ambient light sensor) is
_only_ sold in a calibrated version. If we happen to come across a device
with empty calibration registers, its lux output will be garbage. In this
case we don't register the device at all.

I would be happy to add some comments here to explain what's happening.

> > +			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;
> 
> Is there a reason not to break here? If the product number matched
> once, can it match for a second time?
> 

It can in the case of the aforementioned IQS620A (no 'T') device. The driver
first looks for the IQS620AT which defines 3 calibration registers. If we're
talking to an IQS620A, the first pass of the loop (i = 0) will find 0xC2 = 0,
then j < num_cal_regs and the outer loop will wind forward (i = 1).

At that point, the second pass of the outer loop will check for an IQS620A,
which has the same product number but defines num_cal_regs as zero. In that
case, j = num_cal_regs = 0 and the outer loop will break.

After the outer loop finishes, if i < NUM_DEV then we know the following:

1. The product number is recognized, and:
2. The software number is valid, and:
3. All calibration registers, if any, are nonzero.

Again, this process warrants some comments and I would be happy to add some.

> > +	}
> > +
> > +	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;
> > +}
> 
> Please move the suspend/resume calls down to here.
> 

Sure thing; will do.

> > +static const struct i2c_device_id iqs62x_id[] = {
> > +	{ "iqs620a", 0 },
> > +	{ "iqs621", 1 },
> > +	{ "iqs622", 2 },
> > +	{ "iqs624", 3 },
> > +	{ "iqs625", 4 },
> 
> Better to define these. In fact, do you even use the .ids?
> 
> If not, you can use the new I2C probe function and drop this table.
> 

Sure thing; will do. I don't use the .ids, so I'll implement a probe_new
callback instead.

> > +	{ }
> > +};
> > +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 <jeff@labundy.com>");
> > +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 <jeff@labundy.com>
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/iqs62x.h>
> > +
> > +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,
> > +	},
> > +};
> 
> These should be moved into the core driver.
> 

The reason they're placed here is because they're referenced in the iqs62x_devs
array, members of which are then referenced by devm_mfd_add_devices in the core
driver.

If the mfd_cell arrays move to the core driver (where they're not used directly),
I think I'd have to make them extern. I think it's cleaner to limit the scope of
any given element to the minimum level that is necessary.

However if I have misunderstood or I could possibly make this more clear with a
comment or two, please let me know.

> > +static const u8 iqs620at_cal_regs[] = { 0xC2, 0xC3, 0xC4, };
> > +static const u8 iqs621_cal_regs[] = { 0x82, 0x83, };
> 
> What do these mean?
> 

These are the register addresses that correspond to the aforementioned calibration
(OTP) registers used for device identification.

> Probably best of do define them, so people can read them.
> 

Sure thing; will do.

> [...]
> 
> -- 
> Lee Jones [李琼斯]
> Linaro Services Technical Lead
> Linaro.org │ Open source software for ARM SoCs
> Follow Linaro: Facebook | Twitter | Blog
> 

[0] https://github.com/torvalds/linux/blob/master/sound/soc/codecs/wm_adsp.c
[1] https://github.com/jlabundy/iqs62x-h2bin/blob/master/README.md

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625
  2019-11-01  4:59     ` Jeff LaBundy
@ 2019-11-01  8:56       ` Lee Jones
  2019-11-02  2:49         ` Jeff LaBundy
  0 siblings, 1 reply; 37+ messages in thread
From: Lee Jones @ 2019-11-01  8:56 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: dmitry.torokhov, jdelvare, linux, thierry.reding, jic23,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

On Thu, 31 Oct 2019, Jeff LaBundy wrote:
> On Thu, Oct 31, 2019 at 01:44:10PM +0000, Lee Jones wrote:
> > On Sun, 20 Oct 2019, Jeff LaBundy wrote:
> > 
> > > 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 <jeff@labundy.com>
> > > ---
> > >  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.

[...]

> > What is preventing a very naughty person from providing their own
> > register map (firmware) in order to read/write unsuitable registers
> > from kernel context for their own gains; simply by swapping out a file
> > contained in userspace?
> 
> I would argue that if someone is willing to go that length, they likely understand
> that their dock switch sensitivity may change or their ambient light sensor may no
> longer function properly.
> 
> > It would probably be a better idea to compile the register definitions
> > with the kernel/module to be safe. You can use Device Tree for
> > run-time configuration changes.
> 
> Taking the IQS620A as an example, there are over 100 individual fields that need
> configured. Forcing customers to manually transfer the values derived within the
> GUI to a corresponding collection of device tree bindings would be prohibitively
> complex.
> 
> To complicate matters, many registers change meaning or restrict their available
> values based on the values stored in other registers. Duplicating all of the de-
> pendencies and restrictions comprehended by the vendor's tool in the driver and/
> or the bindings document would not be practical.
> 
> Just to clarify, we're not storing register definitions (i.e. addresses) in this
> "firmware"; rather, we're storing application-specific register values that don't
> belong in the driver source.

Okay, this allays my fears. I was under the impression that you could
manipulate addresses in the firmware in order to read/write from
non-expected registers in kernel context.

[...]

> > > +	/*
> > > +	 * 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);
> > 
> > Is it safe to re-initialise the entire device in IRQ context?
> > 
> 
> Here, we are simply re-writing several registers from memory. This is a threaded
> interrupt handler, so it should be safe to do so. But if I've misunderstood your
> concern, please let me know.

My intent here is to ensure it's been thought about. I see that you
are in a threaded handler, so it should be save to read/write register
and sleep.

> > > +		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 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;
> > 
> > Nit: It's more common to use 'ret' or 'err' - my preference is 'ret'.
> > 
> 
> I think there are valid arguments both ways, but in my experience, the preference
> is not consistent across the audience of this patch series. Unless this is a deal
> breaker, I'd like to leave it as 'error' simply for consistency.

The difference is *very* significant, more than an order of magnitude:

$ git grep "int.* ret[;\|,]" | wc -l
40549
$ git grep "int.* err[;\|,]" | wc -l
18558
$ git grep "int.* error[;\|,]" | wc -l
3381

[...]

> > > +		for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) {
> > 
> > What are you doing here? Please provide a comment.
> 
> The search process here is as follows:
> 
> 1. Check if the product number (device ID) is recognized, and if so:
> 2. Check that the software number (FW revision) is valid, and if so:
> 3. Check that the device's calibration (OTP) registers are non-zero (i.e.
>    programmed) in which case some additional functionality is awarded, or
>    the device is bad.
> 
> For example, the IQS620A device can report its absolute die temperature if
> its scale/offset registers (0xC2 through 0xC4) have been programmed at the
> factory. In that case, we're actually talking to an IQS620AT device and we
> load an additional hwmon (soon to be iio) driver. However if they're blank,
> we're talking to a plain IQS620A device and stick to input and pwm drivers.
> 
> In another example, the IQS621 (which includes an ambient light sensor) is
> _only_ sold in a calibrated version. If we happen to come across a device
> with empty calibration registers, its lux output will be garbage. In this
> case we don't register the device at all.
> 
> I would be happy to add some comments here to explain what's happening.

Please.

> > > +			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;
> > 
> > Is there a reason not to break here? If the product number matched
> > once, can it match for a second time?
> > 
> 
> It can in the case of the aforementioned IQS620A (no 'T') device. The driver
> first looks for the IQS620AT which defines 3 calibration registers. If we're
> talking to an IQS620A, the first pass of the loop (i = 0) will find 0xC2 = 0,
> then j < num_cal_regs and the outer loop will wind forward (i = 1).
> 
> At that point, the second pass of the outer loop will check for an IQS620A,
> which has the same product number but defines num_cal_regs as zero. In that
> case, j = num_cal_regs = 0 and the outer loop will break.
> 
> After the outer loop finishes, if i < NUM_DEV then we know the following:
> 
> 1. The product number is recognized, and:
> 2. The software number is valid, and:
> 3. All calibration registers, if any, are nonzero.
> 
> Again, this process warrants some comments and I would be happy to add some.

Great, thanks.

[...]

> > > +static const struct mfd_cell iqs625_sub_devs[] = {
> > > +	{
> > > +		.name = IQS62X_DRV_NAME_KEYS,
> > > +		.of_compatible = "azoteq,iqs625-keys",
> > > +	},
> > > +	{
> > > +		.name = IQS624_DRV_NAME_POS,
> > > +	},
> > > +};
> > 
> > These should be moved into the core driver.
> > 
> 
> The reason they're placed here is because they're referenced in the iqs62x_devs
> array, members of which are then referenced by devm_mfd_add_devices in the core
> driver.
> 
> If the mfd_cell arrays move to the core driver (where they're not used directly),
> I think I'd have to make them extern. I think it's cleaner to limit the scope of
> any given element to the minimum level that is necessary.
> 
> However if I have misunderstood or I could possibly make this more clear with a
> comment or two, please let me know.

Leave them where they are for now. I still need to do a review of this
file. It's strange to see such an odd weave of; registers, masks,
files, values and names bundled up in structure arrays like this. It
may take a little time.

[...]

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625
  2019-11-01  8:56       ` Lee Jones
@ 2019-11-02  2:49         ` Jeff LaBundy
  0 siblings, 0 replies; 37+ messages in thread
From: Jeff LaBundy @ 2019-11-02  2:49 UTC (permalink / raw)
  To: Lee Jones
  Cc: dmitry.torokhov, jdelvare, linux, thierry.reding, jic23,
	devicetree, linux-input, linux-hwmon, u.kleine-koenig, linux-pwm,
	knaack.h, lars, pmeerw, linux-iio, robh+dt, mark.rutland

Hi Lee,

On Fri, Nov 01, 2019 at 08:56:12AM +0000, Lee Jones wrote:
> On Thu, 31 Oct 2019, Jeff LaBundy wrote:
> > On Thu, Oct 31, 2019 at 01:44:10PM +0000, Lee Jones wrote:
> > > On Sun, 20 Oct 2019, Jeff LaBundy wrote:
> > > 
> > > > 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 <jeff@labundy.com>
> > > > ---
> > > >  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.
> 
> [...]
> 
> > > What is preventing a very naughty person from providing their own
> > > register map (firmware) in order to read/write unsuitable registers
> > > from kernel context for their own gains; simply by swapping out a file
> > > contained in userspace?
> > 
> > I would argue that if someone is willing to go that length, they likely understand
> > that their dock switch sensitivity may change or their ambient light sensor may no
> > longer function properly.
> > 
> > > It would probably be a better idea to compile the register definitions
> > > with the kernel/module to be safe. You can use Device Tree for
> > > run-time configuration changes.
> > 
> > Taking the IQS620A as an example, there are over 100 individual fields that need
> > configured. Forcing customers to manually transfer the values derived within the
> > GUI to a corresponding collection of device tree bindings would be prohibitively
> > complex.
> > 
> > To complicate matters, many registers change meaning or restrict their available
> > values based on the values stored in other registers. Duplicating all of the de-
> > pendencies and restrictions comprehended by the vendor's tool in the driver and/
> > or the bindings document would not be practical.
> > 
> > Just to clarify, we're not storing register definitions (i.e. addresses) in this
> > "firmware"; rather, we're storing application-specific register values that don't
> > belong in the driver source.
> 
> Okay, this allays my fears. I was under the impression that you could
> manipulate addresses in the firmware in order to read/write from
> non-expected registers in kernel context.
> 

My apologies, as my response was misleading. We're not defining register addresses in
this firmware the way a driver might (e.g. #define IQS62X_SYS_FLAGS 0x10). But we are
in fact storing the address to which the vendor's GUI requests that one or more data
bytes be written.

The firmware in this case is largely based off Intel hex format, with some extensions
to handle the device family's specific calibration needs. Specifically, in some cases
a field within a register is populated by OTP memory and the remaining fields must be
modified using a R/M/W operation. The tool comprehends all such registers.

And just like Intel hex, if a user _really_ wanted to be ornery, that user could edit
the address field of a record and re-route the record's data to an unused or reserved
register address. This is also the case for the aforementioned wm_adsp framework that
stores register address offsets in a .bin file which is also handled as "firmware."

Any any rate--I only clarify this to be transparent, and to point out that we're not
doing anything new. If there is any further information I can provide, please let me
know.

> [...]
> 
> > > > +	/*
> > > > +	 * 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);
> > > 
> > > Is it safe to re-initialise the entire device in IRQ context?
> > > 
> > 
> > Here, we are simply re-writing several registers from memory. This is a threaded
> > interrupt handler, so it should be safe to do so. But if I've misunderstood your
> > concern, please let me know.
> 
> My intent here is to ensure it's been thought about. I see that you
> are in a threaded handler, so it should be save to read/write register
> and sleep.
> 
> > > > +		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 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;
> > > 
> > > Nit: It's more common to use 'ret' or 'err' - my preference is 'ret'.
> > > 
> > 
> > I think there are valid arguments both ways, but in my experience, the preference
> > is not consistent across the audience of this patch series. Unless this is a deal
> > breaker, I'd like to leave it as 'error' simply for consistency.
> 
> The difference is *very* significant, more than an order of magnitude:
> 
> $ git grep "int.* ret[;\|,]" | wc -l
> 40549
> $ git grep "int.* err[;\|,]" | wc -l
> 18558
> $ git grep "int.* error[;\|,]" | wc -l
> 3381
> 
> [...]
> 

I'm happy to change to 'ret' here; I'd simply like to remain consistent within this
patch series.

@Dmitry: I know that 'error' tends to be preferred in input and the reasoning makes
sense to me, but I'd like to change 'error' to 'ret' in patch [3/8] as well. If you
have any objection, please let me know.

> > > > +		for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) {
> > > 
> > > What are you doing here? Please provide a comment.
> > 
> > The search process here is as follows:
> > 
> > 1. Check if the product number (device ID) is recognized, and if so:
> > 2. Check that the software number (FW revision) is valid, and if so:
> > 3. Check that the device's calibration (OTP) registers are non-zero (i.e.
> >    programmed) in which case some additional functionality is awarded, or
> >    the device is bad.
> > 
> > For example, the IQS620A device can report its absolute die temperature if
> > its scale/offset registers (0xC2 through 0xC4) have been programmed at the
> > factory. In that case, we're actually talking to an IQS620AT device and we
> > load an additional hwmon (soon to be iio) driver. However if they're blank,
> > we're talking to a plain IQS620A device and stick to input and pwm drivers.
> > 
> > In another example, the IQS621 (which includes an ambient light sensor) is
> > _only_ sold in a calibrated version. If we happen to come across a device
> > with empty calibration registers, its lux output will be garbage. In this
> > case we don't register the device at all.
> > 
> > I would be happy to add some comments here to explain what's happening.
> 
> Please.
> 
> > > > +			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;
> > > 
> > > Is there a reason not to break here? If the product number matched
> > > once, can it match for a second time?
> > > 
> > 
> > It can in the case of the aforementioned IQS620A (no 'T') device. The driver
> > first looks for the IQS620AT which defines 3 calibration registers. If we're
> > talking to an IQS620A, the first pass of the loop (i = 0) will find 0xC2 = 0,
> > then j < num_cal_regs and the outer loop will wind forward (i = 1).
> > 
> > At that point, the second pass of the outer loop will check for an IQS620A,
> > which has the same product number but defines num_cal_regs as zero. In that
> > case, j = num_cal_regs = 0 and the outer loop will break.
> > 
> > After the outer loop finishes, if i < NUM_DEV then we know the following:
> > 
> > 1. The product number is recognized, and:
> > 2. The software number is valid, and:
> > 3. All calibration registers, if any, are nonzero.
> > 
> > Again, this process warrants some comments and I would be happy to add some.
> 
> Great, thanks.
> 
> [...]
> 
> > > > +static const struct mfd_cell iqs625_sub_devs[] = {
> > > > +	{
> > > > +		.name = IQS62X_DRV_NAME_KEYS,
> > > > +		.of_compatible = "azoteq,iqs625-keys",
> > > > +	},
> > > > +	{
> > > > +		.name = IQS624_DRV_NAME_POS,
> > > > +	},
> > > > +};
> > > 
> > > These should be moved into the core driver.
> > > 
> > 
> > The reason they're placed here is because they're referenced in the iqs62x_devs
> > array, members of which are then referenced by devm_mfd_add_devices in the core
> > driver.
> > 
> > If the mfd_cell arrays move to the core driver (where they're not used directly),
> > I think I'd have to make them extern. I think it's cleaner to limit the scope of
> > any given element to the minimum level that is necessary.
> > 
> > However if I have misunderstood or I could possibly make this more clear with a
> > comment or two, please let me know.
> 
> Leave them where they are for now. I still need to do a review of this
> file. It's strange to see such an odd weave of; registers, masks,
> files, values and names bundled up in structure arrays like this. It
> may take a little time.
> 

Sure thing; will do. The reason for this arrangement is that this family of devices
exposes status bits at (mostly) the same bit locations within a given register, but
the addresses of those particular registers may vary per device. The structures and
arrays in this file help keep common functions simple and generic, with all device-
specific details in a separate location that's easy to maintain.

> [...]
> 
> -- 
> Lee Jones [李琼斯]
> Linaro Services Technical Lead
> Linaro.org │ Open source software for ARM SoCs
> Follow Linaro: Facebook | Twitter | Blog
> 

Kind regards,
Jeff LaBundy

^ permalink raw reply	[flat|nested] 37+ messages in thread

end of thread, back to index

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-21  4:11 [PATCH 0/8] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
2019-10-21  4:11 ` [PATCH 1/8] dt-bindings: mfd: iqs62x: Add bindings Jeff LaBundy
2019-10-22 11:00   ` Jonathan Cameron
2019-10-23  3:36     ` Jeff LaBundy
2019-10-23  9:30       ` Lee Jones
2019-10-24  2:38         ` Jeff LaBundy
2019-10-21  4:11 ` [PATCH 2/8] mfd: Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
2019-10-31 13:44   ` Lee Jones
2019-10-31 18:42     ` Dmitry Torokhov
2019-11-01  4:59     ` Jeff LaBundy
2019-11-01  8:56       ` Lee Jones
2019-11-02  2:49         ` Jeff LaBundy
2019-10-21  4:11 ` [PATCH 3/8] input: keyboard: " Jeff LaBundy
2019-10-23  0:22   ` Dmitry Torokhov
2019-10-23  1:29     ` Jeff LaBundy
2019-10-23 23:08       ` Dmitry Torokhov
2019-10-21  4:11 ` [PATCH 4/8] hwmon: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
2019-10-21 15:38   ` Guenter Roeck
2019-10-22  2:26     ` Jeff LaBundy
2019-10-22  3:22       ` Guenter Roeck
2019-10-22 11:38         ` Jonathan Cameron
2019-10-23  2:04           ` Jeff LaBundy
2019-10-21  4:11 ` [PATCH 5/8] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
2019-10-21  7:34   ` Uwe Kleine-König
2019-10-22  4:36     ` Jeff LaBundy
2019-10-22  6:54       ` Uwe Kleine-König
2019-10-23  2:45         ` Jeff LaBundy
2019-10-23  7:23           ` Uwe Kleine-König
2019-10-24  3:02             ` Jeff LaBundy
2019-10-21  4:11 ` [PATCH 6/8] iio: light: Add support for Azoteq IQS621 ambient light sensor Jeff LaBundy
2019-10-22 11:23   ` Jonathan Cameron
2019-10-23  2:59     ` Jeff LaBundy
2019-10-21  4:11 ` [PATCH 7/8] iio: proximity: Add support for Azoteq IQS622 proximity sensor Jeff LaBundy
2019-10-22 11:23   ` Jonathan Cameron
2019-10-23  3:09     ` Jeff LaBundy
2019-10-21  4:11 ` [PATCH 8/8] iio: position: Add support for Azoteq IQS624/625 angle sensor Jeff LaBundy
2019-10-22 11:28   ` Jonathan Cameron

Linux-IIO Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-iio/0 linux-iio/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-iio linux-iio/ https://lore.kernel.org/linux-iio \
		linux-iio@vger.kernel.org
	public-inbox-index linux-iio

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-iio


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git