linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625
@ 2019-12-09  0:38 Jeff LaBundy
  2019-12-09  0:38 ` [PATCH v2 1/7] dt-bindings: Add bindings " Jeff LaBundy
                   ` (6 more replies)
  0 siblings, 7 replies; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-09  0:38 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree
  Cc: linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland, Jeff LaBundy

This series adds support for the Azoteq IQS620A, IQS621, IQS622, IQS624 and
IQS625 multi-function sensors. Each device integrates numerous sensing tech-
nologies 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 and iio
drivers supports device-specific functions including ambient light and angular
position sensing.

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

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

 .../devicetree/bindings/input/iqs62x-keys.yaml     | 126 ++++
 Documentation/devicetree/bindings/mfd/iqs62x.yaml  | 177 ++++++
 .../devicetree/bindings/pwm/iqs620a-pwm.yaml       |  30 +
 drivers/iio/Kconfig                                |   1 +
 drivers/iio/Makefile                               |   1 +
 drivers/iio/light/Kconfig                          |  10 +
 drivers/iio/light/Makefile                         |   1 +
 drivers/iio/light/iqs621-als.c                     | 614 ++++++++++++++++++++
 drivers/iio/position/Kconfig                       |  19 +
 drivers/iio/position/Makefile                      |   7 +
 drivers/iio/position/iqs624-pos.c                  | 284 +++++++++
 drivers/iio/temperature/Kconfig                    |  10 +
 drivers/iio/temperature/Makefile                   |   1 +
 drivers/iio/temperature/iqs620at-temp.c            |  97 ++++
 drivers/input/keyboard/Kconfig                     |  10 +
 drivers/input/keyboard/Makefile                    |   1 +
 drivers/input/keyboard/iqs62x-keys.c               | 340 +++++++++++
 drivers/mfd/Kconfig                                |  13 +
 drivers/mfd/Makefile                               |   3 +
 drivers/mfd/iqs62x-core.c                          | 639 +++++++++++++++++++++
 drivers/mfd/iqs62x-tables.c                        | 438 ++++++++++++++
 drivers/pwm/Kconfig                                |  10 +
 drivers/pwm/Makefile                               |   1 +
 drivers/pwm/pwm-iqs620a.c                          | 206 +++++++
 include/linux/mfd/iqs62x.h                         | 146 +++++
 25 files changed, 3185 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/iqs62x-keys.yaml
 create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.yaml
 create mode 100644 Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
 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/temperature/iqs620at-temp.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] 30+ messages in thread

* [PATCH v2 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625
  2019-12-09  0:38 [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
@ 2019-12-09  0:38 ` Jeff LaBundy
  2019-12-18 23:52   ` Rob Herring
  2019-12-09  0:38 ` [PATCH v2 2/7] mfd: Add support " Jeff LaBundy
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-09  0:38 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree
  Cc: linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland, Jeff LaBundy

This patch adds device tree bindings for the Azoteq IQS620A, IQS621,
IQS622, IQS624 and IQS625 multi-function sensors.

A total of three bindings are presented (one MFD and two child nodes);
they are submitted as a single patch because the child node bindings
have no meaning in the absence of the MFD binding.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
---
Changes in v2:
  - Removed "prox" child node and moved "keys" and "pwm" child nodes to their
    own bindings
  - Replaced linux,fw-file property with more common firmware-name property
  - Converted all bindings to YAML

 .../devicetree/bindings/input/iqs62x-keys.yaml     | 126 +++++++++++++++
 Documentation/devicetree/bindings/mfd/iqs62x.yaml  | 177 +++++++++++++++++++++
 .../devicetree/bindings/pwm/iqs620a-pwm.yaml       |  30 ++++
 3 files changed, 333 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/iqs62x-keys.yaml
 create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.yaml
 create mode 100644 Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml

diff --git a/Documentation/devicetree/bindings/input/iqs62x-keys.yaml b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
new file mode 100644
index 0000000..e9b54e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
@@ -0,0 +1,126 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/iqs62x-keys.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Azoteq IQS620A/621/622/624/625 Keys and Switches
+
+maintainers:
+  - Jeff LaBundy <jeff@labundy.com>
+
+description: |
+  The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors
+  feature a variety of self-capacitive, mutual-inductive and Hall-effect sens-
+  ing capabilities that can facilitate a variety of contactless key and switch
+  applications.
+
+  These functions are collectively represented by a "keys" child node from the
+  parent MFD driver. See Documentation/devicetree/bindings/mfd/iqs62x.yaml for
+  further details and examples. Sensor hardware configuration (self-capacitive
+  vs. mutual-inductive, etc.) is selected based on the device's firmware.
+
+properties:
+  compatible:
+    enum:
+      - azoteq,iqs620a-keys
+      - azoteq,iqs621-keys
+      - azoteq,iqs622-keys
+      - azoteq,iqs624-keys
+      - azoteq,iqs625-keys
+
+  linux,keycodes:
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - minItems: 1
+        maxItems: 16
+    description: |
+      Specifies the numeric keycodes associated with each available touch or
+      proximity event according to the following table. An 'x' indicates the
+      event is supported for a given device. Specify 0 for unused events.
+
+      -------------------------------------------------------------------------
+      | #  | Event              | IQS620A | IQS621 | IQS622 | IQS624 | IQS625 |
+      -------------------------------------------------------------------------
+      | 0  | CH0 Touch          |    x    |    x   |    x   |    x   |    x   |
+      |    | Antenna 1 Touch*   |    x    |        |        |        |        |
+      -------------------------------------------------------------------------
+      | 1  | CH0 Proximity      |    x    |    x   |    x   |    x   |    x   |
+      |    | Antenna 1 Prox.*   |    x    |        |        |        |        |
+      -------------------------------------------------------------------------
+      | 2  | CH1 Touch          |    x    |    x   |    x   |    x   |    x   |
+      |    | Ant. 1 Deep Touch* |    x    |        |        |        |        |
+      -------------------------------------------------------------------------
+      | 3  | CH1 Proximity      |    x    |    x   |    x   |    x   |    x   |
+      -------------------------------------------------------------------------
+      | 4  | CH2 Touch          |    x    |        |        |        |        |
+      -------------------------------------------------------------------------
+      | 5  | CH2 Proximity      |    x    |        |        |        |        |
+      |    | Antenna 2 Prox.*   |    x    |        |        |        |        |
+      -------------------------------------------------------------------------
+      | 6  | Metal (+) Touch**  |    x    |    x   |        |        |        |
+      |    | Ant. 2 Deep Touch* |    x    |        |        |        |        |
+      -------------------------------------------------------------------------
+      | 7  | Metal (+) Prox.**  |    x    |    x   |        |        |        |
+      |    | Antenna 2 Touch*   |    x    |        |        |        |        |
+      -------------------------------------------------------------------------
+      | 8  | Metal (-) Touch**  |    x    |    x   |        |        |        |
+      -------------------------------------------------------------------------
+      | 9  | Metal (-) Prox.**  |    x    |    x   |        |        |        |
+      -------------------------------------------------------------------------
+      | 10 | SAR Active***      |    x    |        |    x   |        |        |
+      -------------------------------------------------------------------------
+      | 11 | SAR Quick Rel.***  |    x    |        |    x   |        |        |
+      -------------------------------------------------------------------------
+      | 12 | SAR Movement***    |    x    |        |    x   |        |        |
+      -------------------------------------------------------------------------
+      | 13 | SAR Filter Halt*** |    x    |        |    x   |        |        |
+      -------------------------------------------------------------------------
+      | 14 | Wheel Up           |         |        |        |    x   |        |
+      -------------------------------------------------------------------------
+      | 15 | Wheel Down         |         |        |        |    x   |        |
+      -------------------------------------------------------------------------
+      *   Two-channel SAR. Replaces CH0-2 plus metal touch and proximity events
+          if enabled via firmware.
+      **  "+" and "-" refer to the polarity of a channel's delta (LTA - counts),
+          where "LTA" is defined as the channel's long-term average.
+      *** One-channel SAR. Replaces CH0-2 touch and proximity events if enabled
+          via firmware.
+
+required:
+  - compatible
+  - linux,keycodes
+
+if:
+  properties:
+    compatible:
+      contains:
+        enum:
+          - azoteq,iqs620a-keys
+          - azoteq,iqs621-keys
+          - azoteq,iqs622-keys
+then:
+  patternProperties:
+    "^hall-switch-(north|south)$":
+      type: object
+      description:
+        Represents north/south-field Hall-effect sensor touch or proximity
+        events. Note that north/south-field orientation is reversed on the
+        IQS620AXzCSR device due to its flip-chip package.
+
+      properties:
+        linux,code:
+          $ref: /schemas/types.yaml#/definitions/uint32
+          description: Numeric switch code associated with the event.
+
+        azoteq,use-prox:
+          $ref: /schemas/types.yaml#/definitions/flag
+          description:
+            If present, specifies that Hall-effect sensor reporting should
+            use the device's wide-range proximity threshold instead of its
+            close-range touch threshold (default).
+
+      required:
+        - linux,code
+
+...
diff --git a/Documentation/devicetree/bindings/mfd/iqs62x.yaml b/Documentation/devicetree/bindings/mfd/iqs62x.yaml
new file mode 100644
index 0000000..24e6004
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/iqs62x.yaml
@@ -0,0 +1,177 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/iqs62x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
+
+maintainers:
+  - Jeff LaBundy <jeff@labundy.com>
+
+description: |
+  The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors
+  integrate multiple sensing technologies in a single package.
+
+  Link to data sheets: https://www.azoteq.com/
+
+properties:
+  compatible:
+    enum:
+      - azoteq,iqs620a
+      - azoteq,iqs621
+      - azoteq,iqs622
+      - azoteq,iqs624
+      - azoteq,iqs625
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  firmware-name:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Specifies the name of the calibration and configuration file selected by
+      the driver. If this property is omitted, the name is chosen based on the
+      device name with ".bin" as the extension (e.g. iqs620a.bin for IQS620A).
+
+  keys:
+    $ref: ../input/iqs62x-keys.yaml
+
+  pwm:
+    $ref: ../pwm/iqs620a-pwm.yaml
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+examples:
+  - |
+    /*
+     * Dual capacitive buttons with additional "air button," unipolar lid
+     * switch and panel-mounted LED.
+     */
+    #include <dt-bindings/input/input.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            iqs620a@44 {
+                    compatible = "azoteq,iqs620a";
+                    reg = <0x44>;
+                    interrupt-parent = <&gpio>;
+                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
+
+                    keys {
+                            compatible = "azoteq,iqs620a-keys";
+
+                            linux,keycodes = <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>;
+            };
+    };
+
+  - |
+    /* Single inductive button with bipolar dock/tablet-mode switch. */
+    #include <dt-bindings/input/input.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            iqs620a@44 {
+                    compatible = "azoteq,iqs620a";
+                    reg = <0x44>;
+                    interrupt-parent = <&gpio>;
+                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
+
+                    firmware-name = "iqs620a_coil.bin";
+
+                    keys {
+                            compatible = "azoteq,iqs620a-keys";
+
+                            linux,keycodes = <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <KEY_MUTE>;
+
+                            hall-switch-north {
+                                    linux,code = <SW_DOCK>;
+                            };
+
+                            hall-switch-south {
+                                    linux,code = <SW_TABLET_MODE>;
+                            };
+                    };
+            };
+    };
+
+  - |
+    /* Dual capacitive buttons with volume knob. */
+    #include <dt-bindings/input/input.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            iqs624@44 {
+                    compatible = "azoteq,iqs624";
+                    reg = <0x44>;
+                    interrupt-parent = <&gpio>;
+                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
+
+                    keys {
+                            compatible = "azoteq,iqs624-keys";
+
+                            linux,keycodes = <BTN_0>,
+                                             <0>,
+                                             <BTN_1>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <0>,
+                                             <KEY_VOLUMEUP>,
+                                             <KEY_VOLUMEDOWN>;
+                    };
+            };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
new file mode 100644
index 0000000..6b7aaef
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/iqs620a-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Azoteq IQS620A PWM Generator
+
+maintainers:
+  - Jeff LaBundy <jeff@labundy.com>
+
+description: |
+  The Azoteq IQS620A multi-function sensor generates a fixed-frequency PWM
+  output represented by a "pwm" child node from the parent MFD driver. See
+  Documentation/devicetree/bindings/mfd/iqs62x.yaml for further details as
+  well as an example.
+
+properties:
+  compatible:
+    enum:
+      - azoteq,iqs620a-pwm
+
+  "#pwm-cells":
+    const: 2
+
+required:
+  - compatible
+  - "#pwm-cells"
+
+...
--
2.7.4


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

* [PATCH v2 2/7] mfd: Add support for Azoteq IQS620A/621/622/624/625
  2019-12-09  0:38 [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
  2019-12-09  0:38 ` [PATCH v2 1/7] dt-bindings: Add bindings " Jeff LaBundy
@ 2019-12-09  0:38 ` Jeff LaBundy
  2019-12-09  0:38 ` [PATCH v2 3/7] input: keyboard: " Jeff LaBundy
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-09  0:38 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree
  Cc: linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland, Jeff LaBundy

This patch adds core support for the Azoteq IQS620A, IQS621, IQS622,
IQS624 and IQS625 multi-function sensors.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
---
Changes in v2:
  - Merged 'Copyright' and 'Author' lines into one in introductory comments
  - Replaced 'error' with 'ret' throughout
  - Updated iqs62x_dev_init to account for 4/8/16-MHz clock divider in start-up
    delays and replaced ATI timeout routine with regmap_read_poll_timeout
  - Added an error message to iqs62x_irq in case device status fails to be read
  - Replaced sw_num member of iqs62x_core with a local variable in iqs62x_probe
    as the former was unused anywhere else
  - Added comments throughout iqs62x_probe to clarify how devices are matched
    based on the presence of calibration data
  - Inverted the product and software number comparison logic in iqs62x_probe
    to avoid an else...continue branch
  - Changed iqs62x_probe from .probe callback to .probe_new callback, thereby
    eliminating the otherwise unused iqs62x_id array
  - Moved iqs62x_suspend and iqs62x_resume below iqs62x_remove
  - Eliminated tabbed alignment of regmap_config and i2c_driver struct members
  - Added register definitions for register addresses used in iqs621_cal_regs,
    iqs620at_cal_regs and iqs62x_devs arrays
  - Removed of_compatible string from IQS622 mfd_cell struct as its proximity
    (now ambient light) sensing functionality need not be represented using a
    child node
  - Dissolved union in iqs62x_event_data to allow simultaneous use of ir_flags
    and als_flags
  - Removed temp_flags member of iqs62x_event_data, IQS62X_EVENT_TEMP register
    enumeration and IQS62X_EVENT_UI_HI/LO from iqs620a_event_regs (thereby re-
    ducing IQS62X_EVENT_SIZE to 10) as they were unused

 drivers/mfd/Kconfig         |  13 +
 drivers/mfd/Makefile        |   3 +
 drivers/mfd/iqs62x-core.c   | 639 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/iqs62x-tables.c | 438 ++++++++++++++++++++++++++++++
 include/linux/mfd/iqs62x.h  | 146 ++++++++++
 5 files changed, 1239 insertions(+)
 create mode 100644 drivers/mfd/iqs62x-core.c
 create mode 100644 drivers/mfd/iqs62x-tables.c
 create mode 100644 include/linux/mfd/iqs62x.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 4209008..151984c 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -642,6 +642,19 @@ config MFD_IPAQ_MICRO
 	  AT90LS8535 microcontroller flashed with a special iPAQ
 	  firmware using the custom protocol implemented in this driver.

+config MFD_IQS62X
+	tristate "Azoteq IQS620A/621/622/624/625 core support"
+	depends on I2C
+	select MFD_CORE
+	select REGMAP_I2C
+	help
+	  Say Y here if you want to build core support for the Azoteq IQS620A,
+	  IQS621, IQS622, IQS624 and IQS625 multi-function sensors. Additional
+	  options must be selected to enable device-specific functions.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called iqs62x.
+
 config MFD_JANZ_CMODIO
 	tristate "Janz CMOD-IO PCI MODULbus Carrier Board"
 	select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index aed99f0..c4fc26b 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -232,6 +232,9 @@ obj-$(CONFIG_MFD_DLN2)		+= dln2.o
 obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
 obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o

+iqs62x-objs			:= iqs62x-core.o iqs62x-tables.o
+obj-$(CONFIG_MFD_IQS62X)	+= iqs62x.o
+
 intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
 obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
 obj-$(CONFIG_INTEL_SOC_PMIC_BXTWC)	+= intel_soc_pmic_bxtwc.o
diff --git a/drivers/mfd/iqs62x-core.c b/drivers/mfd/iqs62x-core.c
new file mode 100644
index 0000000..767f9d8
--- /dev/null
+++ b/drivers/mfd/iqs62x-core.c
@@ -0,0 +1,639 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
+ *
+ * Copyright (C) 2019 Jeff LaBundy <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/
+ *
+ * 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_CLK_DIV		BIT(4)
+#define IQS62X_SYS_SETTINGS_REDO_ATI		BIT(1)
+
+#define IQS62X_PWR_SETTINGS			0xD2
+#define IQS62X_PWR_SETTINGS_DIS_AUTO		BIT(5)
+#define IQS62X_PWR_SETTINGS_PWR_MODE_MASK	(BIT(4) | BIT(3))
+#define IQS62X_PWR_SETTINGS_PWR_MODE_HALT	(BIT(4) | BIT(3))
+#define IQS62X_PWR_SETTINGS_PWR_MODE_NORM	0
+
+#define IQS62X_OTP_CMD				0xF0
+#define IQS62X_OTP_CMD_FG3			0x13
+#define IQS62X_OTP_DATA				0xF1
+#define IQS62X_MAX_REG				0xFF
+
+#define IQS62X_HALL_CAL_MASK			GENMASK(3, 0)
+
+#define IQS62X_FW_REC_TYPE_INFO			0
+#define IQS62X_FW_REC_TYPE_PROD			1
+#define IQS62X_FW_REC_TYPE_HALL			2
+#define IQS62X_FW_REC_TYPE_MASK			3
+#define IQS62X_FW_REC_TYPE_DATA			4
+
+struct iqs62x_fw_rec {
+	u8 type;
+	u8 addr;
+	u8 len;
+	u8 data;
+} __packed;
+
+struct iqs62x_fw_blk {
+	struct list_head list;
+	u8 addr;
+	u8 mask;
+	u8 len;
+	u8 data[];
+};
+
+struct iqs62x_info {
+	u8 prod_num;
+	u8 sw_num;
+	u8 hw_num;
+} __packed;
+
+static int iqs62x_dev_init(struct iqs62x_core *iqs62x)
+{
+	struct iqs62x_fw_blk *fw_blk;
+	unsigned int val;
+	int ret;
+	u8 clk_div = 1;
+
+	list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) {
+		if (fw_blk->mask)
+			ret = regmap_update_bits(iqs62x->map, fw_blk->addr,
+						 fw_blk->mask, *fw_blk->data);
+		else
+			ret = regmap_raw_write(iqs62x->map, fw_blk->addr,
+					       fw_blk->data, fw_blk->len);
+		if (ret)
+			return ret;
+	}
+
+	switch (iqs62x->dev_desc->prod_num) {
+	case IQS620_PROD_NUM:
+	case IQS622_PROD_NUM:
+		ret = regmap_read(iqs62x->map, iqs62x->dev_desc->prod_num ==
+				  IQS620_PROD_NUM ? IQS620_PROX_SETTINGS_4 :
+						    IQS622_PROX_SETTINGS_4,
+				  &val);
+		if (ret)
+			return ret;
+
+		if (val & IQS620_PROX_SETTINGS_4_SAR_EN)
+			iqs62x->ui_sel = IQS62X_UI_SAR1;
+		/* fall through */
+
+	case IQS621_PROD_NUM:
+		ret = regmap_write(iqs62x->map, IQS620_GLBL_EVENT_MASK,
+				   IQS620_GLBL_EVENT_MASK_PMU |
+				   iqs62x->dev_desc->prox_mask |
+				   iqs62x->dev_desc->sar_mask |
+				   iqs62x->dev_desc->hall_mask |
+				   iqs62x->dev_desc->hyst_mask |
+				   iqs62x->dev_desc->temp_mask |
+				   iqs62x->dev_desc->als_mask |
+				   iqs62x->dev_desc->ir_mask);
+		if (ret)
+			return ret;
+		break;
+
+	default:
+		ret = regmap_write(iqs62x->map, IQS624_HALL_UI,
+				   IQS624_HALL_UI_WHL_EVENT |
+				   IQS624_HALL_UI_INT_EVENT |
+				   IQS624_HALL_UI_AUTO_CAL);
+		if (ret)
+			return ret;
+
+		ret = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV, &val);
+		if (ret)
+			return ret;
+
+		if (val >= iqs62x->dev_desc->interval_div)
+			break;
+
+		ret = regmap_write(iqs62x->map, IQS624_INTERVAL_DIV,
+				   iqs62x->dev_desc->interval_div);
+		if (ret)
+			return ret;
+	}
+
+	ret = regmap_read(iqs62x->map, IQS62X_SYS_SETTINGS, &val);
+	if (ret)
+		return ret;
+
+	if (val & IQS62X_SYS_SETTINGS_CLK_DIV)
+		clk_div = iqs62x->dev_desc->clk_div;
+
+	ret = regmap_write(iqs62x->map, IQS62X_SYS_SETTINGS, val |
+			   IQS62X_SYS_SETTINGS_ACK_RESET |
+			   IQS62X_SYS_SETTINGS_EVENT_MODE |
+			   IQS62X_SYS_SETTINGS_REDO_ATI);
+	if (ret)
+		return ret;
+
+	ret = regmap_read_poll_timeout(iqs62x->map, IQS62X_SYS_FLAGS, val,
+				       !(val & IQS62X_SYS_FLAGS_IN_ATI),
+				       10000, clk_div * 500000);
+	if (ret)
+		return ret;
+
+	/*
+	 * The following delay accommodates the post-ATI stabilization time
+	 * specified in the data sheet (with additional margin).
+	 */
+	msleep(clk_div * 150);
+
+	return 0;
+}
+
+static int iqs62x_fw_prs(struct iqs62x_core *iqs62x, const struct firmware *fw)
+{
+	struct i2c_client *client = iqs62x->client;
+	struct iqs62x_fw_rec *fw_rec;
+	struct iqs62x_fw_blk *fw_blk;
+	unsigned int val;
+	size_t pos = 0;
+	int ret = 0;
+	u8 mask, len, *data;
+	u8 hall_cal_index = 0;
+
+	while (pos < fw->size) {
+		if (pos + sizeof(*fw_rec) > fw->size) {
+			ret = -EINVAL;
+			break;
+		}
+		fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos);
+		pos += sizeof(*fw_rec);
+
+		if (pos + fw_rec->len - 1 > fw->size) {
+			ret = -EINVAL;
+			break;
+		}
+		pos += fw_rec->len - 1;
+
+		switch (fw_rec->type) {
+		case IQS62X_FW_REC_TYPE_INFO:
+			continue;
+
+		case IQS62X_FW_REC_TYPE_PROD:
+			if (fw_rec->data == iqs62x->dev_desc->prod_num)
+				continue;
+
+			dev_err(&client->dev,
+				"Incompatible product number: 0x%02X\n",
+				fw_rec->data);
+			ret = -EINVAL;
+			break;
+
+		case IQS62X_FW_REC_TYPE_HALL:
+			if (!hall_cal_index) {
+				ret = regmap_write(iqs62x->map, IQS62X_OTP_CMD,
+						   IQS62X_OTP_CMD_FG3);
+				if (ret)
+					break;
+
+				ret = regmap_read(iqs62x->map, IQS62X_OTP_DATA,
+						  &val);
+				if (ret)
+					break;
+
+				hall_cal_index = val & IQS62X_HALL_CAL_MASK;
+				if (!hall_cal_index) {
+					dev_err(&client->dev,
+						"Uncalibrated device\n");
+					ret = -ENODATA;
+					break;
+				}
+			}
+
+			if (hall_cal_index > fw_rec->len) {
+				ret = -EINVAL;
+				break;
+			}
+
+			mask = 0;
+			data = &fw_rec->data + hall_cal_index - 1;
+			len = sizeof(*data);
+			break;
+
+		case IQS62X_FW_REC_TYPE_MASK:
+			if (fw_rec->len < (sizeof(mask) + sizeof(*data))) {
+				ret = -EINVAL;
+				break;
+			}
+
+			mask = fw_rec->data;
+			data = &fw_rec->data + sizeof(mask);
+			len = sizeof(*data);
+			break;
+
+		case IQS62X_FW_REC_TYPE_DATA:
+			mask = 0;
+			data = &fw_rec->data;
+			len = fw_rec->len;
+			break;
+
+		default:
+			dev_err(&client->dev,
+				"Unrecognized record type: 0x%02X\n",
+				fw_rec->type);
+			ret = -EINVAL;
+		}
+
+		if (ret)
+			break;
+
+		fw_blk = devm_kzalloc(&client->dev,
+				      struct_size(fw_blk, data, len),
+				      GFP_KERNEL);
+		if (!fw_blk) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		fw_blk->addr = fw_rec->addr;
+		fw_blk->mask = mask;
+		fw_blk->len = len;
+		memcpy(fw_blk->data, data, len);
+
+		list_add(&fw_blk->list, &iqs62x->fw_blk_head);
+	}
+
+	release_firmware(fw);
+
+	return ret;
+}
+
+static irqreturn_t iqs62x_irq(int irq, void *context)
+{
+	struct iqs62x_core *iqs62x = context;
+	struct i2c_client *client = iqs62x->client;
+	struct iqs62x_event_data event_data;
+	struct iqs62x_event_desc event_desc;
+	enum iqs62x_event_reg event_reg;
+	unsigned long event_flags = 0;
+	int ret, i, j;
+	u8 event_map[IQS62X_EVENT_SIZE];
+
+	/*
+	 * The device asserts the RDY output to signal the beginning of a
+	 * communication window, which is closed by an I2C stop condition.
+	 * As such, all interrupt status is captured in a single read and
+	 * broadcast to any interested sub-device drivers.
+	 */
+	ret = regmap_raw_read(iqs62x->map, IQS62X_SYS_FLAGS, event_map,
+			      sizeof(event_map));
+	if (ret) {
+		dev_err(&client->dev, "Failed to read device status: %d\n",
+			ret);
+		return IRQ_NONE;
+	}
+
+	for (i = 0; i < sizeof(event_map); i++) {
+		event_reg = iqs62x->dev_desc->event_regs[iqs62x->ui_sel][i];
+
+		switch (event_reg) {
+		case IQS62X_EVENT_UI_LO:
+			event_data.ui_data = get_unaligned_le16(&event_map[i]);
+			/* fall through */
+		case IQS62X_EVENT_UI_HI:
+		case IQS62X_EVENT_NONE:
+			continue;
+
+		case IQS62X_EVENT_ALS:
+			event_data.als_flags = event_map[i];
+			continue;
+
+		case IQS62X_EVENT_IR:
+			event_data.ir_flags = event_map[i];
+			continue;
+
+		case IQS62X_EVENT_INTER:
+			event_data.interval = event_map[i];
+			continue;
+
+		case IQS62X_EVENT_HYST:
+			event_map[i] <<= iqs62x->dev_desc->hyst_shift;
+			/* fall through */
+		case IQS62X_EVENT_WHEEL:
+		case IQS62X_EVENT_HALL:
+		case IQS62X_EVENT_PROX:
+		case IQS62X_EVENT_SYS:
+			break;
+		}
+
+		for (j = 0; j < IQS62X_NUM_EVENTS; j++) {
+			event_desc = iqs62x_events[j];
+
+			if (event_desc.reg != event_reg)
+				continue;
+
+			if ((event_map[i] & event_desc.mask) == event_desc.val)
+				event_flags |= BIT(j);
+		}
+	}
+
+	/*
+	 * The device resets itself in response to the I2C master stalling
+	 * communication past a fixed timeout. In this case, all registers
+	 * are restored and any interested sub-device drivers are notified.
+	 */
+	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
+		dev_err(&client->dev, "Unexpected device reset\n");
+
+		ret = iqs62x_dev_init(iqs62x);
+		if (ret) {
+			dev_err(&client->dev,
+				"Failed to re-initialize device: %d\n", ret);
+			return IRQ_NONE;
+		}
+	}
+
+	ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags,
+					   &event_data);
+	if (ret & NOTIFY_STOP_MASK)
+		return IRQ_NONE;
+
+	/*
+	 * Once the communication window is closed, a small delay is added to
+	 * ensure the device's RDY output has been deasserted by the time the
+	 * interrupt handler returns.
+	 */
+	usleep_range(50, 100);
+
+	return IRQ_HANDLED;
+}
+
+static void iqs62x_fw_cb(const struct firmware *fw, void *context)
+{
+	struct iqs62x_core *iqs62x = context;
+	struct i2c_client *client = iqs62x->client;
+	int ret;
+
+	if (fw) {
+		ret = iqs62x_fw_prs(iqs62x, fw);
+		if (ret) {
+			dev_err(&client->dev, "Failed to parse firmware: %d\n",
+				ret);
+			goto err_out;
+		}
+	}
+
+	ret = iqs62x_dev_init(iqs62x);
+	if (ret) {
+		dev_err(&client->dev, "Failed to initialize device: %d\n", ret);
+		goto err_out;
+	}
+
+	ret = devm_request_threaded_irq(&client->dev, client->irq,
+					NULL, iqs62x_irq, IRQF_ONESHOT,
+					client->name, iqs62x);
+	if (ret) {
+		dev_err(&client->dev, "Failed to request IRQ: %d\n", ret);
+		goto err_out;
+	}
+
+	ret = devm_mfd_add_devices(&client->dev, -1,
+				   iqs62x->dev_desc->sub_devs,
+				   iqs62x->dev_desc->num_sub_devs,
+				   NULL, 0, NULL);
+	if (ret)
+		dev_err(&client->dev, "Failed to add devices: %d\n", ret);
+
+err_out:
+	complete_all(&iqs62x->fw_done);
+}
+
+static const struct regmap_config iqs62x_map_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = IQS62X_MAX_REG,
+};
+
+static int iqs62x_probe(struct i2c_client *client)
+{
+	struct iqs62x_core *iqs62x;
+	struct iqs62x_info info;
+	unsigned int val;
+	int ret, i, j;
+	u8 sw_num = 0;
+	const char *fw_name = NULL;
+
+	iqs62x = devm_kzalloc(&client->dev, sizeof(*iqs62x), GFP_KERNEL);
+	if (!iqs62x)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, iqs62x);
+	iqs62x->client = client;
+
+	BLOCKING_INIT_NOTIFIER_HEAD(&iqs62x->nh);
+	INIT_LIST_HEAD(&iqs62x->fw_blk_head);
+	init_completion(&iqs62x->fw_done);
+
+	iqs62x->map = devm_regmap_init_i2c(client, &iqs62x_map_config);
+	if (IS_ERR(iqs62x->map)) {
+		ret = PTR_ERR(iqs62x->map);
+		dev_err(&client->dev, "Failed to initialize register map: %d\n",
+			ret);
+		return ret;
+	}
+
+	ret = regmap_raw_read(iqs62x->map, IQS62X_PROD_NUM, &info,
+			      sizeof(info));
+	if (ret)
+		return ret;
+
+	/*
+	 * The following sequence validates the device's product and software
+	 * numbers. It then determines if the device is factory-calibrated by
+	 * checking for nonzero values in the device's designated calibration
+	 * registers (if applicable). Depending on the device, the absence of
+	 * calibration data indicates a reduced feature set or invalid device.
+	 *
+	 * For devices given in both calibrated and uncalibrated versions, the
+	 * calibrated version (e.g. IQS620AT) appears first in the iqs62x_devs
+	 * array. The uncalibrated version (e.g. IQS620A) appears next and has
+	 * the same product and software numbers, but no calibration registers
+	 * are specified.
+	 */
+	for (i = 0; i < IQS62X_NUM_DEV; i++) {
+		if (info.prod_num != iqs62x_devs[i].prod_num)
+			continue;
+		iqs62x->dev_desc = &iqs62x_devs[i];
+
+		if (info.sw_num < iqs62x->dev_desc->sw_num)
+			continue;
+		sw_num = info.sw_num;
+
+		/*
+		 * Read each of the device's designated calibration registers,
+		 * if any, and exit from the inner loop early if any are equal
+		 * to zero.
+		 */
+		for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) {
+			ret = regmap_read(iqs62x->map,
+					  iqs62x->dev_desc->cal_regs[j], &val);
+			if (ret)
+				return ret;
+
+			if (!val)
+				break;
+		}
+
+		/*
+		 * If the number of nonzero values read from the device equals
+		 * the number of designated calibration registers (which could
+		 * be zero), exit from the outer loop early to signal a device
+		 * has been matched.
+		 */
+		if (j == iqs62x->dev_desc->num_cal_regs)
+			break;
+	}
+
+	if (!iqs62x->dev_desc) {
+		dev_err(&client->dev, "Unrecognized product number: 0x%02X\n",
+			info.prod_num);
+		return -EINVAL;
+	}
+
+	if (!sw_num) {
+		dev_err(&client->dev, "Unrecognized software number: 0x%02X\n",
+			info.sw_num);
+		return -EINVAL;
+	}
+
+	if (i == IQS62X_NUM_DEV) {
+		dev_err(&client->dev, "Uncalibrated device\n");
+		return -ENODATA;
+	}
+
+	ret = regmap_write(iqs62x->map, IQS62X_SYS_SETTINGS,
+			   IQS62X_SYS_SETTINGS_SOFT_RESET);
+	if (ret)
+		return ret;
+	usleep_range(10000, 10100);
+
+	device_property_read_string(&client->dev, "firmware-name", &fw_name);
+
+	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+				      fw_name ? : iqs62x->dev_desc->fw_name,
+				      &client->dev, GFP_KERNEL, iqs62x,
+				      iqs62x_fw_cb);
+	if (ret)
+		dev_err(&client->dev, "Failed to request firmware: %d\n", ret);
+
+	return ret;
+}
+
+static int iqs62x_remove(struct i2c_client *client)
+{
+	struct iqs62x_core *iqs62x = i2c_get_clientdata(client);
+
+	wait_for_completion(&iqs62x->fw_done);
+
+	return 0;
+}
+
+static int __maybe_unused iqs62x_suspend(struct device *dev)
+{
+	struct iqs62x_core *iqs62x = dev_get_drvdata(dev);
+	int ret;
+
+	wait_for_completion(&iqs62x->fw_done);
+
+	/*
+	 * As per the data sheet, automatic mode switching must be disabled
+	 * before the device is placed in or taken out of halt mode.
+	 */
+	ret = regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS,
+				 IQS62X_PWR_SETTINGS_DIS_AUTO,
+				 IQS62X_PWR_SETTINGS_DIS_AUTO);
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS,
+				  IQS62X_PWR_SETTINGS_PWR_MODE_MASK,
+				  IQS62X_PWR_SETTINGS_PWR_MODE_HALT);
+}
+
+static int __maybe_unused iqs62x_resume(struct device *dev)
+{
+	struct iqs62x_core *iqs62x = dev_get_drvdata(dev);
+	int ret;
+
+	ret = regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS,
+				 IQS62X_PWR_SETTINGS_PWR_MODE_MASK,
+				 IQS62X_PWR_SETTINGS_PWR_MODE_NORM);
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS,
+				  IQS62X_PWR_SETTINGS_DIS_AUTO, 0);
+}
+
+static SIMPLE_DEV_PM_OPS(iqs62x_pm, iqs62x_suspend, iqs62x_resume);
+
+static const struct of_device_id iqs62x_of_match[] = {
+	{ .compatible = "azoteq,iqs620a" },
+	{ .compatible = "azoteq,iqs621" },
+	{ .compatible = "azoteq,iqs622" },
+	{ .compatible = "azoteq,iqs624" },
+	{ .compatible = "azoteq,iqs625" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, iqs62x_of_match);
+
+static struct i2c_driver iqs62x_i2c_driver = {
+	.driver = {
+		.name = "iqs62x",
+		.of_match_table = iqs62x_of_match,
+		.pm = &iqs62x_pm,
+	},
+	.probe_new = iqs62x_probe,
+	.remove = iqs62x_remove,
+};
+module_i2c_driver(iqs62x_i2c_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Multi-Function Sensors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/iqs62x-tables.c b/drivers/mfd/iqs62x-tables.c
new file mode 100644
index 0000000..580f6ac
--- /dev/null
+++ b/drivers/mfd/iqs62x-tables.c
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
+ *
+ * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/iqs62x.h>
+
+#define IQS620_HALL_FLAGS			0x16
+#define IQS620_TEMP_CAL_MULT			0xC2
+#define IQS620_TEMP_CAL_DIV			0xC3
+#define IQS620_TEMP_CAL_OFFS			0xC4
+
+#define IQS621_HALL_FLAGS			0x19
+#define IQS621_ALS_CAL_DIV_LUX			0x82
+#define IQS621_ALS_CAL_DIV_IR			0x83
+
+#define IQS622_HALL_FLAGS			IQS621_HALL_FLAGS
+
+#define IQS624_INTERVAL_NUM			0x18
+#define IQS625_INTERVAL_NUM			0x12
+
+static const struct mfd_cell iqs620at_sub_devs[] = {
+	{
+		.name = IQS62X_DRV_NAME_KEYS,
+		.of_compatible = "azoteq,iqs620a-keys",
+	},
+	{
+		.name = IQS620_DRV_NAME_PWM,
+		.of_compatible = "azoteq,iqs620a-pwm",
+	},
+	{
+		.name = IQS620_DRV_NAME_TEMP,
+	},
+};
+
+static const struct mfd_cell iqs620a_sub_devs[] = {
+	{
+		.name = IQS62X_DRV_NAME_KEYS,
+		.of_compatible = "azoteq,iqs620a-keys",
+	},
+	{
+		.name = IQS620_DRV_NAME_PWM,
+		.of_compatible = "azoteq,iqs620a-pwm",
+	},
+};
+
+static const struct mfd_cell iqs621_sub_devs[] = {
+	{
+		.name = IQS62X_DRV_NAME_KEYS,
+		.of_compatible = "azoteq,iqs621-keys",
+	},
+	{
+		.name = IQS621_DRV_NAME_ALS,
+	},
+};
+
+static const struct mfd_cell iqs622_sub_devs[] = {
+	{
+		.name = IQS62X_DRV_NAME_KEYS,
+		.of_compatible = "azoteq,iqs622-keys",
+	},
+	{
+		.name = IQS621_DRV_NAME_ALS,
+	},
+};
+
+static const struct mfd_cell iqs624_sub_devs[] = {
+	{
+		.name = IQS62X_DRV_NAME_KEYS,
+		.of_compatible = "azoteq,iqs624-keys",
+	},
+	{
+		.name = IQS624_DRV_NAME_POS,
+	},
+};
+
+static const struct mfd_cell iqs625_sub_devs[] = {
+	{
+		.name = IQS62X_DRV_NAME_KEYS,
+		.of_compatible = "azoteq,iqs625-keys",
+	},
+	{
+		.name = IQS624_DRV_NAME_POS,
+	},
+};
+
+static const u8 iqs620at_cal_regs[] = {
+	IQS620_TEMP_CAL_MULT,
+	IQS620_TEMP_CAL_DIV,
+	IQS620_TEMP_CAL_OFFS,
+};
+
+static const u8 iqs621_cal_regs[] = {
+	IQS621_ALS_CAL_DIV_LUX,
+	IQS621_ALS_CAL_DIV_IR,
+};
+
+static const enum iqs62x_event_reg iqs620a_event_regs[][IQS62X_EVENT_SIZE] = {
+	[IQS62X_UI_PROX] = {
+		IQS62X_EVENT_SYS,	/* 0x10 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_PROX,	/* 0x12 */
+		IQS62X_EVENT_HYST,	/* 0x13 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_HALL,	/* 0x16 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+	},
+	[IQS62X_UI_SAR1] = {
+		IQS62X_EVENT_SYS,	/* 0x10 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_HYST,	/* 0x13 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_HALL,	/* 0x16 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+	},
+};
+
+static const enum iqs62x_event_reg iqs621_event_regs[][IQS62X_EVENT_SIZE] = {
+	[IQS62X_UI_PROX] = {
+		IQS62X_EVENT_SYS,	/* 0x10 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_PROX,	/* 0x12 */
+		IQS62X_EVENT_HYST,	/* 0x13 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_ALS,	/* 0x16 */
+		IQS62X_EVENT_UI_LO,	/* 0x17 */
+		IQS62X_EVENT_UI_HI,	/* 0x18 */
+		IQS62X_EVENT_HALL,	/* 0x19 */
+	},
+};
+
+static const enum iqs62x_event_reg iqs622_event_regs[][IQS62X_EVENT_SIZE] = {
+	[IQS62X_UI_PROX] = {
+		IQS62X_EVENT_SYS,	/* 0x10 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_PROX,	/* 0x12 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_ALS,	/* 0x14 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_IR,	/* 0x16 */
+		IQS62X_EVENT_UI_LO,	/* 0x17 */
+		IQS62X_EVENT_UI_HI,	/* 0x18 */
+		IQS62X_EVENT_HALL,	/* 0x19 */
+	},
+	[IQS62X_UI_SAR1] = {
+		IQS62X_EVENT_SYS,	/* 0x10 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_HYST,	/* 0x13 */
+		IQS62X_EVENT_ALS,	/* 0x14 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_IR,	/* 0x16 */
+		IQS62X_EVENT_UI_LO,	/* 0x17 */
+		IQS62X_EVENT_UI_HI,	/* 0x18 */
+		IQS62X_EVENT_HALL,	/* 0x19 */
+	},
+};
+
+static const enum iqs62x_event_reg iqs624_event_regs[][IQS62X_EVENT_SIZE] = {
+	[IQS62X_UI_PROX] = {
+		IQS62X_EVENT_SYS,	/* 0x10 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_PROX,	/* 0x12 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_WHEEL,	/* 0x14 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_UI_LO,	/* 0x16 */
+		IQS62X_EVENT_UI_HI,	/* 0x17 */
+		IQS62X_EVENT_INTER,	/* 0x18 */
+		IQS62X_EVENT_NONE,
+	},
+};
+
+static const enum iqs62x_event_reg iqs625_event_regs[][IQS62X_EVENT_SIZE] = {
+	[IQS62X_UI_PROX] = {
+		IQS62X_EVENT_SYS,	/* 0x10 */
+		IQS62X_EVENT_PROX,	/* 0x11 */
+		IQS62X_EVENT_INTER,	/* 0x12 */
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+		IQS62X_EVENT_NONE,
+	},
+};
+
+enum {
+	IQS620AT_DEV,
+	IQS620A_DEV,
+	IQS621_DEV,
+	IQS622_DEV,
+	IQS624_DEV,
+	IQS625_DEV,
+};
+
+const struct iqs62x_dev_desc iqs62x_devs[IQS62X_NUM_DEV] = {
+	[IQS620AT_DEV] = {
+		.dev_name	= "iqs620at",
+		.sub_devs	= iqs620at_sub_devs,
+		.num_sub_devs	= ARRAY_SIZE(iqs620at_sub_devs),
+
+		.prod_num	= IQS620_PROD_NUM,
+		.sw_num		= 0x08,
+		.cal_regs	= iqs620at_cal_regs,
+		.num_cal_regs	= ARRAY_SIZE(iqs620at_cal_regs),
+
+		.prox_mask	= BIT(0),
+		.sar_mask	= BIT(1) | BIT(7),
+		.hall_mask	= BIT(2),
+		.hyst_mask	= BIT(3),
+		.temp_mask	= BIT(4),
+
+		.hall_flags	= IQS620_HALL_FLAGS,
+
+		.clk_div	= 4,
+		.fw_name	= "iqs620a.bin",
+		.event_regs	= &iqs620a_event_regs[IQS62X_UI_PROX],
+	},
+	[IQS620A_DEV] = {
+		.dev_name	= "iqs620a",
+		.sub_devs	= iqs620a_sub_devs,
+		.num_sub_devs	= ARRAY_SIZE(iqs620a_sub_devs),
+
+		.prod_num	= IQS620_PROD_NUM,
+		.sw_num		= 0x08,
+
+		.prox_mask	= BIT(0),
+		.sar_mask	= BIT(1) | BIT(7),
+		.hall_mask	= BIT(2),
+		.hyst_mask	= BIT(3),
+		.temp_mask	= BIT(4),
+
+		.hall_flags	= IQS620_HALL_FLAGS,
+
+		.clk_div	= 4,
+		.fw_name	= "iqs620a.bin",
+		.event_regs	= &iqs620a_event_regs[IQS62X_UI_PROX],
+	},
+	[IQS621_DEV] = {
+		.dev_name	= "iqs621",
+		.sub_devs	= iqs621_sub_devs,
+		.num_sub_devs	= ARRAY_SIZE(iqs621_sub_devs),
+
+		.prod_num	= IQS621_PROD_NUM,
+		.sw_num		= 0x09,
+		.cal_regs	= iqs621_cal_regs,
+		.num_cal_regs	= ARRAY_SIZE(iqs621_cal_regs),
+
+		.prox_mask	= BIT(0),
+		.hall_mask	= BIT(1),
+		.als_mask	= BIT(2),
+		.hyst_mask	= BIT(3),
+		.temp_mask	= BIT(4),
+
+		.als_flags	= IQS621_ALS_FLAGS,
+		.hall_flags	= IQS621_HALL_FLAGS,
+		.hyst_shift	= 5,
+
+		.clk_div	= 2,
+		.fw_name	= "iqs621.bin",
+		.event_regs	= &iqs621_event_regs[IQS62X_UI_PROX],
+	},
+	[IQS622_DEV] = {
+		.dev_name	= "iqs622",
+		.sub_devs	= iqs622_sub_devs,
+		.num_sub_devs	= ARRAY_SIZE(iqs622_sub_devs),
+
+		.prod_num	= IQS622_PROD_NUM,
+		.sw_num		= 0x06,
+
+		.prox_mask	= BIT(0),
+		.sar_mask	= BIT(1),
+		.hall_mask	= BIT(2),
+		.als_mask	= BIT(3),
+		.ir_mask	= BIT(4),
+
+		.als_flags	= IQS622_ALS_FLAGS,
+		.hall_flags	= IQS622_HALL_FLAGS,
+
+		.clk_div	= 2,
+		.fw_name	= "iqs622.bin",
+		.event_regs	= &iqs622_event_regs[IQS62X_UI_PROX],
+	},
+	[IQS624_DEV] = {
+		.dev_name	= "iqs624",
+		.sub_devs	= iqs624_sub_devs,
+		.num_sub_devs	= ARRAY_SIZE(iqs624_sub_devs),
+
+		.prod_num	= IQS624_PROD_NUM,
+		.sw_num		= 0x0B,
+
+		.interval	= IQS624_INTERVAL_NUM,
+		.interval_div	= 3,
+
+		.clk_div	= 2,
+		.fw_name	= "iqs624.bin",
+		.event_regs	= &iqs624_event_regs[IQS62X_UI_PROX],
+	},
+	[IQS625_DEV] = {
+		.dev_name	= "iqs625",
+		.sub_devs	= iqs625_sub_devs,
+		.num_sub_devs	= ARRAY_SIZE(iqs625_sub_devs),
+
+		.prod_num	= IQS625_PROD_NUM,
+		.sw_num		= 0x0B,
+
+		.interval	= IQS625_INTERVAL_NUM,
+		.interval_div	= 10,
+
+		.clk_div	= 2,
+		.fw_name	= "iqs625.bin",
+		.event_regs	= &iqs625_event_regs[IQS62X_UI_PROX],
+	},
+};
+EXPORT_SYMBOL_GPL(iqs62x_devs);
+
+const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS] = {
+	[IQS62X_EVENT_PROX_CH0_T] = {
+		.reg	= IQS62X_EVENT_PROX,
+		.mask	= BIT(4),
+		.val	= BIT(4),
+	},
+	[IQS62X_EVENT_PROX_CH0_P] = {
+		.reg	= IQS62X_EVENT_PROX,
+		.mask	= BIT(0),
+		.val	= BIT(0),
+	},
+	[IQS62X_EVENT_PROX_CH1_T] = {
+		.reg	= IQS62X_EVENT_PROX,
+		.mask	= BIT(5),
+		.val	= BIT(5),
+	},
+	[IQS62X_EVENT_PROX_CH1_P] = {
+		.reg	= IQS62X_EVENT_PROX,
+		.mask	= BIT(1),
+		.val	= BIT(1),
+	},
+	[IQS62X_EVENT_PROX_CH2_T] = {
+		.reg	= IQS62X_EVENT_PROX,
+		.mask	= BIT(6),
+		.val	= BIT(6),
+	},
+	[IQS62X_EVENT_PROX_CH2_P] = {
+		.reg	= IQS62X_EVENT_PROX,
+		.mask	= BIT(2),
+		.val	= BIT(2),
+	},
+	[IQS62X_EVENT_HYST_POS_T] = {
+		.reg	= IQS62X_EVENT_HYST,
+		.mask	= BIT(6) | BIT(7),
+		.val	= BIT(6),
+	},
+	[IQS62X_EVENT_HYST_POS_P] = {
+		.reg	= IQS62X_EVENT_HYST,
+		.mask	= BIT(5) | BIT(7),
+		.val	= BIT(5),
+	},
+	[IQS62X_EVENT_HYST_NEG_T] = {
+		.reg	= IQS62X_EVENT_HYST,
+		.mask	= BIT(6) | BIT(7),
+		.val	= BIT(6) | BIT(7),
+	},
+	[IQS62X_EVENT_HYST_NEG_P] = {
+		.reg	= IQS62X_EVENT_HYST,
+		.mask	= BIT(5) | BIT(7),
+		.val	= BIT(5) | BIT(7),
+	},
+	[IQS62X_EVENT_SAR1_ACT] = {
+		.reg	= IQS62X_EVENT_HYST,
+		.mask	= BIT(4),
+		.val	= BIT(4),
+	},
+	[IQS62X_EVENT_SAR1_QRD] = {
+		.reg	= IQS62X_EVENT_HYST,
+		.mask	= BIT(2),
+		.val	= BIT(2),
+	},
+	[IQS62X_EVENT_SAR1_MOVE] = {
+		.reg	= IQS62X_EVENT_HYST,
+		.mask	= BIT(1),
+		.val	= BIT(1),
+	},
+	[IQS62X_EVENT_SAR1_HALT] = {
+		.reg	= IQS62X_EVENT_HYST,
+		.mask	= BIT(0),
+		.val	= BIT(0),
+	},
+	[IQS62X_EVENT_WHEEL_UP] = {
+		.reg	= IQS62X_EVENT_WHEEL,
+		.mask	= BIT(7) | BIT(6),
+		.val	= BIT(7),
+	},
+	[IQS62X_EVENT_WHEEL_DN] = {
+		.reg	= IQS62X_EVENT_WHEEL,
+		.mask	= BIT(7) | BIT(6),
+		.val	= BIT(7) | BIT(6),
+	},
+	[IQS62X_EVENT_HALL_N_T] = {
+		.reg	= IQS62X_EVENT_HALL,
+		.mask	= BIT(2) | BIT(0),
+		.val	= BIT(2),
+	},
+	[IQS62X_EVENT_HALL_N_P] = {
+		.reg	= IQS62X_EVENT_HALL,
+		.mask	= BIT(1) | BIT(0),
+		.val	= BIT(1),
+	},
+	[IQS62X_EVENT_HALL_S_T] = {
+		.reg	= IQS62X_EVENT_HALL,
+		.mask	= BIT(2) | BIT(0),
+		.val	= BIT(2) | BIT(0),
+	},
+	[IQS62X_EVENT_HALL_S_P] = {
+		.reg	= IQS62X_EVENT_HALL,
+		.mask	= BIT(1) | BIT(0),
+		.val	= BIT(1) | BIT(0),
+	},
+	[IQS62X_EVENT_SYS_RESET] = {
+		.reg	= IQS62X_EVENT_SYS,
+		.mask	= BIT(7),
+		.val	= BIT(7),
+	},
+};
+EXPORT_SYMBOL_GPL(iqs62x_events);
diff --git a/include/linux/mfd/iqs62x.h b/include/linux/mfd/iqs62x.h
new file mode 100644
index 0000000..0dc5997
--- /dev/null
+++ b/include/linux/mfd/iqs62x.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
+ *
+ * Copyright (C) 2019 Jeff LaBundy <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 IQS621_ALS_FLAGS			0x16
+#define IQS622_ALS_FLAGS			0x14
+
+#define IQS624_HALL_UI				0x70
+#define IQS624_HALL_UI_WHL_EVENT		BIT(4)
+#define IQS624_HALL_UI_INT_EVENT		BIT(3)
+#define IQS624_HALL_UI_AUTO_CAL			BIT(2)
+
+#define IQS624_INTERVAL_DIV			0x7D
+
+#define IQS620_GLBL_EVENT_MASK			0xD7
+#define IQS620_GLBL_EVENT_MASK_PMU		BIT(6)
+
+#define IQS62X_NUM_DEV				6
+#define IQS62X_NUM_KEYS				16
+#define IQS62X_NUM_EVENTS			(IQS62X_NUM_KEYS + 5)
+
+#define IQS62X_EVENT_SIZE			10
+
+#define IQS62X_DRV_NAME_KEYS			"iqs62x-keys"
+#define IQS620_DRV_NAME_TEMP			"iqs620at-temp"
+#define IQS620_DRV_NAME_PWM			"iqs620a-pwm"
+#define IQS621_DRV_NAME_ALS			"iqs621-als"
+#define IQS624_DRV_NAME_POS			"iqs624-pos"
+
+enum iqs62x_ui_sel {
+	IQS62X_UI_PROX,
+	IQS62X_UI_SAR1,
+};
+
+enum iqs62x_event_reg {
+	IQS62X_EVENT_NONE,
+	IQS62X_EVENT_SYS,
+	IQS62X_EVENT_PROX,
+	IQS62X_EVENT_HYST,
+	IQS62X_EVENT_HALL,
+	IQS62X_EVENT_ALS,
+	IQS62X_EVENT_IR,
+	IQS62X_EVENT_WHEEL,
+	IQS62X_EVENT_INTER,
+	IQS62X_EVENT_UI_LO,
+	IQS62X_EVENT_UI_HI,
+};
+
+enum iqs62x_event_flag {
+	/* keys */
+	IQS62X_EVENT_PROX_CH0_T,
+	IQS62X_EVENT_PROX_CH0_P,
+	IQS62X_EVENT_PROX_CH1_T,
+	IQS62X_EVENT_PROX_CH1_P,
+	IQS62X_EVENT_PROX_CH2_T,
+	IQS62X_EVENT_PROX_CH2_P,
+	IQS62X_EVENT_HYST_POS_T,
+	IQS62X_EVENT_HYST_POS_P,
+	IQS62X_EVENT_HYST_NEG_T,
+	IQS62X_EVENT_HYST_NEG_P,
+	IQS62X_EVENT_SAR1_ACT,
+	IQS62X_EVENT_SAR1_QRD,
+	IQS62X_EVENT_SAR1_MOVE,
+	IQS62X_EVENT_SAR1_HALT,
+	IQS62X_EVENT_WHEEL_UP,
+	IQS62X_EVENT_WHEEL_DN,
+
+	/* switches */
+	IQS62X_EVENT_HALL_N_T,
+	IQS62X_EVENT_HALL_N_P,
+	IQS62X_EVENT_HALL_S_T,
+	IQS62X_EVENT_HALL_S_P,
+
+	/* everything else */
+	IQS62X_EVENT_SYS_RESET,
+};
+
+struct iqs62x_event_data {
+	u16 ui_data;
+	u8 als_flags;
+	u8 ir_flags;
+	u8 interval;
+};
+
+struct iqs62x_event_desc {
+	enum iqs62x_event_reg reg;
+	u8 mask;
+	u8 val;
+};
+
+struct iqs62x_dev_desc {
+	const char *dev_name;
+	const struct mfd_cell *sub_devs;
+	int num_sub_devs;
+
+	u8 prod_num;
+	u8 sw_num;
+	const u8 *cal_regs;
+	int num_cal_regs;
+
+	u8 prox_mask;
+	u8 sar_mask;
+	u8 hall_mask;
+	u8 hyst_mask;
+	u8 temp_mask;
+	u8 als_mask;
+	u8 ir_mask;
+
+	u8 als_flags;
+	u8 hall_flags;
+	u8 hyst_shift;
+
+	u8 interval;
+	u8 interval_div;
+
+	u8 clk_div;
+	const char *fw_name;
+	const enum iqs62x_event_reg (*event_regs)[IQS62X_EVENT_SIZE];
+};
+
+struct iqs62x_core {
+	const struct iqs62x_dev_desc *dev_desc;
+	struct i2c_client *client;
+	struct regmap *map;
+	struct blocking_notifier_head nh;
+	struct list_head fw_blk_head;
+	struct completion fw_done;
+	enum iqs62x_ui_sel ui_sel;
+};
+
+extern const struct iqs62x_dev_desc iqs62x_devs[IQS62X_NUM_DEV];
+extern const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS];
+
+#endif /* __LINUX_MFD_IQS62X_H */
--
2.7.4


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

* [PATCH v2 3/7] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
  2019-12-09  0:38 [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
  2019-12-09  0:38 ` [PATCH v2 1/7] dt-bindings: Add bindings " Jeff LaBundy
  2019-12-09  0:38 ` [PATCH v2 2/7] mfd: Add support " Jeff LaBundy
@ 2019-12-09  0:38 ` Jeff LaBundy
  2019-12-09  0:38 ` [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-09  0:38 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree
  Cc: linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland, Jeff LaBundy

This patch adds key and switch support for the Azoteq IQS620A,
IQS621, IQS622, IQS624 and IQS625 multi-function sensors.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
---
Changes in v2:
  - Merged 'Copyright' and 'Author' lines into one in introductory comments
  - Replaced 'error' with 'ret' throughout
  - Updated iqs62x_keys_parse_prop to use unified device property interface
  - Clarified the comment in iqs62x_keys_notifier to state that wheel up or
    down events elicit an emulated release cycle
  - Eliminated tabbed alignment of platform_driver struct members

 drivers/input/keyboard/Kconfig       |  10 ++
 drivers/input/keyboard/Makefile      |   1 +
 drivers/input/keyboard/iqs62x-keys.c | 340 +++++++++++++++++++++++++++++++++++
 3 files changed, 351 insertions(+)
 create mode 100644 drivers/input/keyboard/iqs62x-keys.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 4706ff0..28de965 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -663,6 +663,16 @@ config KEYBOARD_IPAQ_MICRO
 	  To compile this driver as a module, choose M here: the
 	  module will be called ipaq-micro-keys.

+config KEYBOARD_IQS62X
+	tristate "Azoteq IQS620A/621/622/624/625 keys and switches"
+	depends on MFD_IQS62X
+	help
+	  Say Y here to enable key and switch support for the Azoteq IQS620A,
+	  IQS621, IQS622, IQS624 and IQS625 multi-function sensors.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called iqs62x-keys.
+
 config KEYBOARD_OMAP
 	tristate "TI OMAP keypad support"
 	depends on ARCH_OMAP1
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index f5b1752..1d689fd 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_KEYBOARD_TCA8418)		+= tca8418_keypad.o
 obj-$(CONFIG_KEYBOARD_HIL)		+= hil_kbd.o
 obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_IPAQ_MICRO)	+= ipaq-micro-keys.o
+obj-$(CONFIG_KEYBOARD_IQS62X)		+= iqs62x-keys.o
 obj-$(CONFIG_KEYBOARD_IMX)		+= imx_keypad.o
 obj-$(CONFIG_KEYBOARD_IMX_SC_KEY)	+= imx_sc_key.o
 obj-$(CONFIG_KEYBOARD_HP6XX)		+= jornada680_kbd.o
diff --git a/drivers/input/keyboard/iqs62x-keys.c b/drivers/input/keyboard/iqs62x-keys.c
new file mode 100644
index 0000000..b477334
--- /dev/null
+++ b/drivers/input/keyboard/iqs62x-keys.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS620A/621/622/624/625 Keys and Switches
+ *
+ * Copyright (C) 2019 Jeff LaBundy <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/platform_device.h>
+#include <linux/property.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 fwnode_handle *child;
+	unsigned int val;
+	int ret, i;
+
+	ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes",
+					     NULL, 0);
+	if (ret > IQS62X_NUM_KEYS) {
+		dev_err(&pdev->dev, "Too many keycodes present\n");
+		return -EINVAL;
+	} else if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to count keycodes: %d\n", ret);
+		return ret;
+	}
+	iqs62x_keys->keycodemax = ret;
+
+	ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes",
+					     iqs62x_keys->keycode,
+					     iqs62x_keys->keycodemax);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to read keycodes: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) {
+		child = device_get_named_child_node(&pdev->dev,
+						    iqs62x_switch_names[i]);
+		if (!child)
+			continue;
+
+		ret = fwnode_property_read_u32(child, "linux,code", &val);
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to read switch code: %d\n",
+				ret);
+			return ret;
+		}
+		iqs62x_keys->switches[i].code = val;
+		iqs62x_keys->switches[i].enabled = true;
+
+		if (fwnode_property_present(child, "azoteq,use-prox"))
+			iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ?
+							 IQS62X_EVENT_HALL_N_P :
+							 IQS62X_EVENT_HALL_S_P);
+		else
+			iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ?
+							 IQS62X_EVENT_HALL_N_T :
+							 IQS62X_EVENT_HALL_S_T);
+	}
+
+	return 0;
+}
+
+static int iqs62x_keys_init(struct iqs62x_keys_private *iqs62x_keys)
+{
+	struct iqs62x_core *iqs62x = iqs62x_keys->iqs62x;
+	enum iqs62x_event_flag flag;
+	unsigned int event_mask_reg;
+	unsigned int event_mask = 0;
+	unsigned int val;
+	int ret, i;
+
+	switch (iqs62x->dev_desc->prod_num) {
+	case IQS620_PROD_NUM:
+	case IQS621_PROD_NUM:
+	case IQS622_PROD_NUM:
+		event_mask_reg = IQS620_GLBL_EVENT_MASK;
+
+		/*
+		 * Discreet button, hysteresis and SAR UI flags represent keys
+		 * and are unmasked if mapped to a valid keycode.
+		 */
+		for (i = 0; i < iqs62x_keys->keycodemax; i++) {
+			if (iqs62x_keys->keycode[i] == KEY_RESERVED)
+				continue;
+
+			if (iqs62x_events[i].reg == IQS62X_EVENT_PROX)
+				event_mask |= iqs62x->dev_desc->prox_mask;
+			else if (iqs62x_events[i].reg == IQS62X_EVENT_HYST)
+				event_mask |= (iqs62x->dev_desc->hyst_mask |
+					       iqs62x->dev_desc->sar_mask);
+		}
+
+		ret = regmap_read(iqs62x->map, iqs62x->dev_desc->hall_flags,
+				  &val);
+		if (ret)
+			return ret;
+
+		/*
+		 * Hall UI flags represent switches and are unmasked if their
+		 * corresponding child nodes are present.
+		 */
+		for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) {
+			if (!(iqs62x_keys->switches[i].enabled))
+				continue;
+
+			flag = iqs62x_keys->switches[i].flag;
+
+			if (iqs62x_events[flag].reg != IQS62X_EVENT_HALL)
+				continue;
+
+			event_mask |= iqs62x->dev_desc->hall_mask;
+
+			input_report_switch(iqs62x_keys->input,
+					    iqs62x_keys->switches[i].code,
+					    (val & iqs62x_events[flag].mask) ==
+					    iqs62x_events[flag].val);
+		}
+
+		input_sync(iqs62x_keys->input);
+		break;
+
+	case IQS624_PROD_NUM:
+		event_mask_reg = IQS624_HALL_UI;
+
+		/*
+		 * Interval change events represent keys and are unmasked if
+		 * either wheel movement flag is mapped to a valid keycode.
+		 */
+		if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP] != KEY_RESERVED)
+			event_mask |= IQS624_HALL_UI_INT_EVENT;
+
+		if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN] != KEY_RESERVED)
+			event_mask |= IQS624_HALL_UI_INT_EVENT;
+
+		ret = regmap_read(iqs62x->map, iqs62x->dev_desc->interval,
+				  &val);
+		if (ret)
+			return ret;
+
+		iqs62x_keys->interval = val;
+		break;
+
+	default:
+		return 0;
+	}
+
+	return regmap_update_bits(iqs62x->map, event_mask_reg, event_mask, 0);
+}
+
+static int iqs62x_keys_notifier(struct notifier_block *notifier,
+				unsigned long event_flags, void *context)
+{
+	struct iqs62x_event_data *event_data = context;
+	struct iqs62x_keys_private *iqs62x_keys;
+	int ret, i;
+
+	iqs62x_keys = container_of(notifier, struct iqs62x_keys_private,
+				   notifier);
+
+	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
+		ret = iqs62x_keys_init(iqs62x_keys);
+		if (ret) {
+			dev_err(iqs62x_keys->input->dev.parent,
+				"Failed to re-initialize device: %d\n", ret);
+			return NOTIFY_BAD;
+		}
+
+		return NOTIFY_OK;
+	}
+
+	for (i = 0; i < iqs62x_keys->keycodemax; i++) {
+		if (iqs62x_events[i].reg == IQS62X_EVENT_WHEEL &&
+		    event_data->interval == iqs62x_keys->interval)
+			continue;
+
+		input_report_key(iqs62x_keys->input, iqs62x_keys->keycode[i],
+				 event_flags & BIT(i));
+	}
+
+	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++)
+		if (iqs62x_keys->switches[i].enabled)
+			input_report_switch(iqs62x_keys->input,
+					    iqs62x_keys->switches[i].code,
+					    event_flags &
+					    BIT(iqs62x_keys->switches[i].flag));
+
+	input_sync(iqs62x_keys->input);
+
+	if (event_data->interval == iqs62x_keys->interval)
+		return NOTIFY_OK;
+
+	/*
+	 * Each frame contains at most one wheel event (up or down), in which
+	 * case a complementary release cycle is emulated.
+	 */
+	if (event_flags & BIT(IQS62X_EVENT_WHEEL_UP)) {
+		input_report_key(iqs62x_keys->input,
+				 iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP],
+				 0);
+		input_sync(iqs62x_keys->input);
+	} else if (event_flags & BIT(IQS62X_EVENT_WHEEL_DN)) {
+		input_report_key(iqs62x_keys->input,
+				 iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN],
+				 0);
+		input_sync(iqs62x_keys->input);
+	}
+
+	iqs62x_keys->interval = event_data->interval;
+
+	return NOTIFY_OK;
+}
+
+static int iqs62x_keys_probe(struct platform_device *pdev)
+{
+	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
+	struct iqs62x_keys_private *iqs62x_keys;
+	struct input_dev *input;
+	int ret, i;
+
+	iqs62x_keys = devm_kzalloc(&pdev->dev, sizeof(*iqs62x_keys),
+				   GFP_KERNEL);
+	if (!iqs62x_keys)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, iqs62x_keys);
+
+	ret = iqs62x_keys_parse_prop(pdev, iqs62x_keys);
+	if (ret)
+		return ret;
+
+	input = devm_input_allocate_device(&pdev->dev);
+	if (!input)
+		return -ENOMEM;
+
+	input->keycodemax = iqs62x_keys->keycodemax;
+	input->keycode = iqs62x_keys->keycode;
+	input->keycodesize = sizeof(*iqs62x_keys->keycode);
+
+	input->name = iqs62x->dev_desc->dev_name;
+	input->id.bustype = BUS_I2C;
+
+	__set_bit(EV_KEY, input->evbit);
+
+	for (i = 0; i < iqs62x_keys->keycodemax; i++)
+		__set_bit(iqs62x_keys->keycode[i], input->keybit);
+
+	__clear_bit(KEY_RESERVED, input->keybit);
+
+	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++)
+		if (iqs62x_keys->switches[i].enabled) {
+			__set_bit(EV_SW, input->evbit);
+			__set_bit(iqs62x_keys->switches[i].code, input->swbit);
+		}
+
+	iqs62x_keys->iqs62x = iqs62x;
+	iqs62x_keys->input = input;
+
+	ret = iqs62x_keys_init(iqs62x_keys);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to initialize device: %d\n", ret);
+		return ret;
+	}
+
+	ret = input_register_device(iqs62x_keys->input);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register device: %d\n", ret);
+		return ret;
+	}
+
+	iqs62x_keys->notifier.notifier_call = iqs62x_keys_notifier;
+	ret = blocking_notifier_chain_register(&iqs62x_keys->iqs62x->nh,
+					       &iqs62x_keys->notifier);
+	if (ret)
+		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
+
+	return ret;
+}
+
+static int iqs62x_keys_remove(struct platform_device *pdev)
+{
+	struct iqs62x_keys_private *iqs62x_keys = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = blocking_notifier_chain_unregister(&iqs62x_keys->iqs62x->nh,
+						 &iqs62x_keys->notifier);
+	if (ret)
+		dev_err(&pdev->dev, "Failed to unregister notifier: %d\n", ret);
+
+	return ret;
+}
+
+static struct platform_driver iqs62x_keys_platform_driver = {
+	.driver = {
+		.name = IQS62X_DRV_NAME_KEYS,
+	},
+	.probe = iqs62x_keys_probe,
+	.remove = iqs62x_keys_remove,
+};
+module_platform_driver(iqs62x_keys_platform_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Keys and Switches");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS62X_DRV_NAME_KEYS);
--
2.7.4


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

* [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2019-12-09  0:38 [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (2 preceding siblings ...)
  2019-12-09  0:38 ` [PATCH v2 3/7] input: keyboard: " Jeff LaBundy
@ 2019-12-09  0:38 ` Jeff LaBundy
  2019-12-09  7:32   ` Uwe Kleine-König
  2019-12-09  0:38 ` [PATCH v2 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-09  0:38 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree
  Cc: linux-input, 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>
---
Changes in v2:
  - Merged 'Copyright' and 'Author' lines into one in introductory comments
  - Added 'Limitations' section to introductory comments
  - Replaced 'error' with 'ret' throughout
  - Added const qualifier to state argument of iqs620_pwm_apply and removed all
    modifications to the variable's contents
  - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
    polarity is inverted or the requested period is below 1 ms, respectively
  - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
  - Added iqs620_pwm_get_state
  - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
  - Moved notifier unregistration to already present iqs620_pwm_remove, which
    eliminated the need for a device-managed action and ready flag
  - Added a comment in iqs620_pwm_probe to explain the order of operations
  - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST

 drivers/pwm/Kconfig       |  10 +++
 drivers/pwm/Makefile      |   1 +
 drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 217 insertions(+)
 create mode 100644 drivers/pwm/pwm-iqs620a.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index bd21655..60bcf6c 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -222,6 +222,16 @@ config PWM_IMX_TPM
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-imx-tpm.

+config PWM_IQS620A
+	tristate "Azoteq IQS620A PWM support"
+	depends on MFD_IQS62X || COMPILE_TEST
+	help
+	  Generic PWM framework driver for the Azoteq IQS620A multi-function
+	  sensor.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called pwm-iqs620a.
+
 config PWM_JZ4740
 	tristate "Ingenic JZ47xx PWM support"
 	depends on MACH_INGENIC
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 9a47507..a59c710 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
 obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
 obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
 obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
+obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
 obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
new file mode 100644
index 0000000..1ea11b9
--- /dev/null
+++ b/drivers/pwm/pwm-iqs620a.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS620A PWM Generator
+ *
+ * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
+ *
+ * Limitations:
+ * - The period is not guaranteed to run to completion when the duty cycle is
+ *   changed or the output is disabled.
+ * - The period is fixed to 1 ms.
+ */
+
+#include <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;
+};
+
+static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			    const struct pwm_state *state)
+{
+	struct iqs620_pwm_private *iqs620_pwm;
+	struct iqs62x_core *iqs62x;
+	unsigned int pwm_out = 0;
+	int duty_scale, ret;
+
+	if (state->polarity != PWM_POLARITY_NORMAL)
+		return -ENOTSUPP;
+
+	if (state->period < IQS620_PWM_PERIOD_NS)
+		return -EINVAL;
+
+	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
+	iqs62x = iqs620_pwm->iqs62x;
+
+	duty_scale = DIV_ROUND_CLOSEST(state->duty_cycle * 256,
+				       IQS620_PWM_PERIOD_NS);
+
+	if (duty_scale) {
+		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
+				   min(duty_scale - 1, 0xFF));
+		if (ret)
+			return ret;
+
+		if (state->enabled)
+			pwm_out = IQS620_PWR_SETTINGS_PWM_OUT;
+	}
+
+	return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
+				  IQS620_PWR_SETTINGS_PWM_OUT, pwm_out);
+}
+
+static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+				 struct pwm_state *state)
+{
+	struct iqs620_pwm_private *iqs620_pwm;
+	struct iqs62x_core *iqs62x;
+	unsigned int val;
+	int ret;
+
+	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
+	iqs62x = iqs620_pwm->iqs62x;
+
+	ret = regmap_read(iqs62x->map, IQS620_PWR_SETTINGS, &val);
+	if (ret)
+		goto err_out;
+	state->enabled = val & IQS620_PWR_SETTINGS_PWM_OUT;
+
+	ret = regmap_read(iqs62x->map, IQS620_PWM_DUTY_CYCLE, &val);
+	if (ret)
+		goto err_out;
+	state->duty_cycle = DIV_ROUND_CLOSEST((val + 1) * IQS620_PWM_PERIOD_NS,
+					      256);
+	state->period = IQS620_PWM_PERIOD_NS;
+
+err_out:
+	if (ret)
+		dev_err(iqs620_pwm->chip.dev, "Failed to get state: %d\n", ret);
+}
+
+static int iqs620_pwm_notifier(struct notifier_block *notifier,
+			       unsigned long event_flags, void *context)
+{
+	struct iqs620_pwm_private *iqs620_pwm;
+	struct pwm_state state;
+	int ret;
+
+	if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))
+		return NOTIFY_DONE;
+
+	iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
+				  notifier);
+	pwm_get_state(&iqs620_pwm->chip.pwms[0], &state);
+
+	ret = iqs620_pwm_apply(&iqs620_pwm->chip,
+			       &iqs620_pwm->chip.pwms[0], &state);
+	if (ret) {
+		dev_err(iqs620_pwm->chip.dev,
+			"Failed to re-initialize device: %d\n", ret);
+		return NOTIFY_BAD;
+	}
+
+	return NOTIFY_OK;
+}
+
+static const struct pwm_ops iqs620_pwm_ops = {
+	.apply = iqs620_pwm_apply,
+	.get_state = iqs620_pwm_get_state,
+	.owner = THIS_MODULE,
+};
+
+static int iqs620_pwm_probe(struct platform_device *pdev)
+{
+	struct iqs620_pwm_private *iqs620_pwm;
+	int ret1, ret2;
+
+	iqs620_pwm = devm_kzalloc(&pdev->dev, sizeof(*iqs620_pwm), GFP_KERNEL);
+	if (!iqs620_pwm)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, iqs620_pwm);
+	iqs620_pwm->iqs62x = dev_get_drvdata(pdev->dev.parent);
+
+	iqs620_pwm->chip.dev = &pdev->dev;
+	iqs620_pwm->chip.ops = &iqs620_pwm_ops;
+	iqs620_pwm->chip.base = -1;
+	iqs620_pwm->chip.npwm = 1;
+
+	ret1 = pwmchip_add(&iqs620_pwm->chip);
+	if (ret1) {
+		dev_err(&pdev->dev, "Failed to add device: %d\n", ret1);
+		return ret1;
+	}
+
+	/*
+	 * Since iqs620_pwm_notifier uses iqs620_pwm->chip.pwms[], the notifier
+	 * is not registered until pwmchip_add (which allocates that array) has
+	 * been called. If registration fails, the newly added device has to be
+	 * removed because the driver fails to probe and iqs620_pwm_remove will
+	 * never be called.
+	 */
+	iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier;
+	ret1 = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh,
+						&iqs620_pwm->notifier);
+	if (ret1) {
+		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret1);
+
+		ret2 = pwmchip_remove(&iqs620_pwm->chip);
+		if (ret2) {
+			dev_err(&pdev->dev, "Failed to remove device: %d\n",
+				ret2);
+			return ret2;
+		}
+	}
+
+	return ret1;
+}
+
+static int iqs620_pwm_remove(struct platform_device *pdev)
+{
+	struct iqs620_pwm_private *iqs620_pwm = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh,
+						 &iqs620_pwm->notifier);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to unregister notifier: %d\n", ret);
+		return ret;
+	}
+
+	ret = pwmchip_remove(&iqs620_pwm->chip);
+	if (ret)
+		dev_err(&pdev->dev, "Failed to remove device: %d\n", ret);
+
+	return ret;
+}
+
+static struct platform_driver iqs620_pwm_platform_driver = {
+	.driver = {
+		.name = IQS620_DRV_NAME_PWM,
+	},
+	.probe = iqs620_pwm_probe,
+	.remove = iqs620_pwm_remove,
+};
+module_platform_driver(iqs620_pwm_platform_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <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 related	[flat|nested] 30+ messages in thread

* [PATCH v2 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor
  2019-12-09  0:38 [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (3 preceding siblings ...)
  2019-12-09  0:38 ` [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
@ 2019-12-09  0:38 ` Jeff LaBundy
  2019-12-15 16:34   ` Jonathan Cameron
  2019-12-09  0:38 ` [PATCH v2 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Jeff LaBundy
  2019-12-09  0:38 ` [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Jeff LaBundy
  6 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-09  0:38 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree
  Cc: linux-input, 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>
---
Changes in v2:
  - Moved the driver from hwmon to iio
  - Merged 'Copyright' and 'Author' lines into one in introductory comments
  - Replaced 'error' with 'ret' throughout
  - Eliminated tabbed alignment of platform_driver struct members
  - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST

 drivers/iio/temperature/Kconfig         | 10 ++++
 drivers/iio/temperature/Makefile        |  1 +
 drivers/iio/temperature/iqs620at-temp.c | 97 +++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+)
 create mode 100644 drivers/iio/temperature/iqs620at-temp.c

diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig
index e1ccb40..f1f2a14 100644
--- a/drivers/iio/temperature/Kconfig
+++ b/drivers/iio/temperature/Kconfig
@@ -4,6 +4,16 @@
 #
 menu "Temperature sensors"

+config IQS620AT_TEMP
+	tristate "Azoteq IQS620AT temperature sensor"
+	depends on MFD_IQS62X || COMPILE_TEST
+	help
+	  Say Y here if you want to build support for the Azoteq IQS620AT
+	  temperature sensor.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called iqs620at-temp.
+
 config LTC2983
 	tristate "Analog Devices Multi-Sensor Digital Temperature Measurement System"
 	depends on SPI
diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile
index d6b850b..90c1131 100644
--- a/drivers/iio/temperature/Makefile
+++ b/drivers/iio/temperature/Makefile
@@ -3,6 +3,7 @@
 # Makefile for industrial I/O temperature drivers
 #

+obj-$(CONFIG_IQS620AT_TEMP) += iqs620at-temp.o
 obj-$(CONFIG_LTC2983) += ltc2983.o
 obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o
 obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o
diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c
new file mode 100644
index 0000000..d20cb6ad
--- /dev/null
+++ b/drivers/iio/temperature/iqs620at-temp.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS620AT Temperature Sensor
+ *
+ * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
+ */
+
+#include <linux/device.h>
+#include <linux/iio/iio.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
+
+#define IQS620_TEMP_SCALE			1000
+#define IQS620_TEMP_OFFSET			(-100)
+
+static int iqs620_temp_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct iqs62x_core *iqs62x = iio_device_get_drvdata(indio_dev);
+	int ret;
+	__le16 val_buf;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = regmap_raw_read(iqs62x->map, IQS620_TEMP_UI_OUT, &val_buf,
+				      sizeof(val_buf));
+		if (ret)
+			return ret;
+
+		*val = le16_to_cpu(val_buf);
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		*val = IQS620_TEMP_SCALE;
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_OFFSET:
+		*val = IQS620_TEMP_OFFSET;
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info iqs620_temp_info = {
+	.read_raw = &iqs620_temp_read_raw,
+};
+
+static const struct iio_chan_spec iqs620_temp_channels[] = {
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_OFFSET),
+	},
+};
+
+static int iqs620_temp_probe(struct platform_device *pdev)
+{
+	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
+	struct iio_dev *indio_dev;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, 0);
+	if (!indio_dev)
+		return -ENOMEM;
+
+	iio_device_set_drvdata(indio_dev, iqs62x);
+
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->channels = iqs620_temp_channels;
+	indio_dev->num_channels = ARRAY_SIZE(iqs620_temp_channels);
+	indio_dev->name = iqs62x->dev_desc->dev_name;
+	indio_dev->info = &iqs620_temp_info;
+
+	return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static struct platform_driver iqs620_temp_platform_driver = {
+	.driver = {
+		.name = IQS620_DRV_NAME_TEMP,
+	},
+	.probe = iqs620_temp_probe,
+};
+module_platform_driver(iqs620_temp_platform_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <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 related	[flat|nested] 30+ messages in thread

* [PATCH v2 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors
  2019-12-09  0:38 [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (4 preceding siblings ...)
  2019-12-09  0:38 ` [PATCH v2 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
@ 2019-12-09  0:38 ` Jeff LaBundy
  2019-12-15 16:47   ` Jonathan Cameron
  2019-12-09  0:38 ` [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Jeff LaBundy
  6 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-09  0:38 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree
  Cc: linux-input, 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 and IQS622 ambient light
sensors, both of which can report a four-bit representation of ambient
light intensity.

The IQS621 can additionally report illuminace directly in units of lux,
while the IQS622 can report a four-bit representation of infrared light
intensity. Furthermore, the IQS622 can report a unitless measurement of
a target's proximity to the device.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
---
Changes in v2:
  - Merged 'Copyright' and 'Author' lines into one in introductory comments
  - Replaced 'error' with 'ret' throughout
  - Merged support for the closely related IQS622 (formerly represented by
    a separate iio/proximity driver)
  - Added support for unitless ambient light intensity (IQS621 and IQS622)
    and infrared light intensity (IQS622 only)
  - Moved the read of IQS621_ALS_FLAGS to iqs621_als_write_event_config to
    account for the fact that IQS621_ALS_FLAGS may have changed in between
    having first been read in iqs621_als_init and the time at which events
    are enabled, thereby eliminating the need to call iqs621_als_init from
    iqs621_als_probe
  - Refactored the logic in iqs621_als_notifier and added a lock to safely
    evaluate variables that may change in response to user action
  - Added locks to iqs621_als_read_event_config/value to account for cases in
    which the corresponding hardware state is in the process of being updated
  - Refactored the logic in iqs621_als_read/write_event_value and removed all
    #defines that could instead be represented by simple math
  - Based the decision whether to select the IQS622 IR touch vs. proximity
    threshold on the single proximity threshold written by user space, and
    added a comment to describe the difference between either threshold
  - Replaced IIO_CHAN_INFO_RAW with IIO_CHAN_INFO_PROCESSED for the IIO_LIGHT
    channel (IQS621 only)
  - Removed devm_add_action_or_reset failure message
  - Eliminated tabbed alignment of platform_driver struct members
  - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST

 drivers/iio/light/Kconfig      |  10 +
 drivers/iio/light/Makefile     |   1 +
 drivers/iio/light/iqs621-als.c | 614 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 625 insertions(+)
 create mode 100644 drivers/iio/light/iqs621-als.c

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 9968f98..baf7958b 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -173,6 +173,16 @@ config GP2AP020A00F
 	  To compile this driver as a module, choose M here: the
 	  module will be called gp2ap020a00f.

+config IQS621_ALS
+	tristate "Azoteq IQS621/622 ambient light sensors"
+	depends on MFD_IQS62X || COMPILE_TEST
+	help
+	  Say Y here if you want to build support for the Azoteq IQS621
+	  and IQS622 ambient light sensors.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called iqs621-als.
+
 config SENSORS_ISL29018
 	tristate "Intersil 29018 light and proximity sensor"
 	depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index c98d1ce..988e8f4 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o
 obj-$(CONFIG_GP2AP020A00F)	+= gp2ap020a00f.o
 obj-$(CONFIG_HID_SENSOR_ALS)	+= hid-sensor-als.o
 obj-$(CONFIG_HID_SENSOR_PROX)	+= hid-sensor-prox.o
+obj-$(CONFIG_IQS621_ALS)	+= iqs621-als.o
 obj-$(CONFIG_SENSORS_ISL29018)	+= isl29018.o
 obj-$(CONFIG_SENSORS_ISL29028)	+= isl29028.o
 obj-$(CONFIG_ISL29125)		+= isl29125.o
diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c
new file mode 100644
index 0000000..a4dd718
--- /dev/null
+++ b/drivers/iio/light/iqs621-als.c
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS621/622 Ambient Light Sensors
+ *
+ * Copyright (C) 2019 Jeff LaBundy <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_LIGHT			BIT(7)
+#define IQS621_ALS_FLAGS_RANGE			GENMASK(3, 0)
+
+#define IQS621_ALS_UI_OUT			0x17
+
+#define IQS621_ALS_THRESH_DARK			0x80
+#define IQS621_ALS_THRESH_LIGHT			0x81
+
+#define IQS622_IR_RANGE				0x15
+#define IQS622_IR_FLAGS				0x16
+#define IQS622_IR_FLAGS_TOUCH			BIT(1)
+#define IQS622_IR_FLAGS_PROX			BIT(0)
+
+#define IQS622_IR_UI_OUT			0x17
+
+#define IQS622_IR_THRESH_PROX			0x91
+#define IQS622_IR_THRESH_TOUCH			0x92
+
+struct iqs621_als_private {
+	struct iqs62x_core *iqs62x;
+	struct notifier_block notifier;
+	struct mutex lock;
+	bool light_en;
+	bool range_en;
+	bool prox_en;
+	u8 als_flags;
+	u8 ir_flags_mask;
+	u8 ir_flags;
+	u8 thresh_light;
+	u8 thresh_dark;
+	u8 thresh_prox;
+};
+
+static int iqs621_als_init(struct iqs621_als_private *iqs621_als)
+{
+	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
+	unsigned int event_mask = 0;
+	int ret;
+
+	switch (iqs621_als->ir_flags_mask) {
+	case IQS622_IR_FLAGS_TOUCH:
+		ret = regmap_write(iqs62x->map, IQS622_IR_THRESH_TOUCH,
+				   iqs621_als->thresh_prox);
+		break;
+
+	case IQS622_IR_FLAGS_PROX:
+		ret = regmap_write(iqs62x->map, IQS622_IR_THRESH_PROX,
+				   iqs621_als->thresh_prox);
+		break;
+
+	default:
+		ret = regmap_write(iqs62x->map, IQS621_ALS_THRESH_LIGHT,
+				   iqs621_als->thresh_light);
+		if (ret)
+			return ret;
+
+		ret = regmap_write(iqs62x->map, IQS621_ALS_THRESH_DARK,
+				   iqs621_als->thresh_dark);
+	}
+
+	if (ret)
+		return ret;
+
+	if (iqs621_als->light_en || iqs621_als->range_en)
+		event_mask |= iqs62x->dev_desc->als_mask;
+
+	if (iqs621_als->prox_en)
+		event_mask |= iqs62x->dev_desc->ir_mask;
+
+	return regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK,
+				  event_mask, 0);
+}
+
+static int iqs621_als_notifier(struct notifier_block *notifier,
+			       unsigned long event_flags, void *context)
+{
+	struct iqs62x_event_data *event_data = context;
+	struct iqs621_als_private *iqs621_als;
+	struct iio_dev *indio_dev;
+	bool light_new, light_old;
+	bool prox_new, prox_old;
+	u8 range_new, range_old;
+	s64 timestamp;
+	int ret;
+
+	iqs621_als = container_of(notifier, struct iqs621_als_private,
+				  notifier);
+	indio_dev = iio_priv_to_dev(iqs621_als);
+	timestamp = iio_get_time_ns(indio_dev);
+
+	mutex_lock(&iqs621_als->lock);
+
+	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
+		ret = iqs621_als_init(iqs621_als);
+		if (ret) {
+			dev_err(indio_dev->dev.parent,
+				"Failed to re-initialize device: %d\n", ret);
+			ret = NOTIFY_BAD;
+		} else {
+			ret = NOTIFY_OK;
+		}
+
+		goto err_mutex;
+	}
+
+	if (!iqs621_als->light_en && !iqs621_als->range_en &&
+	    !iqs621_als->prox_en) {
+		ret = NOTIFY_DONE;
+		goto err_mutex;
+	}
+
+	/* IQS621 only */
+	light_new = event_data->als_flags & IQS621_ALS_FLAGS_LIGHT;
+	light_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_LIGHT;
+
+	if (iqs621_als->light_en && light_new && !light_old)
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_RISING),
+			       timestamp);
+	else if (iqs621_als->light_en && !light_new && light_old)
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_FALLING),
+			       timestamp);
+
+	/* IQS621 and IQS622 */
+	range_new = event_data->als_flags & IQS621_ALS_FLAGS_RANGE;
+	range_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_RANGE;
+
+	if (iqs621_als->range_en && (range_new > range_old))
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+						    IIO_EV_TYPE_CHANGE,
+						    IIO_EV_DIR_RISING),
+			       timestamp);
+	else if (iqs621_als->range_en && (range_new < range_old))
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+						    IIO_EV_TYPE_CHANGE,
+						    IIO_EV_DIR_FALLING),
+			       timestamp);
+
+	/* IQS622 only */
+	prox_new = event_data->ir_flags & iqs621_als->ir_flags_mask;
+	prox_old = iqs621_als->ir_flags & iqs621_als->ir_flags_mask;
+
+	if (iqs621_als->prox_en && prox_new && !prox_old)
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_RISING),
+			       timestamp);
+	else if (iqs621_als->prox_en && !prox_new && prox_old)
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_FALLING),
+			       timestamp);
+
+	iqs621_als->als_flags = event_data->als_flags;
+	iqs621_als->ir_flags = event_data->ir_flags;
+	ret = NOTIFY_OK;
+
+err_mutex:
+	mutex_unlock(&iqs621_als->lock);
+
+	return ret;
+}
+
+static void iqs621_als_notifier_unregister(void *context)
+{
+	struct iqs621_als_private *iqs621_als = context;
+	struct iio_dev *indio_dev = iio_priv_to_dev(iqs621_als);
+	int ret;
+
+	ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh,
+						 &iqs621_als->notifier);
+	if (ret)
+		dev_err(indio_dev->dev.parent,
+			"Failed to unregister notifier: %d\n", ret);
+}
+
+static int iqs621_als_read_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int *val, int *val2, long mask)
+{
+	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
+	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
+	int ret;
+	__le16 val_buf;
+
+	switch (chan->type) {
+	case IIO_INTENSITY:
+		ret = regmap_read(iqs62x->map, chan->address, val);
+		if (ret)
+			return ret;
+
+		*val &= IQS621_ALS_FLAGS_RANGE;
+		return IIO_VAL_INT;
+
+	case IIO_PROXIMITY:
+	case IIO_LIGHT:
+		ret = regmap_raw_read(iqs62x->map, chan->address, &val_buf,
+				      sizeof(val_buf));
+		if (ret)
+			return ret;
+
+		*val = le16_to_cpu(val_buf);
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int iqs621_als_read_event_config(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					enum iio_event_type type,
+					enum iio_event_direction dir)
+{
+	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&iqs621_als->lock);
+
+	switch (chan->type) {
+	case IIO_LIGHT:
+		ret = iqs621_als->light_en;
+		break;
+
+	case IIO_INTENSITY:
+		ret = iqs621_als->range_en;
+		break;
+
+	case IIO_PROXIMITY:
+		ret = iqs621_als->prox_en;
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&iqs621_als->lock);
+
+	return ret;
+}
+
+static int iqs621_als_write_event_config(struct iio_dev *indio_dev,
+					 const struct iio_chan_spec *chan,
+					 enum iio_event_type type,
+					 enum iio_event_direction dir,
+					 int state)
+{
+	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
+	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
+	unsigned int val;
+	int ret;
+
+	mutex_lock(&iqs621_als->lock);
+
+	ret = regmap_read(iqs62x->map, iqs62x->dev_desc->als_flags, &val);
+	if (ret)
+		goto err_mutex;
+	iqs621_als->als_flags = val;
+
+	switch (chan->type) {
+	case IIO_LIGHT:
+		ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK,
+					 iqs62x->dev_desc->als_mask,
+					 iqs621_als->range_en | state ? 0 :
+									0xFF);
+		if (!ret)
+			iqs621_als->light_en = state;
+		break;
+
+	case IIO_INTENSITY:
+		ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK,
+					 iqs62x->dev_desc->als_mask,
+					 iqs621_als->light_en | state ? 0 :
+									0xFF);
+		if (!ret)
+			iqs621_als->range_en = state;
+		break;
+
+	case IIO_PROXIMITY:
+		ret = regmap_read(iqs62x->map, IQS622_IR_FLAGS, &val);
+		if (ret)
+			goto err_mutex;
+		iqs621_als->ir_flags = val;
+
+		ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK,
+					 iqs62x->dev_desc->ir_mask,
+					 state ? 0 : 0xFF);
+		if (!ret)
+			iqs621_als->prox_en = state;
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+err_mutex:
+	mutex_unlock(&iqs621_als->lock);
+
+	return ret;
+}
+
+static int iqs621_als_read_event_value(struct iio_dev *indio_dev,
+				       const struct iio_chan_spec *chan,
+				       enum iio_event_type type,
+				       enum iio_event_direction dir,
+				       enum iio_event_info info,
+				       int *val, int *val2)
+{
+	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
+	int ret = IIO_VAL_INT;
+
+	mutex_lock(&iqs621_als->lock);
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		*val = iqs621_als->thresh_light * 16;
+		break;
+
+	case IIO_EV_DIR_FALLING:
+		*val = iqs621_als->thresh_dark * 4;
+		break;
+
+	case IIO_EV_DIR_EITHER:
+		if (iqs621_als->ir_flags_mask == IQS622_IR_FLAGS_TOUCH)
+			*val = iqs621_als->thresh_prox * 4;
+		else
+			*val = iqs621_als->thresh_prox;
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&iqs621_als->lock);
+
+	return ret;
+}
+
+static int iqs621_als_write_event_value(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					enum iio_event_type type,
+					enum iio_event_direction dir,
+					enum iio_event_info info,
+					int val, int val2)
+{
+	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
+	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
+	unsigned int thresh_reg, thresh_val;
+	u8 ir_flags_mask, *thresh_cache;
+	int ret = -EINVAL;
+
+	mutex_lock(&iqs621_als->lock);
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		thresh_reg = IQS621_ALS_THRESH_LIGHT;
+		thresh_val = val / 16;
+
+		thresh_cache = &iqs621_als->thresh_light;
+		ir_flags_mask = 0;
+		break;
+
+	case IIO_EV_DIR_FALLING:
+		thresh_reg = IQS621_ALS_THRESH_DARK;
+		thresh_val = val / 4;
+
+		thresh_cache = &iqs621_als->thresh_dark;
+		ir_flags_mask = 0;
+		break;
+
+	case IIO_EV_DIR_EITHER:
+		/*
+		 * The IQS622 supports two detection thresholds, both measured
+		 * in the same arbitrary units reported by read_raw: proximity
+		 * (0 through 255 in steps of 1), and touch (0 through 1020 in
+		 * steps of 4).
+		 *
+		 * Based on the single detection threshold chosen by the user,
+		 * select the hardware threshold that gives the best trade-off
+		 * between range and resolution.
+		 *
+		 * By default, the close-range (but coarse) touch threshold is
+		 * chosen during probe.
+		 */
+		switch (val) {
+		case 0 ... 255:
+			thresh_reg = IQS622_IR_THRESH_PROX;
+			thresh_val = val;
+
+			ir_flags_mask = IQS622_IR_FLAGS_PROX;
+			break;
+
+		case 256 ... 1020:
+			thresh_reg = IQS622_IR_THRESH_TOUCH;
+			thresh_val = val / 4;
+
+			ir_flags_mask = IQS622_IR_FLAGS_TOUCH;
+			break;
+
+		default:
+			goto err_mutex;
+		}
+
+		thresh_cache = &iqs621_als->thresh_prox;
+		break;
+
+	default:
+		goto err_mutex;
+	}
+
+	if (thresh_val > 0xFF)
+		goto err_mutex;
+
+	ret = regmap_write(iqs62x->map, thresh_reg, thresh_val);
+	if (ret)
+		goto err_mutex;
+
+	*thresh_cache = thresh_val;
+	iqs621_als->ir_flags_mask = ir_flags_mask;
+
+err_mutex:
+	mutex_unlock(&iqs621_als->lock);
+
+	return ret;
+}
+
+static const struct iio_info iqs621_als_info = {
+	.read_raw = &iqs621_als_read_raw,
+	.read_event_config = iqs621_als_read_event_config,
+	.write_event_config = iqs621_als_write_event_config,
+	.read_event_value = iqs621_als_read_event_value,
+	.write_event_value = iqs621_als_write_event_value,
+};
+
+static const struct iio_event_spec iqs621_als_range_events[] = {
+	{
+		.type = IIO_EV_TYPE_CHANGE,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_event_spec iqs621_als_light_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
+	},
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	},
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	},
+};
+
+static const struct iio_chan_spec iqs621_als_channels[] = {
+	{
+		.type = IIO_INTENSITY,
+		.address = IQS621_ALS_FLAGS,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.event_spec = iqs621_als_range_events,
+		.num_event_specs = ARRAY_SIZE(iqs621_als_range_events),
+	},
+	{
+		.type = IIO_LIGHT,
+		.address = IQS621_ALS_UI_OUT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+		.event_spec = iqs621_als_light_events,
+		.num_event_specs = ARRAY_SIZE(iqs621_als_light_events),
+	},
+};
+
+static const struct iio_event_spec iqs622_als_prox_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+				 BIT(IIO_EV_INFO_VALUE),
+	},
+};
+
+static const struct iio_chan_spec iqs622_als_channels[] = {
+	{
+		.type = IIO_INTENSITY,
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.address = IQS622_ALS_FLAGS,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.event_spec = iqs621_als_range_events,
+		.num_event_specs = ARRAY_SIZE(iqs621_als_range_events),
+		.modified = true,
+	},
+	{
+		.type = IIO_INTENSITY,
+		.channel2 = IIO_MOD_LIGHT_IR,
+		.address = IQS622_IR_RANGE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.modified = true,
+	},
+	{
+		.type = IIO_PROXIMITY,
+		.address = IQS622_IR_UI_OUT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.event_spec = iqs622_als_prox_events,
+		.num_event_specs = ARRAY_SIZE(iqs622_als_prox_events),
+	},
+};
+
+static int iqs621_als_probe(struct platform_device *pdev)
+{
+	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
+	struct iqs621_als_private *iqs621_als;
+	struct iio_dev *indio_dev;
+	unsigned int val;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	iqs621_als = iio_priv(indio_dev);
+	iqs621_als->iqs62x = iqs62x;
+
+	if (iqs62x->dev_desc->prod_num == IQS622_PROD_NUM) {
+		ret = regmap_read(iqs62x->map, IQS622_IR_THRESH_TOUCH, &val);
+		if (ret)
+			return ret;
+		iqs621_als->thresh_prox = val;
+		iqs621_als->ir_flags_mask = IQS622_IR_FLAGS_TOUCH;
+
+		indio_dev->channels = iqs622_als_channels;
+		indio_dev->num_channels = ARRAY_SIZE(iqs622_als_channels);
+	} else {
+		ret = regmap_read(iqs62x->map, IQS621_ALS_THRESH_LIGHT, &val);
+		if (ret)
+			return ret;
+		iqs621_als->thresh_light = val;
+
+		ret = regmap_read(iqs62x->map, IQS621_ALS_THRESH_DARK, &val);
+		if (ret)
+			return ret;
+		iqs621_als->thresh_dark = val;
+
+		indio_dev->channels = iqs621_als_channels;
+		indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels);
+	}
+
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->name = iqs62x->dev_desc->dev_name;
+	indio_dev->info = &iqs621_als_info;
+
+	mutex_init(&iqs621_als->lock);
+
+	iqs621_als->notifier.notifier_call = iqs621_als_notifier;
+	ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh,
+					       &iqs621_als->notifier);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(&pdev->dev,
+				       iqs621_als_notifier_unregister,
+				       iqs621_als);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static struct platform_driver iqs621_als_platform_driver = {
+	.driver = {
+		.name = IQS621_DRV_NAME_ALS,
+	},
+	.probe = iqs621_als_probe,
+};
+module_platform_driver(iqs621_als_platform_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS621/622 Ambient Light Sensors");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS621_DRV_NAME_ALS);
--
2.7.4


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

* [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors
  2019-12-09  0:38 [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
                   ` (5 preceding siblings ...)
  2019-12-09  0:38 ` [PATCH v2 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Jeff LaBundy
@ 2019-12-09  0:38 ` Jeff LaBundy
  2019-12-15 16:53   ` Jonathan Cameron
  6 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-09  0:38 UTC (permalink / raw)
  To: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree
  Cc: linux-input, 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>
---
Changes in v2:
  - Merged 'Copyright' and 'Author' lines into one in introductory comments
  - Replaced 'error' with 'ret' throughout
  - Added iqs624_pos_angle_en and iqs624_pos_angle_get to remove duplicate
    logic previously used throughout
  - Refactored the logic in iqs624_pos_notifier and added a lock to safely
    evaluate variables that may change in response to user action
  - Refactored the logic in iqs624_pos_read_raw
  - Added a lock to iqs624_pos_read_event_config to account for cases in which
    the corresponding hardware state is in the process of being updated
  - Refactored the logic in iqs624_pos_write_event_config and read the initial
    angle in case it changed since having first been read in iqs624_pos_init
  - Removed iqs624_pos_init as its logic has since been absorbed elsewhere
  - Removed devm_add_action_or_reset failure message
  - Eliminated tabbed alignment of platform_driver struct members
  - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST

 drivers/iio/Kconfig               |   1 +
 drivers/iio/Makefile              |   1 +
 drivers/iio/position/Kconfig      |  19 +++
 drivers/iio/position/Makefile     |   7 +
 drivers/iio/position/iqs624-pos.c | 284 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 312 insertions(+)
 create mode 100644 drivers/iio/position/Kconfig
 create mode 100644 drivers/iio/position/Makefile
 create mode 100644 drivers/iio/position/iqs624-pos.c

diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 5bd5185..d5c073a 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -88,6 +88,7 @@ source "drivers/iio/orientation/Kconfig"
 if IIO_TRIGGER
    source "drivers/iio/trigger/Kconfig"
 endif #IIO_TRIGGER
+source "drivers/iio/position/Kconfig"
 source "drivers/iio/potentiometer/Kconfig"
 source "drivers/iio/potentiostat/Kconfig"
 source "drivers/iio/pressure/Kconfig"
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index bff682a..1712011 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -31,6 +31,7 @@ obj-y += light/
 obj-y += magnetometer/
 obj-y += multiplexer/
 obj-y += orientation/
+obj-y += position/
 obj-y += potentiometer/
 obj-y += potentiostat/
 obj-y += pressure/
diff --git a/drivers/iio/position/Kconfig b/drivers/iio/position/Kconfig
new file mode 100644
index 0000000..eda67f0
--- /dev/null
+++ b/drivers/iio/position/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Linear and angular position sensors
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Linear and angular position sensors"
+
+config IQS624_POS
+	tristate "Azoteq IQS624/625 angular position sensors"
+	depends on MFD_IQS62X || COMPILE_TEST
+	help
+	  Say Y here if you want to build support for the Azoteq IQS624
+	  and IQS625 angular position sensors.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called iqs624-pos.
+
+endmenu
diff --git a/drivers/iio/position/Makefile b/drivers/iio/position/Makefile
new file mode 100644
index 0000000..3cbe7a7
--- /dev/null
+++ b/drivers/iio/position/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for IIO linear and angular position sensors
+#
+
+# When adding new entries keep the list in alphabetical order
+
+obj-$(CONFIG_IQS624_POS)	+= iqs624-pos.o
diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c
new file mode 100644
index 0000000..af629bf5
--- /dev/null
+++ b/drivers/iio/position/iqs624-pos.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS624/625 Angular Position Sensors
+ *
+ * Copyright (C) 2019 Jeff LaBundy <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 angle_en;
+	u16 angle;
+};
+
+static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en)
+{
+	unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT;
+
+	/*
+	 * The IQS625 reports angular position in the form of coarse intervals,
+	 * so only interval change events are unmasked. Conversely, the IQS624
+	 * reports angular position down to one degree of resolution, so wheel
+	 * movement events are unmasked instead.
+	 */
+	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
+		event_mask = IQS624_HALL_UI_INT_EVENT;
+
+	return regmap_update_bits(iqs62x->map, IQS624_HALL_UI, event_mask,
+				  angle_en ? 0 : 0xFF);
+}
+
+static int iqs624_pos_notifier(struct notifier_block *notifier,
+			       unsigned long event_flags, void *context)
+{
+	struct iqs62x_event_data *event_data = context;
+	struct iqs624_pos_private *iqs624_pos;
+	struct iqs62x_core *iqs62x;
+	struct iio_dev *indio_dev;
+	u16 angle = event_data->ui_data;
+	s64 timestamp;
+	int ret;
+
+	iqs624_pos = container_of(notifier, struct iqs624_pos_private,
+				  notifier);
+	indio_dev = iio_priv_to_dev(iqs624_pos);
+	timestamp = iio_get_time_ns(indio_dev);
+
+	iqs62x = iqs624_pos->iqs62x;
+	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
+		angle = event_data->interval;
+
+	mutex_lock(&iqs624_pos->lock);
+
+	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
+		ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en);
+		if (ret) {
+			dev_err(indio_dev->dev.parent,
+				"Failed to re-initialize device: %d\n", ret);
+			ret = NOTIFY_BAD;
+		} else {
+			ret = NOTIFY_OK;
+		}
+	} else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) {
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0,
+						    IIO_EV_TYPE_CHANGE,
+						    IIO_EV_DIR_NONE),
+			       timestamp);
+
+		iqs624_pos->angle = angle;
+		ret = NOTIFY_OK;
+	} else {
+		ret = NOTIFY_DONE;
+	}
+
+	mutex_unlock(&iqs624_pos->lock);
+
+	return ret;
+}
+
+static void iqs624_pos_notifier_unregister(void *context)
+{
+	struct iqs624_pos_private *iqs624_pos = context;
+	struct iio_dev *indio_dev = iio_priv_to_dev(iqs624_pos);
+	int ret;
+
+	ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh,
+						 &iqs624_pos->notifier);
+	if (ret)
+		dev_err(indio_dev->dev.parent,
+			"Failed to unregister notifier: %d\n", ret);
+}
+
+static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val)
+{
+	int ret;
+	__le16 val_buf;
+
+	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
+		return regmap_read(iqs62x->map, iqs62x->dev_desc->interval,
+				   val);
+
+	ret = regmap_raw_read(iqs62x->map, IQS624_POS_DEG_OUT, &val_buf,
+			      sizeof(val_buf));
+	if (ret)
+		return ret;
+
+	*val = le16_to_cpu(val_buf);
+
+	return 0;
+}
+
+static int iqs624_pos_read_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int *val, int *val2, long mask)
+{
+	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
+	struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
+	unsigned int scale = 1;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iqs624_pos_angle_get(iqs62x, val);
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) {
+			ret = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV,
+					  &scale);
+			if (ret)
+				return ret;
+		}
+
+		*val = scale * IQS624_POS_SCALE1;
+		*val2 = IQS624_POS_SCALE2;
+		return IIO_VAL_FRACTIONAL;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int iqs624_pos_read_event_config(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					enum iio_event_type type,
+					enum iio_event_direction dir)
+{
+	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&iqs624_pos->lock);
+	ret = iqs624_pos->angle_en;
+	mutex_unlock(&iqs624_pos->lock);
+
+	return ret;
+}
+
+static int iqs624_pos_write_event_config(struct iio_dev *indio_dev,
+					 const struct iio_chan_spec *chan,
+					 enum iio_event_type type,
+					 enum iio_event_direction dir,
+					 int state)
+{
+	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
+	struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
+	unsigned int val;
+	int ret;
+
+	mutex_lock(&iqs624_pos->lock);
+
+	ret = iqs624_pos_angle_get(iqs62x, &val);
+	if (ret)
+		goto err_mutex;
+
+	ret = iqs624_pos_angle_en(iqs62x, state);
+	if (ret)
+		goto err_mutex;
+
+	iqs624_pos->angle = val;
+	iqs624_pos->angle_en = state;
+
+err_mutex:
+	mutex_unlock(&iqs624_pos->lock);
+
+	return ret;
+}
+
+static const struct iio_info iqs624_pos_info = {
+	.read_raw = &iqs624_pos_read_raw,
+	.read_event_config = iqs624_pos_read_event_config,
+	.write_event_config = iqs624_pos_write_event_config,
+};
+
+static const struct iio_event_spec iqs624_pos_events[] = {
+	{
+		.type = IIO_EV_TYPE_CHANGE,
+		.dir = IIO_EV_DIR_NONE,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_chan_spec iqs624_pos_channels[] = {
+	{
+		.type = IIO_ANGL,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.event_spec = iqs624_pos_events,
+		.num_event_specs = ARRAY_SIZE(iqs624_pos_events),
+	},
+};
+
+static int iqs624_pos_probe(struct platform_device *pdev)
+{
+	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
+	struct iqs624_pos_private *iqs624_pos;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	iqs624_pos = iio_priv(indio_dev);
+	iqs624_pos->iqs62x = iqs62x;
+
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->channels = iqs624_pos_channels;
+	indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels);
+	indio_dev->name = iqs62x->dev_desc->dev_name;
+	indio_dev->info = &iqs624_pos_info;
+
+	mutex_init(&iqs624_pos->lock);
+
+	iqs624_pos->notifier.notifier_call = iqs624_pos_notifier;
+	ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh,
+					       &iqs624_pos->notifier);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(&pdev->dev,
+				       iqs624_pos_notifier_unregister,
+				       iqs624_pos);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static struct platform_driver iqs624_pos_platform_driver = {
+	.driver = {
+		.name = IQS624_DRV_NAME_POS,
+	},
+	.probe = iqs624_pos_probe,
+};
+module_platform_driver(iqs624_pos_platform_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" IQS624_DRV_NAME_POS);
--
2.7.4


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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2019-12-09  0:38 ` [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
@ 2019-12-09  7:32   ` Uwe Kleine-König
  2019-12-10  0:03     ` Jeff LaBundy
  0 siblings, 1 reply; 30+ messages in thread
From: Uwe Kleine-König @ 2019-12-09  7:32 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree,
	linux-input, linux-pwm, knaack.h, lars, pmeerw, linux-iio,
	robh+dt, mark.rutland, kernel

On Mon, Dec 09, 2019 at 12:38:36AM +0000, 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>
> ---
> Changes in v2:
>   - Merged 'Copyright' and 'Author' lines into one in introductory comments
>   - Added 'Limitations' section to introductory comments
>   - Replaced 'error' with 'ret' throughout
>   - Added const qualifier to state argument of iqs620_pwm_apply and removed all
>     modifications to the variable's contents
>   - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
>     polarity is inverted or the requested period is below 1 ms, respectively
>   - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
>   - Added iqs620_pwm_get_state
>   - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
>   - Moved notifier unregistration to already present iqs620_pwm_remove, which
>     eliminated the need for a device-managed action and ready flag
>   - Added a comment in iqs620_pwm_probe to explain the order of operations
>   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> 
>  drivers/pwm/Kconfig       |  10 +++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 217 insertions(+)
>  create mode 100644 drivers/pwm/pwm-iqs620a.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index bd21655..60bcf6c 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -222,6 +222,16 @@ config PWM_IMX_TPM
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-imx-tpm.
> 
> +config PWM_IQS620A
> +	tristate "Azoteq IQS620A PWM support"
> +	depends on MFD_IQS62X || COMPILE_TEST
> +	help
> +	  Generic PWM framework driver for the Azoteq IQS620A multi-function
> +	  sensor.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called pwm-iqs620a.
> +
>  config PWM_JZ4740
>  	tristate "Ingenic JZ47xx PWM support"
>  	depends on MACH_INGENIC
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9a47507..a59c710 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
>  obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> +obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
>  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
> new file mode 100644
> index 0000000..1ea11b9
> --- /dev/null
> +++ b/drivers/pwm/pwm-iqs620a.c
> @@ -0,0 +1,206 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Azoteq IQS620A PWM Generator
> + *
> + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> + *
> + * Limitations:
> + * - The period is not guaranteed to run to completion when the duty cycle is
> + *   changed or the output is disabled.

Do you know more details here? "not guaranteed" means that the new
period starts immediately when duty_cycle or the enabled bit is written?

> + * - The period is fixed to 1 ms.
> + */
> +
> +#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;
> +};
> +
> +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			    const struct pwm_state *state)
> +{
> +	struct iqs620_pwm_private *iqs620_pwm;
> +	struct iqs62x_core *iqs62x;
> +	unsigned int pwm_out = 0;
> +	int duty_scale, ret;
> +
> +	if (state->polarity != PWM_POLARITY_NORMAL)
> +		return -ENOTSUPP;
> +
> +	if (state->period < IQS620_PWM_PERIOD_NS)
> +		return -EINVAL;
> +
> +	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> +	iqs62x = iqs620_pwm->iqs62x;
> +
> +	duty_scale = DIV_ROUND_CLOSEST(state->duty_cycle * 256,
> +				       IQS620_PWM_PERIOD_NS);
> +
> +	if (duty_scale) {
> +		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> +				   min(duty_scale - 1, 0xFF));
> +		if (ret)
> +			return ret;
> +
> +		if (state->enabled)
> +			pwm_out = IQS620_PWR_SETTINGS_PWM_OUT;
> +	}
> +
> +	return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> +				  IQS620_PWR_SETTINGS_PWM_OUT, pwm_out);

A comment explaining the semantic here would be good. I assume
IQS620_PWM_DUTY_CYCLE takes a value between 0 and 255 and the resulting
duty cycle is:

	(IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms

.

If this is right, please use:

	duty_scale = (state->duty_cycle * 256) / IQS620_PWM_PERIOD_NS

Also, when the hardware is running at

	.enabled = 1, .duty_cycle = 1/256 ms, .period = 1ms

and you reconfigure to

	.enabled = 0, .duty_cycle = 1ms, .period = 1ms

the output might be active for > 1/256 ms if the process is preempted
between writing IQS620_PWM_DUTY_CYCLE and IQS620_PWR_SETTINGS_PWM_OUT.

> +}
> +
> +static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +				 struct pwm_state *state)
> +{
> +	struct iqs620_pwm_private *iqs620_pwm;
> +	struct iqs62x_core *iqs62x;
> +	unsigned int val;
> +	int ret;
> +
> +	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> +	iqs62x = iqs620_pwm->iqs62x;
> +
> +	ret = regmap_read(iqs62x->map, IQS620_PWR_SETTINGS, &val);
> +	if (ret)
> +		goto err_out;
> +	state->enabled = val & IQS620_PWR_SETTINGS_PWM_OUT;
> +
> +	ret = regmap_read(iqs62x->map, IQS620_PWM_DUTY_CYCLE, &val);
> +	if (ret)
> +		goto err_out;
> +	state->duty_cycle = DIV_ROUND_CLOSEST((val + 1) * IQS620_PWM_PERIOD_NS,
> +					      256);

Please round up.

> +	state->period = IQS620_PWM_PERIOD_NS;
> +
> +err_out:
> +	if (ret)
> +		dev_err(iqs620_pwm->chip.dev, "Failed to get state: %d\n", ret);
> +}
> +
> +static int iqs620_pwm_notifier(struct notifier_block *notifier,
> +			       unsigned long event_flags, void *context)
> +{
> +	struct iqs620_pwm_private *iqs620_pwm;
> +	struct pwm_state state;
> +	int ret;
> +
> +	if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))
> +		return NOTIFY_DONE;
> +
> +	iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
> +				  notifier);
> +	pwm_get_state(&iqs620_pwm->chip.pwms[0], &state);

Please don't call pwm API functions in callbacks. I assume you rely on
pwm_get_state returning the previously set state and that
iqs620_pwm_get_state isn't called. Please use pwm->state for that.

> +	ret = iqs620_pwm_apply(&iqs620_pwm->chip,
> +			       &iqs620_pwm->chip.pwms[0], &state);
> +	if (ret) {
> +		dev_err(iqs620_pwm->chip.dev,
> +			"Failed to re-initialize device: %d\n", ret);
> +		return NOTIFY_BAD;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static const struct pwm_ops iqs620_pwm_ops = {
> +	.apply = iqs620_pwm_apply,
> +	.get_state = iqs620_pwm_get_state,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int iqs620_pwm_probe(struct platform_device *pdev)
> +{
> +	struct iqs620_pwm_private *iqs620_pwm;
> +	int ret1, ret2;
> +
> +	iqs620_pwm = devm_kzalloc(&pdev->dev, sizeof(*iqs620_pwm), GFP_KERNEL);
> +	if (!iqs620_pwm)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, iqs620_pwm);
> +	iqs620_pwm->iqs62x = dev_get_drvdata(pdev->dev.parent);
> +
> +	iqs620_pwm->chip.dev = &pdev->dev;
> +	iqs620_pwm->chip.ops = &iqs620_pwm_ops;
> +	iqs620_pwm->chip.base = -1;
> +	iqs620_pwm->chip.npwm = 1;
> +
> +	ret1 = pwmchip_add(&iqs620_pwm->chip);
> +	if (ret1) {
> +		dev_err(&pdev->dev, "Failed to add device: %d\n", ret1);
> +		return ret1;
> +	}
> +
> +	/*
> +	 * Since iqs620_pwm_notifier uses iqs620_pwm->chip.pwms[], the notifier
> +	 * is not registered until pwmchip_add (which allocates that array) has
> +	 * been called. If registration fails, the newly added device has to be
> +	 * removed because the driver fails to probe and iqs620_pwm_remove will
> +	 * never be called.
> +	 */
> +	iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier;
> +	ret1 = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh,
> +						&iqs620_pwm->notifier);
> +	if (ret1) {
> +		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret1);
> +
> +		ret2 = pwmchip_remove(&iqs620_pwm->chip);
> +		if (ret2) {
> +			dev_err(&pdev->dev, "Failed to remove device: %d\n",
> +				ret2);
> +			return ret2;

This exitpoint is bad. The PWM driver is active but the module gets
unloaded. I liked the approach from v1 better.

ret2 could be local to this block.

> +		}
> +	}
> +
> +	return ret1;
> +}

Best regards
Uwe

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

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

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

Hi Uwe,

Thank you for your continued support on this project.

On Mon, Dec 09, 2019 at 08:32:06AM +0100, Uwe Kleine-König wrote:
> On Mon, Dec 09, 2019 at 12:38:36AM +0000, 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>
> > ---
> > Changes in v2:
> >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> >   - Added 'Limitations' section to introductory comments
> >   - Replaced 'error' with 'ret' throughout
> >   - Added const qualifier to state argument of iqs620_pwm_apply and removed all
> >     modifications to the variable's contents
> >   - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
> >     polarity is inverted or the requested period is below 1 ms, respectively
> >   - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
> >   - Added iqs620_pwm_get_state
> >   - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
> >   - Moved notifier unregistration to already present iqs620_pwm_remove, which
> >     eliminated the need for a device-managed action and ready flag
> >   - Added a comment in iqs620_pwm_probe to explain the order of operations
> >   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> > 
> >  drivers/pwm/Kconfig       |  10 +++
> >  drivers/pwm/Makefile      |   1 +
> >  drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 217 insertions(+)
> >  create mode 100644 drivers/pwm/pwm-iqs620a.c
> > 
> > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > index bd21655..60bcf6c 100644
> > --- a/drivers/pwm/Kconfig
> > +++ b/drivers/pwm/Kconfig
> > @@ -222,6 +222,16 @@ config PWM_IMX_TPM
> >  	  To compile this driver as a module, choose M here: the module
> >  	  will be called pwm-imx-tpm.
> > 
> > +config PWM_IQS620A
> > +	tristate "Azoteq IQS620A PWM support"
> > +	depends on MFD_IQS62X || COMPILE_TEST
> > +	help
> > +	  Generic PWM framework driver for the Azoteq IQS620A multi-function
> > +	  sensor.
> > +
> > +	  To compile this driver as a module, choose M here: the module will
> > +	  be called pwm-iqs620a.
> > +
> >  config PWM_JZ4740
> >  	tristate "Ingenic JZ47xx PWM support"
> >  	depends on MACH_INGENIC
> > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> > index 9a47507..a59c710 100644
> > --- a/drivers/pwm/Makefile
> > +++ b/drivers/pwm/Makefile
> > @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> >  obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
> >  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
> >  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> > +obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
> >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
> > new file mode 100644
> > index 0000000..1ea11b9
> > --- /dev/null
> > +++ b/drivers/pwm/pwm-iqs620a.c
> > @@ -0,0 +1,206 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Azoteq IQS620A PWM Generator
> > + *
> > + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> > + *
> > + * Limitations:
> > + * - The period is not guaranteed to run to completion when the duty cycle is
> > + *   changed or the output is disabled.
> 
> Do you know more details here? "not guaranteed" means that the new
> period starts immediately when duty_cycle or the enabled bit is written?
> 

Increasing the duty cycle on-the-fly (e.g. 25% to 75%) results in the
following behavior (depending on where the I2C write falls):

                       I2C write
   __        __        __  V_    ______    ______    ______    __
__|  |______|  |______|  |_|x|__|      |__|      |__|      |__|
  ^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^

The PWM continues to tick at 1 ms, but the currently running period suffers
an extraneous pulse as the output is abruptly set high to "catch up" to the
new duty cycle.

A similar behavior can occur if the duty cycle is decreased, meaning the
output is abruptly set low if the I2C transaction completes in what has
suddenly become the inactive region of the currently running period.

The PWM seems to be a simple counter that rolls over at a period of 1 ms.
Both the counter and the IQS620_PWM_DUTY_CYCLE register effectively go to
a comparator whose output is ANDed with IQS620_PWR_SETTINGS_PWM_OUT which
then drives the PWM output.

As such, if either IQS620_PWM_DUTY_CYCLE or IQS620_PWR_SETTINGS_PWM_OUT
change, so may the PWM output state depending on the counter's value at
the time the I2C write is completed within the 1-ms continuous loop.

For v3 I will update the note as follows:

- Changes in duty cycle or enable/disable state are immediately reflected
  by the PWM output and are not aligned to the start of any period.

> > + * - The period is fixed to 1 ms.
> > + */
> > +
> > +#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;
> > +};
> > +
> > +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > +			    const struct pwm_state *state)
> > +{
> > +	struct iqs620_pwm_private *iqs620_pwm;
> > +	struct iqs62x_core *iqs62x;
> > +	unsigned int pwm_out = 0;
> > +	int duty_scale, ret;
> > +
> > +	if (state->polarity != PWM_POLARITY_NORMAL)
> > +		return -ENOTSUPP;
> > +
> > +	if (state->period < IQS620_PWM_PERIOD_NS)
> > +		return -EINVAL;
> > +
> > +	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > +	iqs62x = iqs620_pwm->iqs62x;
> > +
> > +	duty_scale = DIV_ROUND_CLOSEST(state->duty_cycle * 256,
> > +				       IQS620_PWM_PERIOD_NS);
> > +
> > +	if (duty_scale) {
> > +		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > +				   min(duty_scale - 1, 0xFF));
> > +		if (ret)
> > +			return ret;
> > +
> > +		if (state->enabled)
> > +			pwm_out = IQS620_PWR_SETTINGS_PWM_OUT;
> > +	}
> > +
> > +	return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > +				  IQS620_PWR_SETTINGS_PWM_OUT, pwm_out);
> 
> A comment explaining the semantic here would be good. I assume
> IQS620_PWM_DUTY_CYCLE takes a value between 0 and 255 and the resulting
> duty cycle is:
> 
> 	(IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> 
> .
> 
> If this is right, please use:
> 
> 	duty_scale = (state->duty_cycle * 256) / IQS620_PWM_PERIOD_NS
> 

Sure thing, will do. I'll add a comment and round down. Your assumption is
correct as well.

> Also, when the hardware is running at
> 
> 	.enabled = 1, .duty_cycle = 1/256 ms, .period = 1ms
> 
> and you reconfigure to
> 
> 	.enabled = 0, .duty_cycle = 1ms, .period = 1ms
> 
> the output might be active for > 1/256 ms if the process is preempted
> between writing IQS620_PWM_DUTY_CYCLE and IQS620_PWR_SETTINGS_PWM_OUT.
> 

Good catch. I think we can solve this by writing IQS620_PWM_DUTY_CYCLE
first followed by IQS620_PWR_SETTINGS_PWM_OUT when the PWM is going to
be enabled, and the reverse when the PWM is going to be disabled (i.e.
turn OFF to prevent a stale duty cycle from being temporarily driven).

> > +}
> > +
> > +static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> > +				 struct pwm_state *state)
> > +{
> > +	struct iqs620_pwm_private *iqs620_pwm;
> > +	struct iqs62x_core *iqs62x;
> > +	unsigned int val;
> > +	int ret;
> > +
> > +	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > +	iqs62x = iqs620_pwm->iqs62x;
> > +
> > +	ret = regmap_read(iqs62x->map, IQS620_PWR_SETTINGS, &val);
> > +	if (ret)
> > +		goto err_out;
> > +	state->enabled = val & IQS620_PWR_SETTINGS_PWM_OUT;
> > +
> > +	ret = regmap_read(iqs62x->map, IQS620_PWM_DUTY_CYCLE, &val);
> > +	if (ret)
> > +		goto err_out;
> > +	state->duty_cycle = DIV_ROUND_CLOSEST((val + 1) * IQS620_PWM_PERIOD_NS,
> > +					      256);
> 
> Please round up.
> 

Sure thing, will do.

> > +	state->period = IQS620_PWM_PERIOD_NS;
> > +
> > +err_out:
> > +	if (ret)
> > +		dev_err(iqs620_pwm->chip.dev, "Failed to get state: %d\n", ret);
> > +}
> > +
> > +static int iqs620_pwm_notifier(struct notifier_block *notifier,
> > +			       unsigned long event_flags, void *context)
> > +{
> > +	struct iqs620_pwm_private *iqs620_pwm;
> > +	struct pwm_state state;
> > +	int ret;
> > +
> > +	if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))
> > +		return NOTIFY_DONE;
> > +
> > +	iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
> > +				  notifier);
> > +	pwm_get_state(&iqs620_pwm->chip.pwms[0], &state);
> 
> Please don't call pwm API functions in callbacks. I assume you rely on
> pwm_get_state returning the previously set state and that
> iqs620_pwm_get_state isn't called. Please use pwm->state for that.
> 

Sure thing, will do. Your assumption is correct. If pwm_get_state called
chip->ops->get_state instead of return pwm->state as it does today, this
function would break because it would restore the hardware using default
register values (since this function follows a reset).

Just for my own understanding, are you saying the PWM framework reserves
the right to update pwm_get_state to call chip->ops->get_state some time
in the future? In any event I will refer to pwm->state as that is what I
ultimately need here.

FWIW, I borrowed the idea from the resume callback of [0] which possibly
suffers the same fate if I have understood the concern correctly.

This discussion also made me realize that we need a lock around back-to-
back access to IQS620_PWR_SETTINGS_PWM_OUT and IQS620_PWM_DUTY_CYCLE in
case iqs620_pwm_get_state is called while iqs620_pwm_apply restores them
on behalf of iqs620_pwm_notifier. I will add that in v3.

> > +	ret = iqs620_pwm_apply(&iqs620_pwm->chip,
> > +			       &iqs620_pwm->chip.pwms[0], &state);
> > +	if (ret) {
> > +		dev_err(iqs620_pwm->chip.dev,
> > +			"Failed to re-initialize device: %d\n", ret);
> > +		return NOTIFY_BAD;
> > +	}
> > +
> > +	return NOTIFY_OK;
> > +}
> > +
> > +static const struct pwm_ops iqs620_pwm_ops = {
> > +	.apply = iqs620_pwm_apply,
> > +	.get_state = iqs620_pwm_get_state,
> > +	.owner = THIS_MODULE,
> > +};
> > +
> > +static int iqs620_pwm_probe(struct platform_device *pdev)
> > +{
> > +	struct iqs620_pwm_private *iqs620_pwm;
> > +	int ret1, ret2;
> > +
> > +	iqs620_pwm = devm_kzalloc(&pdev->dev, sizeof(*iqs620_pwm), GFP_KERNEL);
> > +	if (!iqs620_pwm)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, iqs620_pwm);
> > +	iqs620_pwm->iqs62x = dev_get_drvdata(pdev->dev.parent);
> > +
> > +	iqs620_pwm->chip.dev = &pdev->dev;
> > +	iqs620_pwm->chip.ops = &iqs620_pwm_ops;
> > +	iqs620_pwm->chip.base = -1;
> > +	iqs620_pwm->chip.npwm = 1;
> > +
> > +	ret1 = pwmchip_add(&iqs620_pwm->chip);
> > +	if (ret1) {
> > +		dev_err(&pdev->dev, "Failed to add device: %d\n", ret1);
> > +		return ret1;
> > +	}
> > +
> > +	/*
> > +	 * Since iqs620_pwm_notifier uses iqs620_pwm->chip.pwms[], the notifier
> > +	 * is not registered until pwmchip_add (which allocates that array) has
> > +	 * been called. If registration fails, the newly added device has to be
> > +	 * removed because the driver fails to probe and iqs620_pwm_remove will
> > +	 * never be called.
> > +	 */
> > +	iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier;
> > +	ret1 = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh,
> > +						&iqs620_pwm->notifier);
> > +	if (ret1) {
> > +		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret1);
> > +
> > +		ret2 = pwmchip_remove(&iqs620_pwm->chip);
> > +		if (ret2) {
> > +			dev_err(&pdev->dev, "Failed to remove device: %d\n",
> > +				ret2);
> > +			return ret2;
> 
> This exitpoint is bad. The PWM driver is active but the module gets
> unloaded. I liked the approach from v1 better.
> 

Sure thing, I'll revert the behavior to that of v1.

> ret2 could be local to this block.
> 

This will go away automatically.

> > +		}
> > +	}
> > +
> > +	return ret1;
> > +}
> 
> Best regards
> Uwe
> 
> -- 
> Pengutronix e.K.                           | Uwe Kleine-König            |
> Industrial Linux Solutions                 | https://www.pengutronix.de/ |

[0] drivers/pwm/pwm-atmel-hlcdc.c

Kind regards,
Jeff LaBundy

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

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

Hello Jeff,

On Tue, Dec 10, 2019 at 12:03:02AM +0000, Jeff LaBundy wrote:
> On Mon, Dec 09, 2019 at 08:32:06AM +0100, Uwe Kleine-König wrote:
> > On Mon, Dec 09, 2019 at 12:38:36AM +0000, 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>
> > > ---
> > > Changes in v2:
> > >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> > >   - Added 'Limitations' section to introductory comments
> > >   - Replaced 'error' with 'ret' throughout
> > >   - Added const qualifier to state argument of iqs620_pwm_apply and removed all
> > >     modifications to the variable's contents
> > >   - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
> > >     polarity is inverted or the requested period is below 1 ms, respectively
> > >   - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
> > >   - Added iqs620_pwm_get_state
> > >   - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
> > >   - Moved notifier unregistration to already present iqs620_pwm_remove, which
> > >     eliminated the need for a device-managed action and ready flag
> > >   - Added a comment in iqs620_pwm_probe to explain the order of operations
> > >   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> > > 
> > >  drivers/pwm/Kconfig       |  10 +++
> > >  drivers/pwm/Makefile      |   1 +
> > >  drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
> > >  3 files changed, 217 insertions(+)
> > >  create mode 100644 drivers/pwm/pwm-iqs620a.c
> > > 
> > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > > index bd21655..60bcf6c 100644
> > > --- a/drivers/pwm/Kconfig
> > > +++ b/drivers/pwm/Kconfig
> > > @@ -222,6 +222,16 @@ config PWM_IMX_TPM
> > >  	  To compile this driver as a module, choose M here: the module
> > >  	  will be called pwm-imx-tpm.
> > > 
> > > +config PWM_IQS620A
> > > +	tristate "Azoteq IQS620A PWM support"
> > > +	depends on MFD_IQS62X || COMPILE_TEST
> > > +	help
> > > +	  Generic PWM framework driver for the Azoteq IQS620A multi-function
> > > +	  sensor.
> > > +
> > > +	  To compile this driver as a module, choose M here: the module will
> > > +	  be called pwm-iqs620a.
> > > +
> > >  config PWM_JZ4740
> > >  	tristate "Ingenic JZ47xx PWM support"
> > >  	depends on MACH_INGENIC
> > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> > > index 9a47507..a59c710 100644
> > > --- a/drivers/pwm/Makefile
> > > +++ b/drivers/pwm/Makefile
> > > @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> > >  obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
> > >  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
> > >  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> > > +obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
> > >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> > >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> > >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > > diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
> > > new file mode 100644
> > > index 0000000..1ea11b9
> > > --- /dev/null
> > > +++ b/drivers/pwm/pwm-iqs620a.c
> > > @@ -0,0 +1,206 @@
> > > +// SPDX-License-Identifier: GPL-2.0+
> > > +/*
> > > + * Azoteq IQS620A PWM Generator
> > > + *
> > > + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> > > + *
> > > + * Limitations:
> > > + * - The period is not guaranteed to run to completion when the duty cycle is
> > > + *   changed or the output is disabled.
> > 
> > Do you know more details here? "not guaranteed" means that the new
> > period starts immediately when duty_cycle or the enabled bit is written?
> > 
> 
> Increasing the duty cycle on-the-fly (e.g. 25% to 75%) results in the
> following behavior (depending on where the I2C write falls):
> 
>                        I2C write
>    __        __        __  V_    ______    ______    ______    __
> __|  |______|  |______|  |_|x|__|      |__|      |__|      |__|
>   ^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^
> 
> The PWM continues to tick at 1 ms, but the currently running period suffers
> an extraneous pulse as the output is abruptly set high to "catch up" to the
> new duty cycle.
> 
> A similar behavior can occur if the duty cycle is decreased, meaning the
> output is abruptly set low if the I2C transaction completes in what has
> suddenly become the inactive region of the currently running period.
> 
> The PWM seems to be a simple counter that rolls over at a period of 1 ms.
> Both the counter and the IQS620_PWM_DUTY_CYCLE register effectively go to
> a comparator whose output is ANDed with IQS620_PWR_SETTINGS_PWM_OUT which
> then drives the PWM output.
> 
> As such, if either IQS620_PWM_DUTY_CYCLE or IQS620_PWR_SETTINGS_PWM_OUT
> change, so may the PWM output state depending on the counter's value at
> the time the I2C write is completed within the 1-ms continuous loop.
> 
> For v3 I will update the note as follows:
> 
> - Changes in duty cycle or enable/disable state are immediately reflected
>   by the PWM output and are not aligned to the start of any period.

I'd like to see a bit more information in the driver. Something about
the 1ms rhythm being unaffected by the duty_cycle and enable setting.
Maybe:

 - The periods run continuously with a fixed length of 1 ms which is
   unaffected by register updates. Writing duty cycle or enable
   registers gets active immediately which might result in glitches.

?

> 
> > > + * - The period is fixed to 1 ms.
> > > + */
> > > +
> > > +#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;
> > > +};
> > > +
> > > +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > +			    const struct pwm_state *state)
> > > +{
> > > +	struct iqs620_pwm_private *iqs620_pwm;
> > > +	struct iqs62x_core *iqs62x;
> > > +	unsigned int pwm_out = 0;
> > > +	int duty_scale, ret;
> > > +
> > > +	if (state->polarity != PWM_POLARITY_NORMAL)
> > > +		return -ENOTSUPP;
> > > +
> > > +	if (state->period < IQS620_PWM_PERIOD_NS)
> > > +		return -EINVAL;
> > > +
> > > +	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > > +	iqs62x = iqs620_pwm->iqs62x;
> > > +
> > > +	duty_scale = DIV_ROUND_CLOSEST(state->duty_cycle * 256,
> > > +				       IQS620_PWM_PERIOD_NS);
> > > +
> > > +	if (duty_scale) {
> > > +		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > > +				   min(duty_scale - 1, 0xFF));
> > > +		if (ret)
> > > +			return ret;
> > > +
> > > +		if (state->enabled)
> > > +			pwm_out = IQS620_PWR_SETTINGS_PWM_OUT;
> > > +	}
> > > +
> > > +	return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > > +				  IQS620_PWR_SETTINGS_PWM_OUT, pwm_out);
> > 
> > A comment explaining the semantic here would be good. I assume
> > IQS620_PWM_DUTY_CYCLE takes a value between 0 and 255 and the resulting
> > duty cycle is:
> > 
> > 	(IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> > 
> > .
> > 
> > If this is right, please use:
> > 
> > 	duty_scale = (state->duty_cycle * 256) / IQS620_PWM_PERIOD_NS
> > 
> 
> Sure thing, will do. I'll add a comment and round down. Your assumption is
> correct as well.
> 
> > Also, when the hardware is running at
> > 
> > 	.enabled = 1, .duty_cycle = 1/256 ms, .period = 1ms
> > 
> > and you reconfigure to
> > 
> > 	.enabled = 0, .duty_cycle = 1ms, .period = 1ms
> > 
> > the output might be active for > 1/256 ms if the process is preempted
> > between writing IQS620_PWM_DUTY_CYCLE and IQS620_PWR_SETTINGS_PWM_OUT.
> > 
> 
> Good catch. I think we can solve this by writing IQS620_PWM_DUTY_CYCLE
> first followed by IQS620_PWR_SETTINGS_PWM_OUT when the PWM is going to
> be enabled, and the reverse when the PWM is going to be disabled (i.e.
> turn OFF to prevent a stale duty cycle from being temporarily driven).

Sounds like a plan. After disabling you even don't need to write the
duty cycle register. (But there might be a discussion ahead that
.get_state should return the duty cycle.)
 
> > > +static int iqs620_pwm_notifier(struct notifier_block *notifier,
> > > +			       unsigned long event_flags, void *context)
> > > +{
> > > +	struct iqs620_pwm_private *iqs620_pwm;
> > > +	struct pwm_state state;
> > > +	int ret;
> > > +
> > > +	if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))
> > > +		return NOTIFY_DONE;
> > > +
> > > +	iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
> > > +				  notifier);
> > > +	pwm_get_state(&iqs620_pwm->chip.pwms[0], &state);
> > 
> > Please don't call pwm API functions in callbacks. I assume you rely on
> > pwm_get_state returning the previously set state and that
> > iqs620_pwm_get_state isn't called. Please use pwm->state for that.
> > 
> 
> Sure thing, will do. Your assumption is correct. If pwm_get_state called
> chip->ops->get_state instead of return pwm->state as it does today, this
> function would break because it would restore the hardware using default
> register values (since this function follows a reset).
> 
> Just for my own understanding, are you saying the PWM framework reserves
> the right to update pwm_get_state to call chip->ops->get_state some time
> in the future? In any event I will refer to pwm->state as that is what I
> ultimately need here.

This already was the case for a short time before v5.4. See 01ccf903edd6
and 40a6b9a00930. (And note that the lazyness mentioned above about not
needing to write duty_cycle when the PWM is off is what made the
approach break however.) I don't know yet how to proceed here. Being
able to get the actually implemented setting would be nice, probably it
is prudent to do this with another API function.

Other than that I consider it a layer violation to call a function that
is designed for consumers in a lowlevel driver. I don't know if we need
locking at some time, but if the core holded a lock when .apply is
called, .apply calls pwm_get_state which wanted to grab the lock again
we get a dead-lock.

> FWIW, I borrowed the idea from the resume callback of [0] which possibly
> suffers the same fate if I have understood the concern correctly.

Yeah, there are many drivers that are not up to date with my review
requirements. The problem is a) missing time to update them and b) for
some drivers it's hard to get test coverage for changes.

Best regards
Uwe

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

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

* Re: [PATCH v2 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor
  2019-12-09  0:38 ` [PATCH v2 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
@ 2019-12-15 16:34   ` Jonathan Cameron
  0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2019-12-15 16:34 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, thierry.reding, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland

On Mon, 9 Dec 2019 00:38:38 +0000
Jeff LaBundy <jeff@labundy.com> 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>
Looks good to me.

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

Thanks,

Jonathan

> ---
> Changes in v2:
>   - Moved the driver from hwmon to iio
>   - Merged 'Copyright' and 'Author' lines into one in introductory comments
>   - Replaced 'error' with 'ret' throughout
>   - Eliminated tabbed alignment of platform_driver struct members
>   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> 
>  drivers/iio/temperature/Kconfig         | 10 ++++
>  drivers/iio/temperature/Makefile        |  1 +
>  drivers/iio/temperature/iqs620at-temp.c | 97 +++++++++++++++++++++++++++++++++
>  3 files changed, 108 insertions(+)
>  create mode 100644 drivers/iio/temperature/iqs620at-temp.c
> 
> diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig
> index e1ccb40..f1f2a14 100644
> --- a/drivers/iio/temperature/Kconfig
> +++ b/drivers/iio/temperature/Kconfig
> @@ -4,6 +4,16 @@
>  #
>  menu "Temperature sensors"
> 
> +config IQS620AT_TEMP
> +	tristate "Azoteq IQS620AT temperature sensor"
> +	depends on MFD_IQS62X || COMPILE_TEST
> +	help
> +	  Say Y here if you want to build support for the Azoteq IQS620AT
> +	  temperature sensor.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called iqs620at-temp.
> +
>  config LTC2983
>  	tristate "Analog Devices Multi-Sensor Digital Temperature Measurement System"
>  	depends on SPI
> diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile
> index d6b850b..90c1131 100644
> --- a/drivers/iio/temperature/Makefile
> +++ b/drivers/iio/temperature/Makefile
> @@ -3,6 +3,7 @@
>  # Makefile for industrial I/O temperature drivers
>  #
> 
> +obj-$(CONFIG_IQS620AT_TEMP) += iqs620at-temp.o
>  obj-$(CONFIG_LTC2983) += ltc2983.o
>  obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o
>  obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o
> diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c
> new file mode 100644
> index 0000000..d20cb6ad
> --- /dev/null
> +++ b/drivers/iio/temperature/iqs620at-temp.c
> @@ -0,0 +1,97 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Azoteq IQS620AT Temperature Sensor
> + *
> + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/iio/iio.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
> +
> +#define IQS620_TEMP_SCALE			1000
> +#define IQS620_TEMP_OFFSET			(-100)
> +
> +static int iqs620_temp_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val, int *val2, long mask)
> +{
> +	struct iqs62x_core *iqs62x = iio_device_get_drvdata(indio_dev);
> +	int ret;
> +	__le16 val_buf;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		ret = regmap_raw_read(iqs62x->map, IQS620_TEMP_UI_OUT, &val_buf,
> +				      sizeof(val_buf));
> +		if (ret)
> +			return ret;
> +
> +		*val = le16_to_cpu(val_buf);
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_SCALE:
> +		*val = IQS620_TEMP_SCALE;
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_OFFSET:
> +		*val = IQS620_TEMP_OFFSET;
> +		return IIO_VAL_INT;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct iio_info iqs620_temp_info = {
> +	.read_raw = &iqs620_temp_read_raw,
> +};
> +
> +static const struct iio_chan_spec iqs620_temp_channels[] = {
> +	{
> +		.type = IIO_TEMP,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE) |
> +				      BIT(IIO_CHAN_INFO_OFFSET),
> +	},
> +};
> +
> +static int iqs620_temp_probe(struct platform_device *pdev)
> +{
> +	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
> +	struct iio_dev *indio_dev;
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, 0);
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	iio_device_set_drvdata(indio_dev, iqs62x);
> +
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->channels = iqs620_temp_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(iqs620_temp_channels);
> +	indio_dev->name = iqs62x->dev_desc->dev_name;
> +	indio_dev->info = &iqs620_temp_info;
> +
> +	return devm_iio_device_register(&pdev->dev, indio_dev);
> +}
> +
> +static struct platform_driver iqs620_temp_platform_driver = {
> +	.driver = {
> +		.name = IQS620_DRV_NAME_TEMP,
> +	},
> +	.probe = iqs620_temp_probe,
> +};
> +module_platform_driver(iqs620_temp_platform_driver);
> +
> +MODULE_AUTHOR("Jeff LaBundy <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] 30+ messages in thread

* Re: [PATCH v2 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors
  2019-12-09  0:38 ` [PATCH v2 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Jeff LaBundy
@ 2019-12-15 16:47   ` Jonathan Cameron
  0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2019-12-15 16:47 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, thierry.reding, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland

On Mon, 9 Dec 2019 00:38:39 +0000
Jeff LaBundy <jeff@labundy.com> wrote:

> This patch adds support for the Azoteq IQS621 and IQS622 ambient light
> sensors, both of which can report a four-bit representation of ambient
> light intensity.
> 
> The IQS621 can additionally report illuminace directly in units of lux,
> while the IQS622 can report a four-bit representation of infrared light
> intensity. Furthermore, the IQS622 can report a unitless measurement of
> a target's proximity to the device.
> 
> Signed-off-by: Jeff LaBundy <jeff@labundy.com>
looks good to me.

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

Nice little driver :)

Jonathan

> ---
> Changes in v2:
>   - Merged 'Copyright' and 'Author' lines into one in introductory comments
>   - Replaced 'error' with 'ret' throughout
>   - Merged support for the closely related IQS622 (formerly represented by
>     a separate iio/proximity driver)
>   - Added support for unitless ambient light intensity (IQS621 and IQS622)
>     and infrared light intensity (IQS622 only)
>   - Moved the read of IQS621_ALS_FLAGS to iqs621_als_write_event_config to
>     account for the fact that IQS621_ALS_FLAGS may have changed in between
>     having first been read in iqs621_als_init and the time at which events
>     are enabled, thereby eliminating the need to call iqs621_als_init from
>     iqs621_als_probe
>   - Refactored the logic in iqs621_als_notifier and added a lock to safely
>     evaluate variables that may change in response to user action
>   - Added locks to iqs621_als_read_event_config/value to account for cases in
>     which the corresponding hardware state is in the process of being updated
>   - Refactored the logic in iqs621_als_read/write_event_value and removed all
>     #defines that could instead be represented by simple math
>   - Based the decision whether to select the IQS622 IR touch vs. proximity
>     threshold on the single proximity threshold written by user space, and
>     added a comment to describe the difference between either threshold
>   - Replaced IIO_CHAN_INFO_RAW with IIO_CHAN_INFO_PROCESSED for the IIO_LIGHT
>     channel (IQS621 only)
>   - Removed devm_add_action_or_reset failure message
>   - Eliminated tabbed alignment of platform_driver struct members
>   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> 
>  drivers/iio/light/Kconfig      |  10 +
>  drivers/iio/light/Makefile     |   1 +
>  drivers/iio/light/iqs621-als.c | 614 +++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 625 insertions(+)
>  create mode 100644 drivers/iio/light/iqs621-als.c
> 
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> index 9968f98..baf7958b 100644
> --- a/drivers/iio/light/Kconfig
> +++ b/drivers/iio/light/Kconfig
> @@ -173,6 +173,16 @@ config GP2AP020A00F
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called gp2ap020a00f.
> 
> +config IQS621_ALS
> +	tristate "Azoteq IQS621/622 ambient light sensors"
> +	depends on MFD_IQS62X || COMPILE_TEST
> +	help
> +	  Say Y here if you want to build support for the Azoteq IQS621
> +	  and IQS622 ambient light sensors.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called iqs621-als.
> +
>  config SENSORS_ISL29018
>  	tristate "Intersil 29018 light and proximity sensor"
>  	depends on I2C
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> index c98d1ce..988e8f4 100644
> --- a/drivers/iio/light/Makefile
> +++ b/drivers/iio/light/Makefile
> @@ -21,6 +21,7 @@ obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o
>  obj-$(CONFIG_GP2AP020A00F)	+= gp2ap020a00f.o
>  obj-$(CONFIG_HID_SENSOR_ALS)	+= hid-sensor-als.o
>  obj-$(CONFIG_HID_SENSOR_PROX)	+= hid-sensor-prox.o
> +obj-$(CONFIG_IQS621_ALS)	+= iqs621-als.o
>  obj-$(CONFIG_SENSORS_ISL29018)	+= isl29018.o
>  obj-$(CONFIG_SENSORS_ISL29028)	+= isl29028.o
>  obj-$(CONFIG_ISL29125)		+= isl29125.o
> diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c
> new file mode 100644
> index 0000000..a4dd718
> --- /dev/null
> +++ b/drivers/iio/light/iqs621-als.c
> @@ -0,0 +1,614 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Azoteq IQS621/622 Ambient Light Sensors
> + *
> + * Copyright (C) 2019 Jeff LaBundy <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_LIGHT			BIT(7)
> +#define IQS621_ALS_FLAGS_RANGE			GENMASK(3, 0)
> +
> +#define IQS621_ALS_UI_OUT			0x17
> +
> +#define IQS621_ALS_THRESH_DARK			0x80
> +#define IQS621_ALS_THRESH_LIGHT			0x81
> +
> +#define IQS622_IR_RANGE				0x15
> +#define IQS622_IR_FLAGS				0x16
> +#define IQS622_IR_FLAGS_TOUCH			BIT(1)
> +#define IQS622_IR_FLAGS_PROX			BIT(0)
> +
> +#define IQS622_IR_UI_OUT			0x17
> +
> +#define IQS622_IR_THRESH_PROX			0x91
> +#define IQS622_IR_THRESH_TOUCH			0x92
> +
> +struct iqs621_als_private {
> +	struct iqs62x_core *iqs62x;
> +	struct notifier_block notifier;
> +	struct mutex lock;
> +	bool light_en;
> +	bool range_en;
> +	bool prox_en;
> +	u8 als_flags;
> +	u8 ir_flags_mask;
> +	u8 ir_flags;
> +	u8 thresh_light;
> +	u8 thresh_dark;
> +	u8 thresh_prox;
> +};
> +
> +static int iqs621_als_init(struct iqs621_als_private *iqs621_als)
> +{
> +	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
> +	unsigned int event_mask = 0;
> +	int ret;
> +
> +	switch (iqs621_als->ir_flags_mask) {
> +	case IQS622_IR_FLAGS_TOUCH:
> +		ret = regmap_write(iqs62x->map, IQS622_IR_THRESH_TOUCH,
> +				   iqs621_als->thresh_prox);
> +		break;
> +
> +	case IQS622_IR_FLAGS_PROX:
> +		ret = regmap_write(iqs62x->map, IQS622_IR_THRESH_PROX,
> +				   iqs621_als->thresh_prox);
> +		break;
> +
> +	default:
> +		ret = regmap_write(iqs62x->map, IQS621_ALS_THRESH_LIGHT,
> +				   iqs621_als->thresh_light);
> +		if (ret)
> +			return ret;
> +
> +		ret = regmap_write(iqs62x->map, IQS621_ALS_THRESH_DARK,
> +				   iqs621_als->thresh_dark);
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	if (iqs621_als->light_en || iqs621_als->range_en)
> +		event_mask |= iqs62x->dev_desc->als_mask;
> +
> +	if (iqs621_als->prox_en)
> +		event_mask |= iqs62x->dev_desc->ir_mask;
> +
> +	return regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK,
> +				  event_mask, 0);
> +}
> +
> +static int iqs621_als_notifier(struct notifier_block *notifier,
> +			       unsigned long event_flags, void *context)
> +{
> +	struct iqs62x_event_data *event_data = context;
> +	struct iqs621_als_private *iqs621_als;
> +	struct iio_dev *indio_dev;
> +	bool light_new, light_old;
> +	bool prox_new, prox_old;
> +	u8 range_new, range_old;
> +	s64 timestamp;
> +	int ret;
> +
> +	iqs621_als = container_of(notifier, struct iqs621_als_private,
> +				  notifier);
> +	indio_dev = iio_priv_to_dev(iqs621_als);
> +	timestamp = iio_get_time_ns(indio_dev);
> +
> +	mutex_lock(&iqs621_als->lock);
> +
> +	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
> +		ret = iqs621_als_init(iqs621_als);
> +		if (ret) {
> +			dev_err(indio_dev->dev.parent,
> +				"Failed to re-initialize device: %d\n", ret);
> +			ret = NOTIFY_BAD;
> +		} else {
> +			ret = NOTIFY_OK;
> +		}
> +
> +		goto err_mutex;
> +	}
> +
> +	if (!iqs621_als->light_en && !iqs621_als->range_en &&
> +	    !iqs621_als->prox_en) {
> +		ret = NOTIFY_DONE;
> +		goto err_mutex;
> +	}
> +
> +	/* IQS621 only */
> +	light_new = event_data->als_flags & IQS621_ALS_FLAGS_LIGHT;
> +	light_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_LIGHT;
> +
> +	if (iqs621_als->light_en && light_new && !light_old)
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
> +						    IIO_EV_TYPE_THRESH,
> +						    IIO_EV_DIR_RISING),
> +			       timestamp);
> +	else if (iqs621_als->light_en && !light_new && light_old)
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
> +						    IIO_EV_TYPE_THRESH,
> +						    IIO_EV_DIR_FALLING),
> +			       timestamp);
> +
> +	/* IQS621 and IQS622 */
> +	range_new = event_data->als_flags & IQS621_ALS_FLAGS_RANGE;
> +	range_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_RANGE;
> +
> +	if (iqs621_als->range_en && (range_new > range_old))
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
> +						    IIO_EV_TYPE_CHANGE,
> +						    IIO_EV_DIR_RISING),
> +			       timestamp);
> +	else if (iqs621_als->range_en && (range_new < range_old))
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
> +						    IIO_EV_TYPE_CHANGE,
> +						    IIO_EV_DIR_FALLING),
> +			       timestamp);
> +
> +	/* IQS622 only */
> +	prox_new = event_data->ir_flags & iqs621_als->ir_flags_mask;
> +	prox_old = iqs621_als->ir_flags & iqs621_als->ir_flags_mask;
> +
> +	if (iqs621_als->prox_en && prox_new && !prox_old)
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
> +						    IIO_EV_TYPE_THRESH,
> +						    IIO_EV_DIR_RISING),
> +			       timestamp);
> +	else if (iqs621_als->prox_en && !prox_new && prox_old)
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
> +						    IIO_EV_TYPE_THRESH,
> +						    IIO_EV_DIR_FALLING),
> +			       timestamp);
> +
> +	iqs621_als->als_flags = event_data->als_flags;
> +	iqs621_als->ir_flags = event_data->ir_flags;
> +	ret = NOTIFY_OK;
> +
> +err_mutex:
> +	mutex_unlock(&iqs621_als->lock);
> +
> +	return ret;
> +}
> +
> +static void iqs621_als_notifier_unregister(void *context)
> +{
> +	struct iqs621_als_private *iqs621_als = context;
> +	struct iio_dev *indio_dev = iio_priv_to_dev(iqs621_als);
> +	int ret;
> +
> +	ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh,
> +						 &iqs621_als->notifier);
> +	if (ret)
> +		dev_err(indio_dev->dev.parent,
> +			"Failed to unregister notifier: %d\n", ret);
> +}
> +
> +static int iqs621_als_read_raw(struct iio_dev *indio_dev,
> +			       struct iio_chan_spec const *chan,
> +			       int *val, int *val2, long mask)
> +{
> +	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
> +	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
> +	int ret;
> +	__le16 val_buf;
> +
> +	switch (chan->type) {
> +	case IIO_INTENSITY:
> +		ret = regmap_read(iqs62x->map, chan->address, val);
> +		if (ret)
> +			return ret;
> +
> +		*val &= IQS621_ALS_FLAGS_RANGE;
> +		return IIO_VAL_INT;
> +
> +	case IIO_PROXIMITY:
> +	case IIO_LIGHT:
> +		ret = regmap_raw_read(iqs62x->map, chan->address, &val_buf,
> +				      sizeof(val_buf));
> +		if (ret)
> +			return ret;
> +
> +		*val = le16_to_cpu(val_buf);
> +		return IIO_VAL_INT;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int iqs621_als_read_event_config(struct iio_dev *indio_dev,
> +					const struct iio_chan_spec *chan,
> +					enum iio_event_type type,
> +					enum iio_event_direction dir)
> +{
> +	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
> +	int ret;
> +
> +	mutex_lock(&iqs621_als->lock);
> +
> +	switch (chan->type) {
> +	case IIO_LIGHT:
> +		ret = iqs621_als->light_en;
> +		break;
> +
> +	case IIO_INTENSITY:
> +		ret = iqs621_als->range_en;
> +		break;
> +
> +	case IIO_PROXIMITY:
> +		ret = iqs621_als->prox_en;
> +		break;
> +
> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +	mutex_unlock(&iqs621_als->lock);
> +
> +	return ret;
> +}
> +
> +static int iqs621_als_write_event_config(struct iio_dev *indio_dev,
> +					 const struct iio_chan_spec *chan,
> +					 enum iio_event_type type,
> +					 enum iio_event_direction dir,
> +					 int state)
> +{
> +	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
> +	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
> +	unsigned int val;
> +	int ret;
> +
> +	mutex_lock(&iqs621_als->lock);
> +
> +	ret = regmap_read(iqs62x->map, iqs62x->dev_desc->als_flags, &val);
> +	if (ret)
> +		goto err_mutex;
> +	iqs621_als->als_flags = val;
> +
> +	switch (chan->type) {
> +	case IIO_LIGHT:
> +		ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK,
> +					 iqs62x->dev_desc->als_mask,
> +					 iqs621_als->range_en | state ? 0 :
> +									0xFF);
> +		if (!ret)
> +			iqs621_als->light_en = state;
> +		break;
> +
> +	case IIO_INTENSITY:
> +		ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK,
> +					 iqs62x->dev_desc->als_mask,
> +					 iqs621_als->light_en | state ? 0 :
> +									0xFF);
> +		if (!ret)
> +			iqs621_als->range_en = state;
> +		break;
> +
> +	case IIO_PROXIMITY:
> +		ret = regmap_read(iqs62x->map, IQS622_IR_FLAGS, &val);
> +		if (ret)
> +			goto err_mutex;
> +		iqs621_als->ir_flags = val;
> +
> +		ret = regmap_update_bits(iqs62x->map, IQS620_GLBL_EVENT_MASK,
> +					 iqs62x->dev_desc->ir_mask,
> +					 state ? 0 : 0xFF);
> +		if (!ret)
> +			iqs621_als->prox_en = state;
> +		break;
> +
> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +err_mutex:
> +	mutex_unlock(&iqs621_als->lock);
> +
> +	return ret;
> +}
> +
> +static int iqs621_als_read_event_value(struct iio_dev *indio_dev,
> +				       const struct iio_chan_spec *chan,
> +				       enum iio_event_type type,
> +				       enum iio_event_direction dir,
> +				       enum iio_event_info info,
> +				       int *val, int *val2)
> +{
> +	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
> +	int ret = IIO_VAL_INT;
> +
> +	mutex_lock(&iqs621_als->lock);
> +
> +	switch (dir) {
> +	case IIO_EV_DIR_RISING:
> +		*val = iqs621_als->thresh_light * 16;
> +		break;
> +
> +	case IIO_EV_DIR_FALLING:
> +		*val = iqs621_als->thresh_dark * 4;
> +		break;
> +
> +	case IIO_EV_DIR_EITHER:
> +		if (iqs621_als->ir_flags_mask == IQS622_IR_FLAGS_TOUCH)
> +			*val = iqs621_als->thresh_prox * 4;
> +		else
> +			*val = iqs621_als->thresh_prox;
> +		break;
> +
> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +	mutex_unlock(&iqs621_als->lock);
> +
> +	return ret;
> +}
> +
> +static int iqs621_als_write_event_value(struct iio_dev *indio_dev,
> +					const struct iio_chan_spec *chan,
> +					enum iio_event_type type,
> +					enum iio_event_direction dir,
> +					enum iio_event_info info,
> +					int val, int val2)
> +{
> +	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
> +	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
> +	unsigned int thresh_reg, thresh_val;
> +	u8 ir_flags_mask, *thresh_cache;
> +	int ret = -EINVAL;
> +
> +	mutex_lock(&iqs621_als->lock);
> +
> +	switch (dir) {
> +	case IIO_EV_DIR_RISING:
> +		thresh_reg = IQS621_ALS_THRESH_LIGHT;
> +		thresh_val = val / 16;
> +
> +		thresh_cache = &iqs621_als->thresh_light;
> +		ir_flags_mask = 0;
> +		break;
> +
> +	case IIO_EV_DIR_FALLING:
> +		thresh_reg = IQS621_ALS_THRESH_DARK;
> +		thresh_val = val / 4;
> +
> +		thresh_cache = &iqs621_als->thresh_dark;
> +		ir_flags_mask = 0;
> +		break;
> +
> +	case IIO_EV_DIR_EITHER:
> +		/*
> +		 * The IQS622 supports two detection thresholds, both measured
> +		 * in the same arbitrary units reported by read_raw: proximity
> +		 * (0 through 255 in steps of 1), and touch (0 through 1020 in
> +		 * steps of 4).
> +		 *
> +		 * Based on the single detection threshold chosen by the user,
> +		 * select the hardware threshold that gives the best trade-off
> +		 * between range and resolution.
> +		 *
> +		 * By default, the close-range (but coarse) touch threshold is
> +		 * chosen during probe.
> +		 */
> +		switch (val) {
> +		case 0 ... 255:
> +			thresh_reg = IQS622_IR_THRESH_PROX;
> +			thresh_val = val;
> +
> +			ir_flags_mask = IQS622_IR_FLAGS_PROX;
> +			break;
> +
> +		case 256 ... 1020:
> +			thresh_reg = IQS622_IR_THRESH_TOUCH;
> +			thresh_val = val / 4;
> +
> +			ir_flags_mask = IQS622_IR_FLAGS_TOUCH;
> +			break;
> +
> +		default:
> +			goto err_mutex;
> +		}
> +
> +		thresh_cache = &iqs621_als->thresh_prox;
> +		break;
> +
> +	default:
> +		goto err_mutex;
> +	}
> +
> +	if (thresh_val > 0xFF)
> +		goto err_mutex;
> +
> +	ret = regmap_write(iqs62x->map, thresh_reg, thresh_val);
> +	if (ret)
> +		goto err_mutex;
> +
> +	*thresh_cache = thresh_val;
> +	iqs621_als->ir_flags_mask = ir_flags_mask;
> +
> +err_mutex:
> +	mutex_unlock(&iqs621_als->lock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info iqs621_als_info = {
> +	.read_raw = &iqs621_als_read_raw,
> +	.read_event_config = iqs621_als_read_event_config,
> +	.write_event_config = iqs621_als_write_event_config,
> +	.read_event_value = iqs621_als_read_event_value,
> +	.write_event_value = iqs621_als_write_event_value,
> +};
> +
> +static const struct iio_event_spec iqs621_als_range_events[] = {
> +	{
> +		.type = IIO_EV_TYPE_CHANGE,
> +		.dir = IIO_EV_DIR_EITHER,
> +		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
> +	},
> +};
> +
> +static const struct iio_event_spec iqs621_als_light_events[] = {
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_EITHER,
> +		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
> +	},
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_RISING,
> +		.mask_separate = BIT(IIO_EV_INFO_VALUE),
> +	},
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_FALLING,
> +		.mask_separate = BIT(IIO_EV_INFO_VALUE),
> +	},
> +};
> +
> +static const struct iio_chan_spec iqs621_als_channels[] = {
> +	{
> +		.type = IIO_INTENSITY,
> +		.address = IQS621_ALS_FLAGS,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.event_spec = iqs621_als_range_events,
> +		.num_event_specs = ARRAY_SIZE(iqs621_als_range_events),
> +	},
> +	{
> +		.type = IIO_LIGHT,
> +		.address = IQS621_ALS_UI_OUT,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
> +		.event_spec = iqs621_als_light_events,
> +		.num_event_specs = ARRAY_SIZE(iqs621_als_light_events),
> +	},
> +};
> +
> +static const struct iio_event_spec iqs622_als_prox_events[] = {
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_EITHER,
> +		.mask_separate = BIT(IIO_EV_INFO_ENABLE) |
> +				 BIT(IIO_EV_INFO_VALUE),
> +	},
> +};
> +
> +static const struct iio_chan_spec iqs622_als_channels[] = {
> +	{
> +		.type = IIO_INTENSITY,
> +		.channel2 = IIO_MOD_LIGHT_BOTH,
> +		.address = IQS622_ALS_FLAGS,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.event_spec = iqs621_als_range_events,
> +		.num_event_specs = ARRAY_SIZE(iqs621_als_range_events),
> +		.modified = true,
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.channel2 = IIO_MOD_LIGHT_IR,
> +		.address = IQS622_IR_RANGE,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.modified = true,
> +	},
> +	{
> +		.type = IIO_PROXIMITY,
> +		.address = IQS622_IR_UI_OUT,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.event_spec = iqs622_als_prox_events,
> +		.num_event_specs = ARRAY_SIZE(iqs622_als_prox_events),
> +	},
> +};
> +
> +static int iqs621_als_probe(struct platform_device *pdev)
> +{
> +	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
> +	struct iqs621_als_private *iqs621_als;
> +	struct iio_dev *indio_dev;
> +	unsigned int val;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	iqs621_als = iio_priv(indio_dev);
> +	iqs621_als->iqs62x = iqs62x;
> +
> +	if (iqs62x->dev_desc->prod_num == IQS622_PROD_NUM) {
> +		ret = regmap_read(iqs62x->map, IQS622_IR_THRESH_TOUCH, &val);
> +		if (ret)
> +			return ret;
> +		iqs621_als->thresh_prox = val;
> +		iqs621_als->ir_flags_mask = IQS622_IR_FLAGS_TOUCH;
> +
> +		indio_dev->channels = iqs622_als_channels;
> +		indio_dev->num_channels = ARRAY_SIZE(iqs622_als_channels);
> +	} else {
> +		ret = regmap_read(iqs62x->map, IQS621_ALS_THRESH_LIGHT, &val);
> +		if (ret)
> +			return ret;
> +		iqs621_als->thresh_light = val;
> +
> +		ret = regmap_read(iqs62x->map, IQS621_ALS_THRESH_DARK, &val);
> +		if (ret)
> +			return ret;
> +		iqs621_als->thresh_dark = val;
> +
> +		indio_dev->channels = iqs621_als_channels;
> +		indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels);
> +	}
> +
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->name = iqs62x->dev_desc->dev_name;
> +	indio_dev->info = &iqs621_als_info;
> +
> +	mutex_init(&iqs621_als->lock);
> +
> +	iqs621_als->notifier.notifier_call = iqs621_als_notifier;
> +	ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh,
> +					       &iqs621_als->notifier);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = devm_add_action_or_reset(&pdev->dev,
> +				       iqs621_als_notifier_unregister,
> +				       iqs621_als);
> +	if (ret)
> +		return ret;
> +
> +	return devm_iio_device_register(&pdev->dev, indio_dev);
> +}
> +
> +static struct platform_driver iqs621_als_platform_driver = {
> +	.driver = {
> +		.name = IQS621_DRV_NAME_ALS,
> +	},
> +	.probe = iqs621_als_probe,
> +};
> +module_platform_driver(iqs621_als_platform_driver);
> +
> +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
> +MODULE_DESCRIPTION("Azoteq IQS621/622 Ambient Light Sensors");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" IQS621_DRV_NAME_ALS);
> --
> 2.7.4
> 


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

* Re: [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors
  2019-12-09  0:38 ` [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Jeff LaBundy
@ 2019-12-15 16:53   ` Jonathan Cameron
  2020-01-01 22:51     ` Jeff LaBundy
  0 siblings, 1 reply; 30+ messages in thread
From: Jonathan Cameron @ 2019-12-15 16:53 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, thierry.reding, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland

On Mon, 9 Dec 2019 00:38:41 +0000
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.
> 
> Signed-off-by: Jeff LaBundy <jeff@labundy.com>

Looks good

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

My current assumption is that Lee will take this lot via an immutable branch
in MFD once it's ready.  Shout if a different path makes sense.
> ---
> Changes in v2:
>   - Merged 'Copyright' and 'Author' lines into one in introductory comments
>   - Replaced 'error' with 'ret' throughout
>   - Added iqs624_pos_angle_en and iqs624_pos_angle_get to remove duplicate
>     logic previously used throughout
>   - Refactored the logic in iqs624_pos_notifier and added a lock to safely
>     evaluate variables that may change in response to user action
>   - Refactored the logic in iqs624_pos_read_raw
>   - Added a lock to iqs624_pos_read_event_config to account for cases in which
>     the corresponding hardware state is in the process of being updated
>   - Refactored the logic in iqs624_pos_write_event_config and read the initial
>     angle in case it changed since having first been read in iqs624_pos_init
>   - Removed iqs624_pos_init as its logic has since been absorbed elsewhere
>   - Removed devm_add_action_or_reset failure message
>   - Eliminated tabbed alignment of platform_driver struct members
>   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> 
>  drivers/iio/Kconfig               |   1 +
>  drivers/iio/Makefile              |   1 +
>  drivers/iio/position/Kconfig      |  19 +++
>  drivers/iio/position/Makefile     |   7 +
>  drivers/iio/position/iqs624-pos.c | 284 ++++++++++++++++++++++++++++++++++++++
>  5 files changed, 312 insertions(+)
>  create mode 100644 drivers/iio/position/Kconfig
>  create mode 100644 drivers/iio/position/Makefile
>  create mode 100644 drivers/iio/position/iqs624-pos.c
> 
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 5bd5185..d5c073a 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -88,6 +88,7 @@ source "drivers/iio/orientation/Kconfig"
>  if IIO_TRIGGER
>     source "drivers/iio/trigger/Kconfig"
>  endif #IIO_TRIGGER
> +source "drivers/iio/position/Kconfig"
>  source "drivers/iio/potentiometer/Kconfig"
>  source "drivers/iio/potentiostat/Kconfig"
>  source "drivers/iio/pressure/Kconfig"
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index bff682a..1712011 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -31,6 +31,7 @@ obj-y += light/
>  obj-y += magnetometer/
>  obj-y += multiplexer/
>  obj-y += orientation/
> +obj-y += position/
>  obj-y += potentiometer/
>  obj-y += potentiostat/
>  obj-y += pressure/
> diff --git a/drivers/iio/position/Kconfig b/drivers/iio/position/Kconfig
> new file mode 100644
> index 0000000..eda67f0
> --- /dev/null
> +++ b/drivers/iio/position/Kconfig
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Linear and angular position sensors
> +#
> +# When adding new entries keep the list in alphabetical order
> +
> +menu "Linear and angular position sensors"
> +
> +config IQS624_POS
> +	tristate "Azoteq IQS624/625 angular position sensors"
> +	depends on MFD_IQS62X || COMPILE_TEST
> +	help
> +	  Say Y here if you want to build support for the Azoteq IQS624
> +	  and IQS625 angular position sensors.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called iqs624-pos.
> +
> +endmenu
> diff --git a/drivers/iio/position/Makefile b/drivers/iio/position/Makefile
> new file mode 100644
> index 0000000..3cbe7a7
> --- /dev/null
> +++ b/drivers/iio/position/Makefile
> @@ -0,0 +1,7 @@
> +#
> +# Makefile for IIO linear and angular position sensors
> +#
> +
> +# When adding new entries keep the list in alphabetical order
> +
> +obj-$(CONFIG_IQS624_POS)	+= iqs624-pos.o
> diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c
> new file mode 100644
> index 0000000..af629bf5
> --- /dev/null
> +++ b/drivers/iio/position/iqs624-pos.c
> @@ -0,0 +1,284 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Azoteq IQS624/625 Angular Position Sensors
> + *
> + * Copyright (C) 2019 Jeff LaBundy <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 angle_en;
> +	u16 angle;
> +};
> +
> +static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en)
> +{
> +	unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT;
> +
> +	/*
> +	 * The IQS625 reports angular position in the form of coarse intervals,
> +	 * so only interval change events are unmasked. Conversely, the IQS624
> +	 * reports angular position down to one degree of resolution, so wheel
> +	 * movement events are unmasked instead.
> +	 */
> +	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
> +		event_mask = IQS624_HALL_UI_INT_EVENT;
> +
> +	return regmap_update_bits(iqs62x->map, IQS624_HALL_UI, event_mask,
> +				  angle_en ? 0 : 0xFF);
> +}
> +
> +static int iqs624_pos_notifier(struct notifier_block *notifier,
> +			       unsigned long event_flags, void *context)
> +{
> +	struct iqs62x_event_data *event_data = context;
> +	struct iqs624_pos_private *iqs624_pos;
> +	struct iqs62x_core *iqs62x;
> +	struct iio_dev *indio_dev;
> +	u16 angle = event_data->ui_data;
> +	s64 timestamp;
> +	int ret;
> +
> +	iqs624_pos = container_of(notifier, struct iqs624_pos_private,
> +				  notifier);
> +	indio_dev = iio_priv_to_dev(iqs624_pos);
> +	timestamp = iio_get_time_ns(indio_dev);
> +
> +	iqs62x = iqs624_pos->iqs62x;
> +	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
> +		angle = event_data->interval;
> +
> +	mutex_lock(&iqs624_pos->lock);
> +
> +	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
> +		ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en);
> +		if (ret) {
> +			dev_err(indio_dev->dev.parent,
> +				"Failed to re-initialize device: %d\n", ret);
> +			ret = NOTIFY_BAD;
> +		} else {
> +			ret = NOTIFY_OK;
> +		}
> +	} else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) {
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0,
> +						    IIO_EV_TYPE_CHANGE,
> +						    IIO_EV_DIR_NONE),
> +			       timestamp);
> +
> +		iqs624_pos->angle = angle;
> +		ret = NOTIFY_OK;
> +	} else {
> +		ret = NOTIFY_DONE;
> +	}
> +
> +	mutex_unlock(&iqs624_pos->lock);
> +
> +	return ret;
> +}
> +
> +static void iqs624_pos_notifier_unregister(void *context)
> +{
> +	struct iqs624_pos_private *iqs624_pos = context;
> +	struct iio_dev *indio_dev = iio_priv_to_dev(iqs624_pos);
> +	int ret;
> +
> +	ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh,
> +						 &iqs624_pos->notifier);
> +	if (ret)
> +		dev_err(indio_dev->dev.parent,
> +			"Failed to unregister notifier: %d\n", ret);
> +}
> +
> +static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val)
> +{
> +	int ret;
> +	__le16 val_buf;
> +
> +	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
> +		return regmap_read(iqs62x->map, iqs62x->dev_desc->interval,
> +				   val);
> +
> +	ret = regmap_raw_read(iqs62x->map, IQS624_POS_DEG_OUT, &val_buf,
> +			      sizeof(val_buf));
> +	if (ret)
> +		return ret;
> +
> +	*val = le16_to_cpu(val_buf);
> +
> +	return 0;
> +}
> +
> +static int iqs624_pos_read_raw(struct iio_dev *indio_dev,
> +			       struct iio_chan_spec const *chan,
> +			       int *val, int *val2, long mask)
> +{
> +	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
> +	struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
> +	unsigned int scale = 1;
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		ret = iqs624_pos_angle_get(iqs62x, val);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_SCALE:
> +		if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) {
> +			ret = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV,
> +					  &scale);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		*val = scale * IQS624_POS_SCALE1;
> +		*val2 = IQS624_POS_SCALE2;
> +		return IIO_VAL_FRACTIONAL;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int iqs624_pos_read_event_config(struct iio_dev *indio_dev,
> +					const struct iio_chan_spec *chan,
> +					enum iio_event_type type,
> +					enum iio_event_direction dir)
> +{
> +	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
> +	int ret;
> +
> +	mutex_lock(&iqs624_pos->lock);
> +	ret = iqs624_pos->angle_en;
> +	mutex_unlock(&iqs624_pos->lock);
> +
> +	return ret;
> +}
> +
> +static int iqs624_pos_write_event_config(struct iio_dev *indio_dev,
> +					 const struct iio_chan_spec *chan,
> +					 enum iio_event_type type,
> +					 enum iio_event_direction dir,
> +					 int state)
> +{
> +	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
> +	struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
> +	unsigned int val;
> +	int ret;
> +
> +	mutex_lock(&iqs624_pos->lock);
> +
> +	ret = iqs624_pos_angle_get(iqs62x, &val);
> +	if (ret)
> +		goto err_mutex;
> +
> +	ret = iqs624_pos_angle_en(iqs62x, state);
> +	if (ret)
> +		goto err_mutex;
> +
> +	iqs624_pos->angle = val;
> +	iqs624_pos->angle_en = state;
> +
> +err_mutex:
> +	mutex_unlock(&iqs624_pos->lock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info iqs624_pos_info = {
> +	.read_raw = &iqs624_pos_read_raw,
> +	.read_event_config = iqs624_pos_read_event_config,
> +	.write_event_config = iqs624_pos_write_event_config,
> +};
> +
> +static const struct iio_event_spec iqs624_pos_events[] = {
> +	{
> +		.type = IIO_EV_TYPE_CHANGE,
> +		.dir = IIO_EV_DIR_NONE,
> +		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
> +	},
> +};
> +
> +static const struct iio_chan_spec iqs624_pos_channels[] = {
> +	{
> +		.type = IIO_ANGL,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE),
> +		.event_spec = iqs624_pos_events,
> +		.num_event_specs = ARRAY_SIZE(iqs624_pos_events),
> +	},
> +};
> +
> +static int iqs624_pos_probe(struct platform_device *pdev)
> +{
> +	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
> +	struct iqs624_pos_private *iqs624_pos;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	iqs624_pos = iio_priv(indio_dev);
> +	iqs624_pos->iqs62x = iqs62x;
> +
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->channels = iqs624_pos_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels);
> +	indio_dev->name = iqs62x->dev_desc->dev_name;
> +	indio_dev->info = &iqs624_pos_info;
> +
> +	mutex_init(&iqs624_pos->lock);
> +
> +	iqs624_pos->notifier.notifier_call = iqs624_pos_notifier;
> +	ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh,
> +					       &iqs624_pos->notifier);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = devm_add_action_or_reset(&pdev->dev,
> +				       iqs624_pos_notifier_unregister,
> +				       iqs624_pos);
> +	if (ret)
> +		return ret;
> +
> +	return devm_iio_device_register(&pdev->dev, indio_dev);
> +}
> +
> +static struct platform_driver iqs624_pos_platform_driver = {
> +	.driver = {
> +		.name = IQS624_DRV_NAME_POS,
> +	},
> +	.probe = iqs624_pos_probe,
> +};
> +module_platform_driver(iqs624_pos_platform_driver);
> +
> +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
> +MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" IQS624_DRV_NAME_POS);
> --
> 2.7.4
> 


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

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

Hi Uwe,

On Tue, Dec 10, 2019 at 08:22:27AM +0100, Uwe Kleine-König wrote:
> Hello Jeff,
> 
> On Tue, Dec 10, 2019 at 12:03:02AM +0000, Jeff LaBundy wrote:
> > On Mon, Dec 09, 2019 at 08:32:06AM +0100, Uwe Kleine-König wrote:
> > > On Mon, Dec 09, 2019 at 12:38:36AM +0000, 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>
> > > > ---
> > > > Changes in v2:
> > > >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> > > >   - Added 'Limitations' section to introductory comments
> > > >   - Replaced 'error' with 'ret' throughout
> > > >   - Added const qualifier to state argument of iqs620_pwm_apply and removed all
> > > >     modifications to the variable's contents
> > > >   - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
> > > >     polarity is inverted or the requested period is below 1 ms, respectively
> > > >   - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
> > > >   - Added iqs620_pwm_get_state
> > > >   - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
> > > >   - Moved notifier unregistration to already present iqs620_pwm_remove, which
> > > >     eliminated the need for a device-managed action and ready flag
> > > >   - Added a comment in iqs620_pwm_probe to explain the order of operations
> > > >   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> > > > 
> > > >  drivers/pwm/Kconfig       |  10 +++
> > > >  drivers/pwm/Makefile      |   1 +
> > > >  drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
> > > >  3 files changed, 217 insertions(+)
> > > >  create mode 100644 drivers/pwm/pwm-iqs620a.c
> > > > 
> > > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > > > index bd21655..60bcf6c 100644
> > > > --- a/drivers/pwm/Kconfig
> > > > +++ b/drivers/pwm/Kconfig
> > > > @@ -222,6 +222,16 @@ config PWM_IMX_TPM
> > > >  	  To compile this driver as a module, choose M here: the module
> > > >  	  will be called pwm-imx-tpm.
> > > > 
> > > > +config PWM_IQS620A
> > > > +	tristate "Azoteq IQS620A PWM support"
> > > > +	depends on MFD_IQS62X || COMPILE_TEST
> > > > +	help
> > > > +	  Generic PWM framework driver for the Azoteq IQS620A multi-function
> > > > +	  sensor.
> > > > +
> > > > +	  To compile this driver as a module, choose M here: the module will
> > > > +	  be called pwm-iqs620a.
> > > > +
> > > >  config PWM_JZ4740
> > > >  	tristate "Ingenic JZ47xx PWM support"
> > > >  	depends on MACH_INGENIC
> > > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> > > > index 9a47507..a59c710 100644
> > > > --- a/drivers/pwm/Makefile
> > > > +++ b/drivers/pwm/Makefile
> > > > @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> > > >  obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
> > > >  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
> > > >  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> > > > +obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
> > > >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> > > >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> > > >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > > > diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
> > > > new file mode 100644
> > > > index 0000000..1ea11b9
> > > > --- /dev/null
> > > > +++ b/drivers/pwm/pwm-iqs620a.c
> > > > @@ -0,0 +1,206 @@
> > > > +// SPDX-License-Identifier: GPL-2.0+
> > > > +/*
> > > > + * Azoteq IQS620A PWM Generator
> > > > + *
> > > > + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> > > > + *
> > > > + * Limitations:
> > > > + * - The period is not guaranteed to run to completion when the duty cycle is
> > > > + *   changed or the output is disabled.
> > > 
> > > Do you know more details here? "not guaranteed" means that the new
> > > period starts immediately when duty_cycle or the enabled bit is written?
> > > 
> > 
> > Increasing the duty cycle on-the-fly (e.g. 25% to 75%) results in the
> > following behavior (depending on where the I2C write falls):
> > 
> >                        I2C write
> >    __        __        __  V_    ______    ______    ______    __
> > __|  |______|  |______|  |_|x|__|      |__|      |__|      |__|
> >   ^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^
> > 
> > The PWM continues to tick at 1 ms, but the currently running period suffers
> > an extraneous pulse as the output is abruptly set high to "catch up" to the
> > new duty cycle.
> > 
> > A similar behavior can occur if the duty cycle is decreased, meaning the
> > output is abruptly set low if the I2C transaction completes in what has
> > suddenly become the inactive region of the currently running period.
> > 
> > The PWM seems to be a simple counter that rolls over at a period of 1 ms.
> > Both the counter and the IQS620_PWM_DUTY_CYCLE register effectively go to
> > a comparator whose output is ANDed with IQS620_PWR_SETTINGS_PWM_OUT which
> > then drives the PWM output.
> > 
> > As such, if either IQS620_PWM_DUTY_CYCLE or IQS620_PWR_SETTINGS_PWM_OUT
> > change, so may the PWM output state depending on the counter's value at
> > the time the I2C write is completed within the 1-ms continuous loop.
> > 
> > For v3 I will update the note as follows:
> > 
> > - Changes in duty cycle or enable/disable state are immediately reflected
> >   by the PWM output and are not aligned to the start of any period.
> 
> I'd like to see a bit more information in the driver. Something about
> the 1ms rhythm being unaffected by the duty_cycle and enable setting.
> Maybe:
> 
>  - The periods run continuously with a fixed length of 1 ms which is
>    unaffected by register updates. Writing duty cycle or enable
>    registers gets active immediately which might result in glitches.
> 
> ?
> 

I adjusted the wording a bit as per my preference and settled on the
following:

  - The period is fixed to 1 ms and is generated continuously despite changes
    to the duty cycle or enable/disable state.
  - Changes to the duty cycle or enable/disable state take effect immediately
    and may result in a glitch during the period in which the change is made.

I believe these capture the spirit of your message; please let me know if
you have any concerns.

Upon further experimentation, I found that disabling the output (which v2
does so as to simulate a 0% duty cycle) does not actively drive zero, but
rather places the output in a high-impedance state with only the device's
own internal leakage eventually discharging the pin.

This is fundamentally different than actively driving the pin low to make
a 0% duty cycle, which does not appear to be possible at all. Therefore I
have removed the control of IQS620_PWR_SETTINGS_PWM_OUT based on the duty
cycle requested by the user and reverted to the behavior of v1, where the
duty cycle requested by the user is mapped only to IQS620_PWM_DUTY_CYCLE.

As such, I have also added a third bullet point similar to what you first
suggested following v1:

  - The device cannot generate a 0% duty cycle.

> > 
> > > > + * - The period is fixed to 1 ms.
> > > > + */
> > > > +
> > > > +#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;
> > > > +};
> > > > +
> > > > +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > > +			    const struct pwm_state *state)
> > > > +{
> > > > +	struct iqs620_pwm_private *iqs620_pwm;
> > > > +	struct iqs62x_core *iqs62x;
> > > > +	unsigned int pwm_out = 0;
> > > > +	int duty_scale, ret;
> > > > +
> > > > +	if (state->polarity != PWM_POLARITY_NORMAL)
> > > > +		return -ENOTSUPP;
> > > > +
> > > > +	if (state->period < IQS620_PWM_PERIOD_NS)
> > > > +		return -EINVAL;
> > > > +
> > > > +	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > > > +	iqs62x = iqs620_pwm->iqs62x;
> > > > +
> > > > +	duty_scale = DIV_ROUND_CLOSEST(state->duty_cycle * 256,
> > > > +				       IQS620_PWM_PERIOD_NS);
> > > > +
> > > > +	if (duty_scale) {
> > > > +		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > > > +				   min(duty_scale - 1, 0xFF));
> > > > +		if (ret)
> > > > +			return ret;
> > > > +
> > > > +		if (state->enabled)
> > > > +			pwm_out = IQS620_PWR_SETTINGS_PWM_OUT;
> > > > +	}
> > > > +
> > > > +	return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > > > +				  IQS620_PWR_SETTINGS_PWM_OUT, pwm_out);
> > > 
> > > A comment explaining the semantic here would be good. I assume
> > > IQS620_PWM_DUTY_CYCLE takes a value between 0 and 255 and the resulting
> > > duty cycle is:
> > > 
> > > 	(IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> > > 
> > > .
> > > 
> > > If this is right, please use:
> > > 
> > > 	duty_scale = (state->duty_cycle * 256) / IQS620_PWM_PERIOD_NS
> > > 
> > 
> > Sure thing, will do. I'll add a comment and round down. Your assumption is
> > correct as well.
> > 
> > > Also, when the hardware is running at
> > > 
> > > 	.enabled = 1, .duty_cycle = 1/256 ms, .period = 1ms
> > > 
> > > and you reconfigure to
> > > 
> > > 	.enabled = 0, .duty_cycle = 1ms, .period = 1ms
> > > 
> > > the output might be active for > 1/256 ms if the process is preempted
> > > between writing IQS620_PWM_DUTY_CYCLE and IQS620_PWR_SETTINGS_PWM_OUT.
> > > 
> > 
> > Good catch. I think we can solve this by writing IQS620_PWM_DUTY_CYCLE
> > first followed by IQS620_PWR_SETTINGS_PWM_OUT when the PWM is going to
> > be enabled, and the reverse when the PWM is going to be disabled (i.e.
> > turn OFF to prevent a stale duty cycle from being temporarily driven).
> 
> Sounds like a plan. After disabling you even don't need to write the
> duty cycle register. (But there might be a discussion ahead that
> .get_state should return the duty cycle.)
>  

For v3, I've opted to write IQS620_PWM_DUTY_CYCLE regardless of whether the PWM
is enabled as a matter of principle (i.e. faithfully pass the entire state down
to the hardware without making assumptions).

And since some consumers (e.g. leds-pwm, the primary use-case for this PWM) may
pre-configure the duty cycle while the PWM is disabled, this method ensures the
driver is already compliant in case 01ccf903edd6 returns.

Also, as mentioned above, I have dropped the automatic disabling of the PWM to
simulate a 0% duty cycle if the requested duty cycle is < 1 / 256 ms since the
device does not actively drive a zero with IQS620_PWR_SETTINGS_PWM_OUT = 0. In
the interest of transparency, here is what I currently have for v3:

static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
			    const struct pwm_state *state)
{
	struct iqs620_pwm_private *iqs620_pwm;
	struct iqs62x_core *iqs62x;
	int duty_scale, ret;

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

	if (state->period < IQS620_PWM_PERIOD_NS)
		return -EINVAL;

	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
	iqs62x = iqs620_pwm->iqs62x;

	if (!state->enabled) {
		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
					 IQS620_PWR_SETTINGS_PWM_OUT, 0);
		if (ret)
			return ret;
	}

	/*
	 * The duty cycle generated by the device is calculated as follows:
	 *
	 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
	 *
	 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
	 * (inclusive). Therefore the lowest duty cycle the device can generate
	 * while the output is enabled is 1 / 256 ms.
	 */
	duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS - 1;

	ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
			   clamp(duty_scale, 0, 0xFF));
	if (ret)
		return ret;

	if (state->enabled)
		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
					 IQS620_PWR_SETTINGS_PWM_OUT, 0xFF);

	return ret;
}

I believe this captures all of the discussion thus far; please let me know if you
have any concerns.

> > > > +static int iqs620_pwm_notifier(struct notifier_block *notifier,
> > > > +			       unsigned long event_flags, void *context)
> > > > +{
> > > > +	struct iqs620_pwm_private *iqs620_pwm;
> > > > +	struct pwm_state state;
> > > > +	int ret;
> > > > +
> > > > +	if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))
> > > > +		return NOTIFY_DONE;
> > > > +
> > > > +	iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
> > > > +				  notifier);
> > > > +	pwm_get_state(&iqs620_pwm->chip.pwms[0], &state);
> > > 
> > > Please don't call pwm API functions in callbacks. I assume you rely on
> > > pwm_get_state returning the previously set state and that
> > > iqs620_pwm_get_state isn't called. Please use pwm->state for that.
> > > 
> > 
> > Sure thing, will do. Your assumption is correct. If pwm_get_state called
> > chip->ops->get_state instead of return pwm->state as it does today, this
> > function would break because it would restore the hardware using default
> > register values (since this function follows a reset).
> > 
> > Just for my own understanding, are you saying the PWM framework reserves
> > the right to update pwm_get_state to call chip->ops->get_state some time
> > in the future? In any event I will refer to pwm->state as that is what I
> > ultimately need here.
> 
> This already was the case for a short time before v5.4. See 01ccf903edd6
> and 40a6b9a00930. (And note that the lazyness mentioned above about not
> needing to write duty_cycle when the PWM is off is what made the
> approach break however.) I don't know yet how to proceed here. Being
> able to get the actually implemented setting would be nice, probably it
> is prudent to do this with another API function.
> 
> Other than that I consider it a layer violation to call a function that
> is designed for consumers in a lowlevel driver. I don't know if we need
> locking at some time, but if the core holded a lock when .apply is
> called, .apply calls pwm_get_state which wanted to grab the lock again
> we get a dead-lock.
> 

I think we may be imagining two different hazards (please correct me if I have
misunderstood). Originally I thought you were warning that pwm_get_state (which
simply returns pwm->state) may in the future call chip->ops->get_state instead,
which here would have caused iqs620_pwm_notifier to call iqs620_pwm_apply with
a reset-like state rather than the last state requested by the user (since this
notifier is called after the device resets itself).

The short-lived change during the 5.4-rc phase, however, was to fill pwm->state
in pwm_apply_state with the quantized state from the hardware instead of the raw
state requested by the user. Whether pwm->state (accessed directly or by calling
pwm_get_state) gives the raw state or the quantized output of get_state following
the last call to pwm_apply_state does not change the outcome (for this particular
driver) because iqs620_pwm_apply ultimately writes the same register values. Just
to be safe, I've been testing with and without 01ccf903edd6 applied locally so as
to validate the behavior in both scenarios.

What I missed originally is that pwm_get_state is intended for consumers only, in
which case I agree it is fundamentally wrong to cannibalize it in the driver. For
v3 I have changed iqs620_pwm_notifier to reference pwm->state directly since that
is what is ultimately needed in the first place.

You're correct that a lock within the core would cause a deadlock; in this case I
was proposing a lock around the pair of reads/writes to IQS620_PWM_DUTY_CYCLE and
IQS620_PWR_SETTINGS_PWM_OUT since v2 introduced an implied relationship between
the two. That would be safe since chip->ops->apply returns before pwm_apply_state
calls chip->ops->get_state.

However, even that is no longer necessary since IQS620_PWR_SETTINGS_PWM_OUT is now
written independently of IQS620_PWM_DUTY_CYCLE due to withdrawing support for a 0%
duty cycle starting in v3. In short, I didn't end up adding any locks as there was
no need.

> > FWIW, I borrowed the idea from the resume callback of [0] which possibly
> > suffers the same fate if I have understood the concern correctly.
> 
> Yeah, there are many drivers that are not up to date with my review
> requirements. The problem is a) missing time to update them and b) for
> some drivers it's hard to get test coverage for changes.
> 
> Best regards
> Uwe
> 
> -- 
> Pengutronix e.K.                           | Uwe Kleine-König            |
> Industrial Linux Solutions                 | https://www.pengutronix.de/ |

Kind regards,
Jeff LaBundy

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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2019-12-15 20:36         ` Jeff LaBundy
@ 2019-12-16  9:19           ` Uwe Kleine-König
  2019-12-20  3:19             ` Jeff LaBundy
  0 siblings, 1 reply; 30+ messages in thread
From: Uwe Kleine-König @ 2019-12-16  9:19 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: mark.rutland, devicetree, lars, kernel, linux-pwm, linux-iio,
	dmitry.torokhov, robh+dt, thierry.reding, pmeerw, linux-input,
	lee.jones, jic23, knaack.h

Hello Jeff,

On Sun, Dec 15, 2019 at 08:36:12PM +0000, Jeff LaBundy wrote:
> On Tue, Dec 10, 2019 at 08:22:27AM +0100, Uwe Kleine-König wrote:
> > On Tue, Dec 10, 2019 at 12:03:02AM +0000, Jeff LaBundy wrote:
> > > On Mon, Dec 09, 2019 at 08:32:06AM +0100, Uwe Kleine-König wrote:
> > > > On Mon, Dec 09, 2019 at 12:38:36AM +0000, 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>
> > > > > ---
> > > > > Changes in v2:
> > > > >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> > > > >   - Added 'Limitations' section to introductory comments
> > > > >   - Replaced 'error' with 'ret' throughout
> > > > >   - Added const qualifier to state argument of iqs620_pwm_apply and removed all
> > > > >     modifications to the variable's contents
> > > > >   - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
> > > > >     polarity is inverted or the requested period is below 1 ms, respectively
> > > > >   - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
> > > > >   - Added iqs620_pwm_get_state
> > > > >   - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
> > > > >   - Moved notifier unregistration to already present iqs620_pwm_remove, which
> > > > >     eliminated the need for a device-managed action and ready flag
> > > > >   - Added a comment in iqs620_pwm_probe to explain the order of operations
> > > > >   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> > > > > 
> > > > >  drivers/pwm/Kconfig       |  10 +++
> > > > >  drivers/pwm/Makefile      |   1 +
> > > > >  drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
> > > > >  3 files changed, 217 insertions(+)
> > > > >  create mode 100644 drivers/pwm/pwm-iqs620a.c
> > > > > 
> > > > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > > > > index bd21655..60bcf6c 100644
> > > > > --- a/drivers/pwm/Kconfig
> > > > > +++ b/drivers/pwm/Kconfig
> > > > > @@ -222,6 +222,16 @@ config PWM_IMX_TPM
> > > > >  	  To compile this driver as a module, choose M here: the module
> > > > >  	  will be called pwm-imx-tpm.
> > > > > 
> > > > > +config PWM_IQS620A
> > > > > +	tristate "Azoteq IQS620A PWM support"
> > > > > +	depends on MFD_IQS62X || COMPILE_TEST
> > > > > +	help
> > > > > +	  Generic PWM framework driver for the Azoteq IQS620A multi-function
> > > > > +	  sensor.
> > > > > +
> > > > > +	  To compile this driver as a module, choose M here: the module will
> > > > > +	  be called pwm-iqs620a.
> > > > > +
> > > > >  config PWM_JZ4740
> > > > >  	tristate "Ingenic JZ47xx PWM support"
> > > > >  	depends on MACH_INGENIC
> > > > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> > > > > index 9a47507..a59c710 100644
> > > > > --- a/drivers/pwm/Makefile
> > > > > +++ b/drivers/pwm/Makefile
> > > > > @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> > > > >  obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
> > > > >  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
> > > > >  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> > > > > +obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
> > > > >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> > > > >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> > > > >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > > > > diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
> > > > > new file mode 100644
> > > > > index 0000000..1ea11b9
> > > > > --- /dev/null
> > > > > +++ b/drivers/pwm/pwm-iqs620a.c
> > > > > @@ -0,0 +1,206 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0+
> > > > > +/*
> > > > > + * Azoteq IQS620A PWM Generator
> > > > > + *
> > > > > + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> > > > > + *
> > > > > + * Limitations:
> > > > > + * - The period is not guaranteed to run to completion when the duty cycle is
> > > > > + *   changed or the output is disabled.
> > > > 
> > > > Do you know more details here? "not guaranteed" means that the new
> > > > period starts immediately when duty_cycle or the enabled bit is written?
> > > > 
> > > 
> > > Increasing the duty cycle on-the-fly (e.g. 25% to 75%) results in the
> > > following behavior (depending on where the I2C write falls):
> > > 
> > >                        I2C write
> > >    __        __        __  V_    ______    ______    ______    __
> > > __|  |______|  |______|  |_|x|__|      |__|      |__|      |__|
> > >   ^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^
> > > 
> > > The PWM continues to tick at 1 ms, but the currently running period suffers
> > > an extraneous pulse as the output is abruptly set high to "catch up" to the
> > > new duty cycle.
> > > 
> > > A similar behavior can occur if the duty cycle is decreased, meaning the
> > > output is abruptly set low if the I2C transaction completes in what has
> > > suddenly become the inactive region of the currently running period.
> > > 
> > > The PWM seems to be a simple counter that rolls over at a period of 1 ms.
> > > Both the counter and the IQS620_PWM_DUTY_CYCLE register effectively go to
> > > a comparator whose output is ANDed with IQS620_PWR_SETTINGS_PWM_OUT which
> > > then drives the PWM output.
> > > 
> > > As such, if either IQS620_PWM_DUTY_CYCLE or IQS620_PWR_SETTINGS_PWM_OUT
> > > change, so may the PWM output state depending on the counter's value at
> > > the time the I2C write is completed within the 1-ms continuous loop.
> > > 
> > > For v3 I will update the note as follows:
> > > 
> > > - Changes in duty cycle or enable/disable state are immediately reflected
> > >   by the PWM output and are not aligned to the start of any period.
> > 
> > I'd like to see a bit more information in the driver. Something about
> > the 1ms rhythm being unaffected by the duty_cycle and enable setting.
> > Maybe:
> > 
> >  - The periods run continuously with a fixed length of 1 ms which is
> >    unaffected by register updates. Writing duty cycle or enable
> >    registers gets active immediately which might result in glitches.
> > 
> > ?
> > 
> 
> I adjusted the wording a bit as per my preference and settled on the
> following:
> 
>   - The period is fixed to 1 ms and is generated continuously despite changes
>     to the duty cycle or enable/disable state.
>   - Changes to the duty cycle or enable/disable state take effect immediately
>     and may result in a glitch during the period in which the change is made.
> 
> I believe these capture the spirit of your message; please let me know if
> you have any concerns.

That's fine.

> Upon further experimentation, I found that disabling the output (which v2
> does so as to simulate a 0% duty cycle) does not actively drive zero, but
> rather places the output in a high-impedance state with only the device's
> own internal leakage eventually discharging the pin.

But maybe this is still the best you can do in this case. @Thierry, what
do you think?

> This is fundamentally different than actively driving the pin low to make
> a 0% duty cycle, which does not appear to be possible at all. Therefore I
> have removed the control of IQS620_PWR_SETTINGS_PWM_OUT based on the duty
> cycle requested by the user and reverted to the behavior of v1, where the
> duty cycle requested by the user is mapped only to IQS620_PWM_DUTY_CYCLE.
> 
> As such, I have also added a third bullet point similar to what you first
> suggested following v1:
> 
>   - The device cannot generate a 0% duty cycle.

Then this would be:

  - The device cannot actively drive a 0% duty cycle. The driver is
    disabled for small duty_cycles relying on a pull down on the board.

But maybe it would be more prudent to do this only if the board
configuration suggests this is save?!

> > > > > + * - The period is fixed to 1 ms.
> > > > > + */
> > > > > +
> > > > > +#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;
> > > > > +};
> > > > > +
> > > > > +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > > > +			    const struct pwm_state *state)
> > > > > +{
> > > > > +	struct iqs620_pwm_private *iqs620_pwm;
> > > > > +	struct iqs62x_core *iqs62x;
> > > > > +	unsigned int pwm_out = 0;
> > > > > +	int duty_scale, ret;
> > > > > +
> > > > > +	if (state->polarity != PWM_POLARITY_NORMAL)
> > > > > +		return -ENOTSUPP;
> > > > > +
> > > > > +	if (state->period < IQS620_PWM_PERIOD_NS)
> > > > > +		return -EINVAL;
> > > > > +
> > > > > +	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > > > > +	iqs62x = iqs620_pwm->iqs62x;
> > > > > +
> > > > > +	duty_scale = DIV_ROUND_CLOSEST(state->duty_cycle * 256,
> > > > > +				       IQS620_PWM_PERIOD_NS);
> > > > > +
> > > > > +	if (duty_scale) {
> > > > > +		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > > > > +				   min(duty_scale - 1, 0xFF));
> > > > > +		if (ret)
> > > > > +			return ret;
> > > > > +
> > > > > +		if (state->enabled)
> > > > > +			pwm_out = IQS620_PWR_SETTINGS_PWM_OUT;
> > > > > +	}
> > > > > +
> > > > > +	return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > > > > +				  IQS620_PWR_SETTINGS_PWM_OUT, pwm_out);
> > > > 
> > > > A comment explaining the semantic here would be good. I assume
> > > > IQS620_PWM_DUTY_CYCLE takes a value between 0 and 255 and the resulting
> > > > duty cycle is:
> > > > 
> > > > 	(IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> > > > 
> > > > .
> > > > 
> > > > If this is right, please use:
> > > > 
> > > > 	duty_scale = (state->duty_cycle * 256) / IQS620_PWM_PERIOD_NS
> > > > 
> > > 
> > > Sure thing, will do. I'll add a comment and round down. Your assumption is
> > > correct as well.
> > > 
> > > > Also, when the hardware is running at
> > > > 
> > > > 	.enabled = 1, .duty_cycle = 1/256 ms, .period = 1ms
> > > > 
> > > > and you reconfigure to
> > > > 
> > > > 	.enabled = 0, .duty_cycle = 1ms, .period = 1ms
> > > > 
> > > > the output might be active for > 1/256 ms if the process is preempted
> > > > between writing IQS620_PWM_DUTY_CYCLE and IQS620_PWR_SETTINGS_PWM_OUT.
> > > > 
> > > 
> > > Good catch. I think we can solve this by writing IQS620_PWM_DUTY_CYCLE
> > > first followed by IQS620_PWR_SETTINGS_PWM_OUT when the PWM is going to
> > > be enabled, and the reverse when the PWM is going to be disabled (i.e.
> > > turn OFF to prevent a stale duty cycle from being temporarily driven).
> > 
> > Sounds like a plan. After disabling you even don't need to write the
> > duty cycle register. (But there might be a discussion ahead that
> > .get_state should return the duty cycle.)
> >  
> 
> For v3, I've opted to write IQS620_PWM_DUTY_CYCLE regardless of whether the PWM
> is enabled as a matter of principle (i.e. faithfully pass the entire state down
> to the hardware without making assumptions).
> 
> And since some consumers (e.g. leds-pwm, the primary use-case for this PWM) may
> pre-configure the duty cycle while the PWM is disabled, this method ensures the
> driver is already compliant in case 01ccf903edd6 returns.
> 
> Also, as mentioned above, I have dropped the automatic disabling of the PWM to
> simulate a 0% duty cycle if the requested duty cycle is < 1 / 256 ms since the
> device does not actively drive a zero with IQS620_PWR_SETTINGS_PWM_OUT = 0. In
> the interest of transparency, here is what I currently have for v3:
> 
> static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> 			    const struct pwm_state *state)
> {
> 	struct iqs620_pwm_private *iqs620_pwm;
> 	struct iqs62x_core *iqs62x;
> 	int duty_scale, ret;
> 
> 	if (state->polarity != PWM_POLARITY_NORMAL)
> 		return -ENOTSUPP;
> 
> 	if (state->period < IQS620_PWM_PERIOD_NS)
> 		return -EINVAL;
> 
> 	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> 	iqs62x = iqs620_pwm->iqs62x;
> 
> 	if (!state->enabled) {
> 		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> 					 IQS620_PWR_SETTINGS_PWM_OUT, 0);
> 		if (ret)
> 			return ret;
> 	}
> 
> 	/*
> 	 * The duty cycle generated by the device is calculated as follows:
> 	 *
> 	 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> 	 *
> 	 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
> 	 * (inclusive). Therefore the lowest duty cycle the device can generate
> 	 * while the output is enabled is 1 / 256 ms.
> 	 */
> 	duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS - 1;

Hmm, this is violating the policy to implement a value not bigger than
requested with state->duty_cycle == 0. I see this has downsides to not
simply cheat here, but only claiming to have implemented 0% can hurt,
too. pwm-rcar returns -EINVAL in this case.

> 	ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> 			   clamp(duty_scale, 0, 0xFF));
> 	if (ret)
> 		return ret;

I understand your motivation to configure the duty cycle also when the
the request has enabled=false, but a strange side effect is that a
failure to configure the dutycycle with .enabled=false isn't really a
problem, is it?
(This is not a request to change anything, it's only expression of my
frustration that we cannot get away without strange effects.)

> 
> 	if (state->enabled)
> 		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> 					 IQS620_PWR_SETTINGS_PWM_OUT, 0xFF);
> 
> 	return ret;
> }
> 
> I believe this captures all of the discussion thus far; please let me know if you
> have any concerns.
> 
> > > > > +static int iqs620_pwm_notifier(struct notifier_block *notifier,
> > > > > +			       unsigned long event_flags, void *context)
> > > > > +{
> > > > > +	struct iqs620_pwm_private *iqs620_pwm;
> > > > > +	struct pwm_state state;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))
> > > > > +		return NOTIFY_DONE;
> > > > > +
> > > > > +	iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
> > > > > +				  notifier);
> > > > > +	pwm_get_state(&iqs620_pwm->chip.pwms[0], &state);
> > > > 
> > > > Please don't call pwm API functions in callbacks. I assume you rely on
> > > > pwm_get_state returning the previously set state and that
> > > > iqs620_pwm_get_state isn't called. Please use pwm->state for that.
> > > > 
> > > 
> > > Sure thing, will do. Your assumption is correct. If pwm_get_state called
> > > chip->ops->get_state instead of return pwm->state as it does today, this
> > > function would break because it would restore the hardware using default
> > > register values (since this function follows a reset).
> > > 
> > > Just for my own understanding, are you saying the PWM framework reserves
> > > the right to update pwm_get_state to call chip->ops->get_state some time
> > > in the future? In any event I will refer to pwm->state as that is what I
> > > ultimately need here.
> > 
> > This already was the case for a short time before v5.4. See 01ccf903edd6
> > and 40a6b9a00930. (And note that the lazyness mentioned above about not
> > needing to write duty_cycle when the PWM is off is what made the
> > approach break however.) I don't know yet how to proceed here. Being
> > able to get the actually implemented setting would be nice, probably it
> > is prudent to do this with another API function.
> > 
> > Other than that I consider it a layer violation to call a function that
> > is designed for consumers in a lowlevel driver. I don't know if we need
> > locking at some time, but if the core holded a lock when .apply is
> > called, .apply calls pwm_get_state which wanted to grab the lock again
> > we get a dead-lock.
> 
> I think we may be imagining two different hazards (please correct me if I have
> misunderstood). Originally I thought you were warning that pwm_get_state (which
> simply returns pwm->state) may in the future call chip->ops->get_state instead,
> which here would have caused iqs620_pwm_notifier to call iqs620_pwm_apply with
> a reset-like state rather than the last state requested by the user (since this
> notifier is called after the device resets itself).
> 
> The short-lived change during the 5.4-rc phase, however, was to fill pwm->state
> in pwm_apply_state with the quantized state from the hardware instead of the raw
> state requested by the user. Whether pwm->state (accessed directly or by calling
> pwm_get_state) gives the raw state or the quantized output of get_state following
> the last call to pwm_apply_state does not change the outcome (for this particular
> driver) because iqs620_pwm_apply ultimately writes the same register values. Just
> to be safe, I've been testing with and without 01ccf903edd6 applied locally so as
> to validate the behavior in both scenarios.
> 
> What I missed originally is that pwm_get_state is intended for consumers only, in
> which case I agree it is fundamentally wrong to cannibalize it in the driver. For
> v3 I have changed iqs620_pwm_notifier to reference pwm->state directly since that
> is what is ultimately needed in the first place.
> 
> You're correct that a lock within the core would cause a deadlock; in this case I
> was proposing a lock around the pair of reads/writes to IQS620_PWM_DUTY_CYCLE and
> IQS620_PWR_SETTINGS_PWM_OUT since v2 introduced an implied relationship between
> the two. That would be safe since chip->ops->apply returns before pwm_apply_state
> calls chip->ops->get_state.
> 
> However, even that is no longer necessary since IQS620_PWR_SETTINGS_PWM_OUT is now
> written independently of IQS620_PWM_DUTY_CYCLE due to withdrawing support for a 0%
> duty cycle starting in v3. In short, I didn't end up adding any locks as there was
> no need.

That's fine. I'm not sure we actually need locks in the framework in the
long run because (other than clocks) a PWM always has at most a single
consumer and up to now it seems reasonable that this single consumer
doesn't race with itself.
 
Best regards
Uwe

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

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

* Re: [PATCH v2 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625
  2019-12-09  0:38 ` [PATCH v2 1/7] dt-bindings: Add bindings " Jeff LaBundy
@ 2019-12-18 23:52   ` Rob Herring
  2019-12-20  4:00     ` Jeff LaBundy
  0 siblings, 1 reply; 30+ messages in thread
From: Rob Herring @ 2019-12-18 23:52 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, mark.rutland

On Mon, Dec 09, 2019 at 12:38:32AM +0000, Jeff LaBundy wrote:
> This patch adds device tree bindings for the Azoteq IQS620A, IQS621,
> IQS622, IQS624 and IQS625 multi-function sensors.
> 
> A total of three bindings are presented (one MFD and two child nodes);
> they are submitted as a single patch because the child node bindings
> have no meaning in the absence of the MFD binding.
> 
> Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> ---
> Changes in v2:
>   - Removed "prox" child node and moved "keys" and "pwm" child nodes to their
>     own bindings
>   - Replaced linux,fw-file property with more common firmware-name property
>   - Converted all bindings to YAML

Good job for first go.

> 
>  .../devicetree/bindings/input/iqs62x-keys.yaml     | 126 +++++++++++++++
>  Documentation/devicetree/bindings/mfd/iqs62x.yaml  | 177 +++++++++++++++++++++
>  .../devicetree/bindings/pwm/iqs620a-pwm.yaml       |  30 ++++
>  3 files changed, 333 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/iqs62x-keys.yaml
>  create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.yaml
>  create mode 100644 Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml

A couple of minor things below. With those fixed:

Reviewed-by: Rob Herring <robh@kernel.org>

> 
> diff --git a/Documentation/devicetree/bindings/input/iqs62x-keys.yaml b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> new file mode 100644
> index 0000000..e9b54e0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> @@ -0,0 +1,126 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/iqs62x-keys.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Azoteq IQS620A/621/622/624/625 Keys and Switches
> +
> +maintainers:
> +  - Jeff LaBundy <jeff@labundy.com>
> +
> +description: |
> +  The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors
> +  feature a variety of self-capacitive, mutual-inductive and Hall-effect sens-
> +  ing capabilities that can facilitate a variety of contactless key and switch
> +  applications.
> +
> +  These functions are collectively represented by a "keys" child node from the
> +  parent MFD driver. See Documentation/devicetree/bindings/mfd/iqs62x.yaml for
> +  further details and examples. Sensor hardware configuration (self-capacitive
> +  vs. mutual-inductive, etc.) is selected based on the device's firmware.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - azoteq,iqs620a-keys
> +      - azoteq,iqs621-keys
> +      - azoteq,iqs622-keys
> +      - azoteq,iqs624-keys
> +      - azoteq,iqs625-keys
> +
> +  linux,keycodes:
> +    allOf:
> +      - $ref: /schemas/types.yaml#/definitions/uint32-array
> +      - minItems: 1
> +        maxItems: 16
> +    description: |
> +      Specifies the numeric keycodes associated with each available touch or
> +      proximity event according to the following table. An 'x' indicates the
> +      event is supported for a given device. Specify 0 for unused events.
> +
> +      -------------------------------------------------------------------------
> +      | #  | Event              | IQS620A | IQS621 | IQS622 | IQS624 | IQS625 |
> +      -------------------------------------------------------------------------
> +      | 0  | CH0 Touch          |    x    |    x   |    x   |    x   |    x   |
> +      |    | Antenna 1 Touch*   |    x    |        |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 1  | CH0 Proximity      |    x    |    x   |    x   |    x   |    x   |
> +      |    | Antenna 1 Prox.*   |    x    |        |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 2  | CH1 Touch          |    x    |    x   |    x   |    x   |    x   |
> +      |    | Ant. 1 Deep Touch* |    x    |        |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 3  | CH1 Proximity      |    x    |    x   |    x   |    x   |    x   |
> +      -------------------------------------------------------------------------
> +      | 4  | CH2 Touch          |    x    |        |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 5  | CH2 Proximity      |    x    |        |        |        |        |
> +      |    | Antenna 2 Prox.*   |    x    |        |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 6  | Metal (+) Touch**  |    x    |    x   |        |        |        |
> +      |    | Ant. 2 Deep Touch* |    x    |        |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 7  | Metal (+) Prox.**  |    x    |    x   |        |        |        |
> +      |    | Antenna 2 Touch*   |    x    |        |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 8  | Metal (-) Touch**  |    x    |    x   |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 9  | Metal (-) Prox.**  |    x    |    x   |        |        |        |
> +      -------------------------------------------------------------------------
> +      | 10 | SAR Active***      |    x    |        |    x   |        |        |
> +      -------------------------------------------------------------------------
> +      | 11 | SAR Quick Rel.***  |    x    |        |    x   |        |        |
> +      -------------------------------------------------------------------------
> +      | 12 | SAR Movement***    |    x    |        |    x   |        |        |
> +      -------------------------------------------------------------------------
> +      | 13 | SAR Filter Halt*** |    x    |        |    x   |        |        |
> +      -------------------------------------------------------------------------
> +      | 14 | Wheel Up           |         |        |        |    x   |        |
> +      -------------------------------------------------------------------------
> +      | 15 | Wheel Down         |         |        |        |    x   |        |
> +      -------------------------------------------------------------------------
> +      *   Two-channel SAR. Replaces CH0-2 plus metal touch and proximity events
> +          if enabled via firmware.
> +      **  "+" and "-" refer to the polarity of a channel's delta (LTA - counts),
> +          where "LTA" is defined as the channel's long-term average.
> +      *** One-channel SAR. Replaces CH0-2 touch and proximity events if enabled
> +          via firmware.
> +
> +required:
> +  - compatible
> +  - linux,keycodes

Add: 

additionalProperties: false

> +
> +if:
> +  properties:
> +    compatible:
> +      contains:
> +        enum:
> +          - azoteq,iqs620a-keys
> +          - azoteq,iqs621-keys
> +          - azoteq,iqs622-keys
> +then:
> +  patternProperties:
> +    "^hall-switch-(north|south)$":
> +      type: object
> +      description:
> +        Represents north/south-field Hall-effect sensor touch or proximity
> +        events. Note that north/south-field orientation is reversed on the
> +        IQS620AXzCSR device due to its flip-chip package.
> +
> +      properties:
> +        linux,code:
> +          $ref: /schemas/types.yaml#/definitions/uint32
> +          description: Numeric switch code associated with the event.
> +
> +        azoteq,use-prox:
> +          $ref: /schemas/types.yaml#/definitions/flag
> +          description:
> +            If present, specifies that Hall-effect sensor reporting should
> +            use the device's wide-range proximity threshold instead of its
> +            close-range touch threshold (default).
> +
> +      required:
> +        - linux,code
> +
> +...
> diff --git a/Documentation/devicetree/bindings/mfd/iqs62x.yaml b/Documentation/devicetree/bindings/mfd/iqs62x.yaml
> new file mode 100644
> index 0000000..24e6004
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/iqs62x.yaml
> @@ -0,0 +1,177 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/mfd/iqs62x.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
> +
> +maintainers:
> +  - Jeff LaBundy <jeff@labundy.com>
> +
> +description: |
> +  The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors
> +  integrate multiple sensing technologies in a single package.
> +
> +  Link to data sheets: https://www.azoteq.com/
> +
> +properties:
> +  compatible:
> +    enum:
> +      - azoteq,iqs620a
> +      - azoteq,iqs621
> +      - azoteq,iqs622
> +      - azoteq,iqs624
> +      - azoteq,iqs625
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  firmware-name:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    description:
> +      Specifies the name of the calibration and configuration file selected by
> +      the driver. If this property is omitted, the name is chosen based on the
> +      device name with ".bin" as the extension (e.g. iqs620a.bin for IQS620A).
> +
> +  keys:
> +    $ref: ../input/iqs62x-keys.yaml
> +
> +  pwm:
> +    $ref: ../pwm/iqs620a-pwm.yaml
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts

Add: 

additionalProperties: false

> +
> +examples:
> +  - |
> +    /*
> +     * Dual capacitive buttons with additional "air button," unipolar lid
> +     * switch and panel-mounted LED.
> +     */
> +    #include <dt-bindings/input/input.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +
> +    i2c {
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            iqs620a@44 {
> +                    compatible = "azoteq,iqs620a";
> +                    reg = <0x44>;
> +                    interrupt-parent = <&gpio>;
> +                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
> +
> +                    keys {
> +                            compatible = "azoteq,iqs620a-keys";
> +
> +                            linux,keycodes = <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>;
> +            };
> +    };
> +
> +  - |
> +    /* Single inductive button with bipolar dock/tablet-mode switch. */
> +    #include <dt-bindings/input/input.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +
> +    i2c {
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            iqs620a@44 {
> +                    compatible = "azoteq,iqs620a";
> +                    reg = <0x44>;
> +                    interrupt-parent = <&gpio>;
> +                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
> +
> +                    firmware-name = "iqs620a_coil.bin";
> +
> +                    keys {
> +                            compatible = "azoteq,iqs620a-keys";
> +
> +                            linux,keycodes = <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <KEY_MUTE>;
> +
> +                            hall-switch-north {
> +                                    linux,code = <SW_DOCK>;
> +                            };
> +
> +                            hall-switch-south {
> +                                    linux,code = <SW_TABLET_MODE>;
> +                            };
> +                    };
> +            };
> +    };
> +
> +  - |
> +    /* Dual capacitive buttons with volume knob. */
> +    #include <dt-bindings/input/input.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +
> +    i2c {
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            iqs624@44 {
> +                    compatible = "azoteq,iqs624";
> +                    reg = <0x44>;
> +                    interrupt-parent = <&gpio>;
> +                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
> +
> +                    keys {
> +                            compatible = "azoteq,iqs624-keys";
> +
> +                            linux,keycodes = <BTN_0>,
> +                                             <0>,
> +                                             <BTN_1>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <0>,
> +                                             <KEY_VOLUMEUP>,
> +                                             <KEY_VOLUMEDOWN>;
> +                    };
> +            };
> +    };
> +
> +...
> diff --git a/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
> new file mode 100644
> index 0000000..6b7aaef
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
> @@ -0,0 +1,30 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pwm/iqs620a-pwm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Azoteq IQS620A PWM Generator
> +
> +maintainers:
> +  - Jeff LaBundy <jeff@labundy.com>
> +
> +description: |
> +  The Azoteq IQS620A multi-function sensor generates a fixed-frequency PWM
> +  output represented by a "pwm" child node from the parent MFD driver. See
> +  Documentation/devicetree/bindings/mfd/iqs62x.yaml for further details as
> +  well as an example.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - azoteq,iqs620a-pwm
> +
> +  "#pwm-cells":
> +    const: 2
> +
> +required:
> +  - compatible
> +  - "#pwm-cells"

Add: 

additionalProperties: false

> +
> +...
> --
> 2.7.4
> 

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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2019-12-16  9:19           ` Uwe Kleine-König
@ 2019-12-20  3:19             ` Jeff LaBundy
  2019-12-20  8:59               ` Uwe Kleine-König
  0 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-20  3:19 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: mark.rutland, devicetree, lars, kernel, linux-pwm, linux-iio,
	dmitry.torokhov, robh+dt, thierry.reding, pmeerw, linux-input,
	lee.jones, jic23, knaack.h

Hi Uwe,

I apologize for my delayed replies as I have been traveling.

On Mon, Dec 16, 2019 at 10:19:12AM +0100, Uwe Kleine-König wrote:
> Hello Jeff,
> 
> On Sun, Dec 15, 2019 at 08:36:12PM +0000, Jeff LaBundy wrote:
> > On Tue, Dec 10, 2019 at 08:22:27AM +0100, Uwe Kleine-König wrote:
> > > On Tue, Dec 10, 2019 at 12:03:02AM +0000, Jeff LaBundy wrote:
> > > > On Mon, Dec 09, 2019 at 08:32:06AM +0100, Uwe Kleine-König wrote:
> > > > > On Mon, Dec 09, 2019 at 12:38:36AM +0000, 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>
> > > > > > ---
> > > > > > Changes in v2:
> > > > > >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> > > > > >   - Added 'Limitations' section to introductory comments
> > > > > >   - Replaced 'error' with 'ret' throughout
> > > > > >   - Added const qualifier to state argument of iqs620_pwm_apply and removed all
> > > > > >     modifications to the variable's contents
> > > > > >   - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
> > > > > >     polarity is inverted or the requested period is below 1 ms, respectively
> > > > > >   - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
> > > > > >   - Added iqs620_pwm_get_state
> > > > > >   - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
> > > > > >   - Moved notifier unregistration to already present iqs620_pwm_remove, which
> > > > > >     eliminated the need for a device-managed action and ready flag
> > > > > >   - Added a comment in iqs620_pwm_probe to explain the order of operations
> > > > > >   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> > > > > > 
> > > > > >  drivers/pwm/Kconfig       |  10 +++
> > > > > >  drivers/pwm/Makefile      |   1 +
> > > > > >  drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
> > > > > >  3 files changed, 217 insertions(+)
> > > > > >  create mode 100644 drivers/pwm/pwm-iqs620a.c
> > > > > > 
> > > > > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > > > > > index bd21655..60bcf6c 100644
> > > > > > --- a/drivers/pwm/Kconfig
> > > > > > +++ b/drivers/pwm/Kconfig
> > > > > > @@ -222,6 +222,16 @@ config PWM_IMX_TPM
> > > > > >  	  To compile this driver as a module, choose M here: the module
> > > > > >  	  will be called pwm-imx-tpm.
> > > > > > 
> > > > > > +config PWM_IQS620A
> > > > > > +	tristate "Azoteq IQS620A PWM support"
> > > > > > +	depends on MFD_IQS62X || COMPILE_TEST
> > > > > > +	help
> > > > > > +	  Generic PWM framework driver for the Azoteq IQS620A multi-function
> > > > > > +	  sensor.
> > > > > > +
> > > > > > +	  To compile this driver as a module, choose M here: the module will
> > > > > > +	  be called pwm-iqs620a.
> > > > > > +
> > > > > >  config PWM_JZ4740
> > > > > >  	tristate "Ingenic JZ47xx PWM support"
> > > > > >  	depends on MACH_INGENIC
> > > > > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> > > > > > index 9a47507..a59c710 100644
> > > > > > --- a/drivers/pwm/Makefile
> > > > > > +++ b/drivers/pwm/Makefile
> > > > > > @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> > > > > >  obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
> > > > > >  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
> > > > > >  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> > > > > > +obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
> > > > > >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> > > > > >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> > > > > >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > > > > > diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
> > > > > > new file mode 100644
> > > > > > index 0000000..1ea11b9
> > > > > > --- /dev/null
> > > > > > +++ b/drivers/pwm/pwm-iqs620a.c
> > > > > > @@ -0,0 +1,206 @@
> > > > > > +// SPDX-License-Identifier: GPL-2.0+
> > > > > > +/*
> > > > > > + * Azoteq IQS620A PWM Generator
> > > > > > + *
> > > > > > + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> > > > > > + *
> > > > > > + * Limitations:
> > > > > > + * - The period is not guaranteed to run to completion when the duty cycle is
> > > > > > + *   changed or the output is disabled.
> > > > > 
> > > > > Do you know more details here? "not guaranteed" means that the new
> > > > > period starts immediately when duty_cycle or the enabled bit is written?
> > > > > 
> > > > 
> > > > Increasing the duty cycle on-the-fly (e.g. 25% to 75%) results in the
> > > > following behavior (depending on where the I2C write falls):
> > > > 
> > > >                        I2C write
> > > >    __        __        __  V_    ______    ______    ______    __
> > > > __|  |______|  |______|  |_|x|__|      |__|      |__|      |__|
> > > >   ^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^
> > > > 
> > > > The PWM continues to tick at 1 ms, but the currently running period suffers
> > > > an extraneous pulse as the output is abruptly set high to "catch up" to the
> > > > new duty cycle.
> > > > 
> > > > A similar behavior can occur if the duty cycle is decreased, meaning the
> > > > output is abruptly set low if the I2C transaction completes in what has
> > > > suddenly become the inactive region of the currently running period.
> > > > 
> > > > The PWM seems to be a simple counter that rolls over at a period of 1 ms.
> > > > Both the counter and the IQS620_PWM_DUTY_CYCLE register effectively go to
> > > > a comparator whose output is ANDed with IQS620_PWR_SETTINGS_PWM_OUT which
> > > > then drives the PWM output.
> > > > 
> > > > As such, if either IQS620_PWM_DUTY_CYCLE or IQS620_PWR_SETTINGS_PWM_OUT
> > > > change, so may the PWM output state depending on the counter's value at
> > > > the time the I2C write is completed within the 1-ms continuous loop.
> > > > 
> > > > For v3 I will update the note as follows:
> > > > 
> > > > - Changes in duty cycle or enable/disable state are immediately reflected
> > > >   by the PWM output and are not aligned to the start of any period.
> > > 
> > > I'd like to see a bit more information in the driver. Something about
> > > the 1ms rhythm being unaffected by the duty_cycle and enable setting.
> > > Maybe:
> > > 
> > >  - The periods run continuously with a fixed length of 1 ms which is
> > >    unaffected by register updates. Writing duty cycle or enable
> > >    registers gets active immediately which might result in glitches.
> > > 
> > > ?
> > > 
> > 
> > I adjusted the wording a bit as per my preference and settled on the
> > following:
> > 
> >   - The period is fixed to 1 ms and is generated continuously despite changes
> >     to the duty cycle or enable/disable state.
> >   - Changes to the duty cycle or enable/disable state take effect immediately
> >     and may result in a glitch during the period in which the change is made.
> > 
> > I believe these capture the spirit of your message; please let me know if
> > you have any concerns.
> 
> That's fine.
> 
> > Upon further experimentation, I found that disabling the output (which v2
> > does so as to simulate a 0% duty cycle) does not actively drive zero, but
> > rather places the output in a high-impedance state with only the device's
> > own internal leakage eventually discharging the pin.
> 
> But maybe this is still the best you can do in this case. @Thierry, what
> do you think?
> 
> > This is fundamentally different than actively driving the pin low to make
> > a 0% duty cycle, which does not appear to be possible at all. Therefore I
> > have removed the control of IQS620_PWR_SETTINGS_PWM_OUT based on the duty
> > cycle requested by the user and reverted to the behavior of v1, where the
> > duty cycle requested by the user is mapped only to IQS620_PWM_DUTY_CYCLE.
> > 
> > As such, I have also added a third bullet point similar to what you first
> > suggested following v1:
> > 
> >   - The device cannot generate a 0% duty cycle.
> 
> Then this would be:
> 
>   - The device cannot actively drive a 0% duty cycle. The driver is
>     disabled for small duty_cycles relying on a pull down on the board.
> 
> But maybe it would be more prudent to do this only if the board
> configuration suggests this is save?!
> 

Given the policy for the actual duty cycle generated by the hardware not to
exceed that which is requested by the user, it seems there are ultimately 3
options for duty_cycle below 1 / 256 ms:

1. Return -EINVAL.
2. Disable the output as in v2.
3. Add an optional boolean in the dts that identifies whether a pull-down is
   present; default to option (1) but use option (2) if the boolean is there.

I don't like option (1) because consumers (including leds-pwm) do in fact ask
for a 0% duty cycle which would make iqs620_pwm_apply return an error. Things
happen to still work since leds-pwm does not check pwm_config's return value,
but I don't want to rely on this coincidence.

Option (2) is better, but I know from experience that board designers do not
consult driver comments and the requirement to add a pull-down may be easily
missed as it is not discussed in the data sheet (which is where that sort of
information belongs, in my opinion).

Option (3) seems like overkill for such a simple PWM, and ultimately doesn't
add any value because I don't want to allow option (1) behavior in any case.
Whether the PWM is disabled because it is truly disabled or to simulate a 0%
duty cycle as in option (2), the pull-down is ultimately required regardless
of whether or not the data sheet happens to go into such detail.

Therefore I have opted to carry forward option (2) from v2 to v3. I reworded
the third limitation a bit as follows:

- The device cannot generate a 0% duty cycle. For duty cycles below 1 / 256
  ms, the output is disabled and relies upon an external pull-down resistor
  to hold the GPIO3/LTX pin low.

I did reach out to the vendor and asked them to consider recommending a pull-
down resistor in a future revision of the data sheet, although at the time of
this writing I have not heard back.

> > > > > > + * - The period is fixed to 1 ms.
> > > > > > + */
> > > > > > +
> > > > > > +#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;
> > > > > > +};
> > > > > > +
> > > > > > +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > > > > +			    const struct pwm_state *state)
> > > > > > +{
> > > > > > +	struct iqs620_pwm_private *iqs620_pwm;
> > > > > > +	struct iqs62x_core *iqs62x;
> > > > > > +	unsigned int pwm_out = 0;
> > > > > > +	int duty_scale, ret;
> > > > > > +
> > > > > > +	if (state->polarity != PWM_POLARITY_NORMAL)
> > > > > > +		return -ENOTSUPP;
> > > > > > +
> > > > > > +	if (state->period < IQS620_PWM_PERIOD_NS)
> > > > > > +		return -EINVAL;
> > > > > > +
> > > > > > +	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > > > > > +	iqs62x = iqs620_pwm->iqs62x;
> > > > > > +
> > > > > > +	duty_scale = DIV_ROUND_CLOSEST(state->duty_cycle * 256,
> > > > > > +				       IQS620_PWM_PERIOD_NS);
> > > > > > +
> > > > > > +	if (duty_scale) {
> > > > > > +		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > > > > > +				   min(duty_scale - 1, 0xFF));
> > > > > > +		if (ret)
> > > > > > +			return ret;
> > > > > > +
> > > > > > +		if (state->enabled)
> > > > > > +			pwm_out = IQS620_PWR_SETTINGS_PWM_OUT;
> > > > > > +	}
> > > > > > +
> > > > > > +	return regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > > > > > +				  IQS620_PWR_SETTINGS_PWM_OUT, pwm_out);
> > > > > 
> > > > > A comment explaining the semantic here would be good. I assume
> > > > > IQS620_PWM_DUTY_CYCLE takes a value between 0 and 255 and the resulting
> > > > > duty cycle is:
> > > > > 
> > > > > 	(IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> > > > > 
> > > > > .
> > > > > 
> > > > > If this is right, please use:
> > > > > 
> > > > > 	duty_scale = (state->duty_cycle * 256) / IQS620_PWM_PERIOD_NS
> > > > > 
> > > > 
> > > > Sure thing, will do. I'll add a comment and round down. Your assumption is
> > > > correct as well.
> > > > 
> > > > > Also, when the hardware is running at
> > > > > 
> > > > > 	.enabled = 1, .duty_cycle = 1/256 ms, .period = 1ms
> > > > > 
> > > > > and you reconfigure to
> > > > > 
> > > > > 	.enabled = 0, .duty_cycle = 1ms, .period = 1ms
> > > > > 
> > > > > the output might be active for > 1/256 ms if the process is preempted
> > > > > between writing IQS620_PWM_DUTY_CYCLE and IQS620_PWR_SETTINGS_PWM_OUT.
> > > > > 
> > > > 
> > > > Good catch. I think we can solve this by writing IQS620_PWM_DUTY_CYCLE
> > > > first followed by IQS620_PWR_SETTINGS_PWM_OUT when the PWM is going to
> > > > be enabled, and the reverse when the PWM is going to be disabled (i.e.
> > > > turn OFF to prevent a stale duty cycle from being temporarily driven).
> > > 
> > > Sounds like a plan. After disabling you even don't need to write the
> > > duty cycle register. (But there might be a discussion ahead that
> > > .get_state should return the duty cycle.)
> > >  
> > 
> > For v3, I've opted to write IQS620_PWM_DUTY_CYCLE regardless of whether the PWM
> > is enabled as a matter of principle (i.e. faithfully pass the entire state down
> > to the hardware without making assumptions).
> > 
> > And since some consumers (e.g. leds-pwm, the primary use-case for this PWM) may
> > pre-configure the duty cycle while the PWM is disabled, this method ensures the
> > driver is already compliant in case 01ccf903edd6 returns.
> > 
> > Also, as mentioned above, I have dropped the automatic disabling of the PWM to
> > simulate a 0% duty cycle if the requested duty cycle is < 1 / 256 ms since the
> > device does not actively drive a zero with IQS620_PWR_SETTINGS_PWM_OUT = 0. In
> > the interest of transparency, here is what I currently have for v3:
> > 
> > static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > 			    const struct pwm_state *state)
> > {
> > 	struct iqs620_pwm_private *iqs620_pwm;
> > 	struct iqs62x_core *iqs62x;
> > 	int duty_scale, ret;
> > 
> > 	if (state->polarity != PWM_POLARITY_NORMAL)
> > 		return -ENOTSUPP;
> > 
> > 	if (state->period < IQS620_PWM_PERIOD_NS)
> > 		return -EINVAL;
> > 
> > 	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > 	iqs62x = iqs620_pwm->iqs62x;
> > 
> > 	if (!state->enabled) {
> > 		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > 					 IQS620_PWR_SETTINGS_PWM_OUT, 0);
> > 		if (ret)
> > 			return ret;
> > 	}
> > 
> > 	/*
> > 	 * The duty cycle generated by the device is calculated as follows:
> > 	 *
> > 	 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> > 	 *
> > 	 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
> > 	 * (inclusive). Therefore the lowest duty cycle the device can generate
> > 	 * while the output is enabled is 1 / 256 ms.
> > 	 */
> > 	duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS - 1;
> 
> Hmm, this is violating the policy to implement a value not bigger than
> requested with state->duty_cycle == 0. I see this has downsides to not
> simply cheat here, but only claiming to have implemented 0% can hurt,
> too. pwm-rcar returns -EINVAL in this case.
> 

That's a great point and is addressed by sticking with option (2) described
above. Here is what I've got for v3:

static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
			    const struct pwm_state *state)
{
	struct iqs620_pwm_private *iqs620_pwm;
	struct iqs62x_core *iqs62x;
	int duty_scale, ret;

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

	if (state->period < IQS620_PWM_PERIOD_NS)
		return -EINVAL;

	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
	iqs62x = iqs620_pwm->iqs62x;

	mutex_lock(&iqs620_pwm->lock);

	/*
	 * The duty cycle generated by the device is calculated as follows:
	 *
	 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
	 *
	 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
	 * (inclusive). Therefore the lowest duty cycle the device can generate
	 * while the output is enabled is 1 / 256 ms.
	 *
	 * For lower duty cycles (e.g. 0), the PWM output is simply disabled to
	 * allow an on-board pull-down resistor to hold the GPIO3/LTX pin low.
	 */
	duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS;

	if (!state->enabled || !duty_scale) {
		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
					 IQS620_PWR_SETTINGS_PWM_OUT, 0);
		if (ret)
			goto err_mutex;
	}

	if (duty_scale) {
		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
				   min(duty_scale - 1, 0xFF));
		if (ret)
			goto err_mutex;
	}

	if (state->enabled && duty_scale)
		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
					 IQS620_PWR_SETTINGS_PWM_OUT, 0xFF);

err_mutex:
	mutex_unlock(&iqs620_pwm->lock);

	return ret;
}

And for the get_state callback:

static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
				 struct pwm_state *state)
{
	struct iqs620_pwm_private *iqs620_pwm;
	struct iqs62x_core *iqs62x;
	unsigned int val;
	int ret;

	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
	iqs62x = iqs620_pwm->iqs62x;

	mutex_lock(&iqs620_pwm->lock);

	ret = regmap_read(iqs62x->map, IQS620_PWR_SETTINGS, &val);
	if (ret)
		goto err_mutex;
	state->enabled = val & IQS620_PWR_SETTINGS_PWM_OUT;

	ret = regmap_read(iqs62x->map, IQS620_PWM_DUTY_CYCLE, &val);
	if (ret)
		goto err_mutex;
	state->duty_cycle = DIV_ROUND_UP((val + 1) * IQS620_PWM_PERIOD_NS, 256);
	state->period = IQS620_PWM_PERIOD_NS;

err_mutex:
	mutex_unlock(&iqs620_pwm->lock);

	if (ret)
		dev_err(iqs620_pwm->chip.dev, "Failed to get state: %d\n", ret);
}

If you and/or Thierry have any concerns, please let me know.

> > 	ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > 			   clamp(duty_scale, 0, 0xFF));
> > 	if (ret)
> > 		return ret;
> 
> I understand your motivation to configure the duty cycle also when the
> the request has enabled=false, but a strange side effect is that a
> failure to configure the dutycycle with .enabled=false isn't really a
> problem, is it?
> (This is not a request to change anything, it's only expression of my
> frustration that we cannot get away without strange effects.)
> 

True, but it would definitely be a problem in case 01ccf903edd6 returns and
we're relying on the device's own registers to hold the PWM state.

Thinking about this more, I agree with your earlier comment that a means to
get the actual (quantized) state needs to be a new API function (of integer
type). Since chip->ops->get_state is void, there is no way for the callback
to warn the core that a register read failed and the PWM state may be junk.

> > 
> > 	if (state->enabled)
> > 		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > 					 IQS620_PWR_SETTINGS_PWM_OUT, 0xFF);
> > 
> > 	return ret;
> > }
> > 
> > I believe this captures all of the discussion thus far; please let me know if you
> > have any concerns.
> > 
> > > > > > +static int iqs620_pwm_notifier(struct notifier_block *notifier,
> > > > > > +			       unsigned long event_flags, void *context)
> > > > > > +{
> > > > > > +	struct iqs620_pwm_private *iqs620_pwm;
> > > > > > +	struct pwm_state state;
> > > > > > +	int ret;
> > > > > > +
> > > > > > +	if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))
> > > > > > +		return NOTIFY_DONE;
> > > > > > +
> > > > > > +	iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
> > > > > > +				  notifier);
> > > > > > +	pwm_get_state(&iqs620_pwm->chip.pwms[0], &state);
> > > > > 
> > > > > Please don't call pwm API functions in callbacks. I assume you rely on
> > > > > pwm_get_state returning the previously set state and that
> > > > > iqs620_pwm_get_state isn't called. Please use pwm->state for that.
> > > > > 
> > > > 
> > > > Sure thing, will do. Your assumption is correct. If pwm_get_state called
> > > > chip->ops->get_state instead of return pwm->state as it does today, this
> > > > function would break because it would restore the hardware using default
> > > > register values (since this function follows a reset).
> > > > 
> > > > Just for my own understanding, are you saying the PWM framework reserves
> > > > the right to update pwm_get_state to call chip->ops->get_state some time
> > > > in the future? In any event I will refer to pwm->state as that is what I
> > > > ultimately need here.
> > > 
> > > This already was the case for a short time before v5.4. See 01ccf903edd6
> > > and 40a6b9a00930. (And note that the lazyness mentioned above about not
> > > needing to write duty_cycle when the PWM is off is what made the
> > > approach break however.) I don't know yet how to proceed here. Being
> > > able to get the actually implemented setting would be nice, probably it
> > > is prudent to do this with another API function.
> > > 
> > > Other than that I consider it a layer violation to call a function that
> > > is designed for consumers in a lowlevel driver. I don't know if we need
> > > locking at some time, but if the core holded a lock when .apply is
> > > called, .apply calls pwm_get_state which wanted to grab the lock again
> > > we get a dead-lock.
> > 
> > I think we may be imagining two different hazards (please correct me if I have
> > misunderstood). Originally I thought you were warning that pwm_get_state (which
> > simply returns pwm->state) may in the future call chip->ops->get_state instead,
> > which here would have caused iqs620_pwm_notifier to call iqs620_pwm_apply with
> > a reset-like state rather than the last state requested by the user (since this
> > notifier is called after the device resets itself).
> > 
> > The short-lived change during the 5.4-rc phase, however, was to fill pwm->state
> > in pwm_apply_state with the quantized state from the hardware instead of the raw
> > state requested by the user. Whether pwm->state (accessed directly or by calling
> > pwm_get_state) gives the raw state or the quantized output of get_state following
> > the last call to pwm_apply_state does not change the outcome (for this particular
> > driver) because iqs620_pwm_apply ultimately writes the same register values. Just
> > to be safe, I've been testing with and without 01ccf903edd6 applied locally so as
> > to validate the behavior in both scenarios.
> > 
> > What I missed originally is that pwm_get_state is intended for consumers only, in
> > which case I agree it is fundamentally wrong to cannibalize it in the driver. For
> > v3 I have changed iqs620_pwm_notifier to reference pwm->state directly since that
> > is what is ultimately needed in the first place.
> > 
> > You're correct that a lock within the core would cause a deadlock; in this case I
> > was proposing a lock around the pair of reads/writes to IQS620_PWM_DUTY_CYCLE and
> > IQS620_PWR_SETTINGS_PWM_OUT since v2 introduced an implied relationship between
> > the two. That would be safe since chip->ops->apply returns before pwm_apply_state
> > calls chip->ops->get_state.
> > 
> > However, even that is no longer necessary since IQS620_PWR_SETTINGS_PWM_OUT is now
> > written independently of IQS620_PWM_DUTY_CYCLE due to withdrawing support for a 0%
> > duty cycle starting in v3. In short, I didn't end up adding any locks as there was
> > no need.
> 
> That's fine. I'm not sure we actually need locks in the framework in the
> long run because (other than clocks) a PWM always has at most a single
> consumer and up to now it seems reasonable that this single consumer
> doesn't race with itself.
>  

Agreed. You will see that I have added my own lock in the apply/get_state functions
now that I've restored v2's behavior, but that is to account for this device's case
where its interrupt handler is in the process of re-initializing the couple of PWM-
related registers when get_state is called.

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

Kind regards,
Jeff LaBundy

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

* Re: [PATCH v2 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625
  2019-12-18 23:52   ` Rob Herring
@ 2019-12-20  4:00     ` Jeff LaBundy
  2019-12-24 21:55       ` Rob Herring
  0 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2019-12-20  4:00 UTC (permalink / raw)
  To: Rob Herring
  Cc: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, mark.rutland

Hi Rob,

Thank you for your prompt review and your kind words. A couple of questions
and comments for you below.

On Wed, Dec 18, 2019 at 05:52:52PM -0600, Rob Herring wrote:
> On Mon, Dec 09, 2019 at 12:38:32AM +0000, Jeff LaBundy wrote:
> > This patch adds device tree bindings for the Azoteq IQS620A, IQS621,
> > IQS622, IQS624 and IQS625 multi-function sensors.
> > 
> > A total of three bindings are presented (one MFD and two child nodes);
> > they are submitted as a single patch because the child node bindings
> > have no meaning in the absence of the MFD binding.
> > 
> > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > ---
> > Changes in v2:
> >   - Removed "prox" child node and moved "keys" and "pwm" child nodes to their
> >     own bindings
> >   - Replaced linux,fw-file property with more common firmware-name property
> >   - Converted all bindings to YAML
> 
> Good job for first go.
> 
> > 
> >  .../devicetree/bindings/input/iqs62x-keys.yaml     | 126 +++++++++++++++
> >  Documentation/devicetree/bindings/mfd/iqs62x.yaml  | 177 +++++++++++++++++++++
> >  .../devicetree/bindings/pwm/iqs620a-pwm.yaml       |  30 ++++
> >  3 files changed, 333 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> >  create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.yaml
> >  create mode 100644 Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
> 
> A couple of minor things below. With those fixed:
> 
> Reviewed-by: Rob Herring <robh@kernel.org>
> 
> > 
> > diff --git a/Documentation/devicetree/bindings/input/iqs62x-keys.yaml b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> > new file mode 100644
> > index 0000000..e9b54e0
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> > @@ -0,0 +1,126 @@
> > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/input/iqs62x-keys.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Azoteq IQS620A/621/622/624/625 Keys and Switches
> > +
> > +maintainers:
> > +  - Jeff LaBundy <jeff@labundy.com>
> > +
> > +description: |
> > +  The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors
> > +  feature a variety of self-capacitive, mutual-inductive and Hall-effect sens-
> > +  ing capabilities that can facilitate a variety of contactless key and switch
> > +  applications.
> > +
> > +  These functions are collectively represented by a "keys" child node from the
> > +  parent MFD driver. See Documentation/devicetree/bindings/mfd/iqs62x.yaml for
> > +  further details and examples. Sensor hardware configuration (self-capacitive
> > +  vs. mutual-inductive, etc.) is selected based on the device's firmware.
> > +
> > +properties:
> > +  compatible:
> > +    enum:
> > +      - azoteq,iqs620a-keys
> > +      - azoteq,iqs621-keys
> > +      - azoteq,iqs622-keys
> > +      - azoteq,iqs624-keys
> > +      - azoteq,iqs625-keys
> > +
> > +  linux,keycodes:
> > +    allOf:
> > +      - $ref: /schemas/types.yaml#/definitions/uint32-array
> > +      - minItems: 1
> > +        maxItems: 16
> > +    description: |
> > +      Specifies the numeric keycodes associated with each available touch or
> > +      proximity event according to the following table. An 'x' indicates the
> > +      event is supported for a given device. Specify 0 for unused events.
> > +
> > +      -------------------------------------------------------------------------
> > +      | #  | Event              | IQS620A | IQS621 | IQS622 | IQS624 | IQS625 |
> > +      -------------------------------------------------------------------------
> > +      | 0  | CH0 Touch          |    x    |    x   |    x   |    x   |    x   |
> > +      |    | Antenna 1 Touch*   |    x    |        |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 1  | CH0 Proximity      |    x    |    x   |    x   |    x   |    x   |
> > +      |    | Antenna 1 Prox.*   |    x    |        |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 2  | CH1 Touch          |    x    |    x   |    x   |    x   |    x   |
> > +      |    | Ant. 1 Deep Touch* |    x    |        |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 3  | CH1 Proximity      |    x    |    x   |    x   |    x   |    x   |
> > +      -------------------------------------------------------------------------
> > +      | 4  | CH2 Touch          |    x    |        |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 5  | CH2 Proximity      |    x    |        |        |        |        |
> > +      |    | Antenna 2 Prox.*   |    x    |        |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 6  | Metal (+) Touch**  |    x    |    x   |        |        |        |
> > +      |    | Ant. 2 Deep Touch* |    x    |        |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 7  | Metal (+) Prox.**  |    x    |    x   |        |        |        |
> > +      |    | Antenna 2 Touch*   |    x    |        |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 8  | Metal (-) Touch**  |    x    |    x   |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 9  | Metal (-) Prox.**  |    x    |    x   |        |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 10 | SAR Active***      |    x    |        |    x   |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 11 | SAR Quick Rel.***  |    x    |        |    x   |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 12 | SAR Movement***    |    x    |        |    x   |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 13 | SAR Filter Halt*** |    x    |        |    x   |        |        |
> > +      -------------------------------------------------------------------------
> > +      | 14 | Wheel Up           |         |        |        |    x   |        |
> > +      -------------------------------------------------------------------------
> > +      | 15 | Wheel Down         |         |        |        |    x   |        |
> > +      -------------------------------------------------------------------------
> > +      *   Two-channel SAR. Replaces CH0-2 plus metal touch and proximity events
> > +          if enabled via firmware.
> > +      **  "+" and "-" refer to the polarity of a channel's delta (LTA - counts),
> > +          where "LTA" is defined as the channel's long-term average.
> > +      *** One-channel SAR. Replaces CH0-2 touch and proximity events if enabled
> > +          via firmware.
> > +
> > +required:
> > +  - compatible
> > +  - linux,keycodes
> 
> Add: 
> 
> additionalProperties: false
> 

When I add this, the dt_binding_check step complains that the hall switch child nodes
used in the examples are unrecognized, e.g.:

iqs620a@44: keys: 'hall-switch-south' does not match any of the regexes: 'pinctrl-[0-9]+'

When I originally encountered this, I found that the mdio-mux child node in [0] seems
to be a similar example and omits additionalProperties, which is why I originally did
that here. Do you have any advice on how to proceed?

> > +
> > +if:
> > +  properties:
> > +    compatible:
> > +      contains:
> > +        enum:
> > +          - azoteq,iqs620a-keys
> > +          - azoteq,iqs621-keys
> > +          - azoteq,iqs622-keys
> > +then:
> > +  patternProperties:
> > +    "^hall-switch-(north|south)$":
> > +      type: object
> > +      description:
> > +        Represents north/south-field Hall-effect sensor touch or proximity
> > +        events. Note that north/south-field orientation is reversed on the
> > +        IQS620AXzCSR device due to its flip-chip package.
> > +
> > +      properties:
> > +        linux,code:
> > +          $ref: /schemas/types.yaml#/definitions/uint32
> > +          description: Numeric switch code associated with the event.
> > +
> > +        azoteq,use-prox:
> > +          $ref: /schemas/types.yaml#/definitions/flag
> > +          description:
> > +            If present, specifies that Hall-effect sensor reporting should
> > +            use the device's wide-range proximity threshold instead of its
> > +            close-range touch threshold (default).
> > +
> > +      required:
> > +        - linux,code
> > +

Do you think I should specify additionalProperties: false within these child nodes?

> > +...
> > diff --git a/Documentation/devicetree/bindings/mfd/iqs62x.yaml b/Documentation/devicetree/bindings/mfd/iqs62x.yaml
> > new file mode 100644
> > index 0000000..24e6004
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/mfd/iqs62x.yaml
> > @@ -0,0 +1,177 @@
> > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/mfd/iqs62x.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
> > +
> > +maintainers:
> > +  - Jeff LaBundy <jeff@labundy.com>
> > +
> > +description: |
> > +  The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors
> > +  integrate multiple sensing technologies in a single package.
> > +
> > +  Link to data sheets: https://www.azoteq.com/
> > +
> > +properties:
> > +  compatible:
> > +    enum:
> > +      - azoteq,iqs620a
> > +      - azoteq,iqs621
> > +      - azoteq,iqs622
> > +      - azoteq,iqs624
> > +      - azoteq,iqs625
> > +
> > +  reg:
> > +    maxItems: 1
> > +
> > +  interrupts:
> > +    maxItems: 1
> > +
> > +  firmware-name:
> > +    $ref: /schemas/types.yaml#/definitions/string
> > +    description:
> > +      Specifies the name of the calibration and configuration file selected by
> > +      the driver. If this property is omitted, the name is chosen based on the
> > +      device name with ".bin" as the extension (e.g. iqs620a.bin for IQS620A).
> > +
> > +  keys:
> > +    $ref: ../input/iqs62x-keys.yaml
> > +
> > +  pwm:
> > +    $ref: ../pwm/iqs620a-pwm.yaml
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - interrupts
> 
> Add: 
> 
> additionalProperties: false
> 

Sure thing, will do.

> > +
> > +examples:
> > +  - |
> > +    /*
> > +     * Dual capacitive buttons with additional "air button," unipolar lid
> > +     * switch and panel-mounted LED.
> > +     */
> > +    #include <dt-bindings/input/input.h>
> > +    #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > +    i2c {
> > +            #address-cells = <1>;
> > +            #size-cells = <0>;
> > +
> > +            iqs620a@44 {
> > +                    compatible = "azoteq,iqs620a";
> > +                    reg = <0x44>;
> > +                    interrupt-parent = <&gpio>;
> > +                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
> > +
> > +                    keys {
> > +                            compatible = "azoteq,iqs620a-keys";
> > +
> > +                            linux,keycodes = <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>;
> > +            };
> > +    };
> > +
> > +  - |
> > +    /* Single inductive button with bipolar dock/tablet-mode switch. */
> > +    #include <dt-bindings/input/input.h>
> > +    #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > +    i2c {
> > +            #address-cells = <1>;
> > +            #size-cells = <0>;
> > +
> > +            iqs620a@44 {
> > +                    compatible = "azoteq,iqs620a";
> > +                    reg = <0x44>;
> > +                    interrupt-parent = <&gpio>;
> > +                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
> > +
> > +                    firmware-name = "iqs620a_coil.bin";
> > +
> > +                    keys {
> > +                            compatible = "azoteq,iqs620a-keys";
> > +
> > +                            linux,keycodes = <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <KEY_MUTE>;
> > +
> > +                            hall-switch-north {
> > +                                    linux,code = <SW_DOCK>;
> > +                            };
> > +
> > +                            hall-switch-south {
> > +                                    linux,code = <SW_TABLET_MODE>;
> > +                            };
> > +                    };
> > +            };
> > +    };
> > +
> > +  - |
> > +    /* Dual capacitive buttons with volume knob. */
> > +    #include <dt-bindings/input/input.h>
> > +    #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > +    i2c {
> > +            #address-cells = <1>;
> > +            #size-cells = <0>;
> > +
> > +            iqs624@44 {
> > +                    compatible = "azoteq,iqs624";
> > +                    reg = <0x44>;
> > +                    interrupt-parent = <&gpio>;
> > +                    interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
> > +
> > +                    keys {
> > +                            compatible = "azoteq,iqs624-keys";
> > +
> > +                            linux,keycodes = <BTN_0>,
> > +                                             <0>,
> > +                                             <BTN_1>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <0>,
> > +                                             <KEY_VOLUMEUP>,
> > +                                             <KEY_VOLUMEDOWN>;
> > +                    };
> > +            };
> > +    };
> > +
> > +...
> > diff --git a/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
> > new file mode 100644
> > index 0000000..6b7aaef
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
> > @@ -0,0 +1,30 @@
> > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/pwm/iqs620a-pwm.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Azoteq IQS620A PWM Generator
> > +
> > +maintainers:
> > +  - Jeff LaBundy <jeff@labundy.com>
> > +
> > +description: |
> > +  The Azoteq IQS620A multi-function sensor generates a fixed-frequency PWM
> > +  output represented by a "pwm" child node from the parent MFD driver. See
> > +  Documentation/devicetree/bindings/mfd/iqs62x.yaml for further details as
> > +  well as an example.
> > +
> > +properties:
> > +  compatible:
> > +    enum:
> > +      - azoteq,iqs620a-pwm
> > +
> > +  "#pwm-cells":
> > +    const: 2
> > +
> > +required:
> > +  - compatible
> > +  - "#pwm-cells"
> 
> Add: 
> 
> additionalProperties: false
> 

Sure thing, will do.

> > +
> > +...
> > --
> > 2.7.4
> > 

[0] Documentation/devicetree/bindings/net/allwinner,sun8i-a83t-emac.yaml

Kind regards,
Jeff LaBundy

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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2019-12-20  3:19             ` Jeff LaBundy
@ 2019-12-20  8:59               ` Uwe Kleine-König
  2019-12-21  3:28                 ` Jeff LaBundy
  0 siblings, 1 reply; 30+ messages in thread
From: Uwe Kleine-König @ 2019-12-20  8:59 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: mark.rutland, devicetree, lars, kernel, linux-pwm, linux-iio,
	dmitry.torokhov, robh+dt, thierry.reding, pmeerw, linux-input,
	lee.jones, jic23, knaack.h

Hello Jeff,

On Fri, Dec 20, 2019 at 03:19:31AM +0000, Jeff LaBundy wrote:
> I apologize for my delayed replies as I have been traveling.

No problem, I didn't hold my breath :-)

> On Mon, Dec 16, 2019 at 10:19:12AM +0100, Uwe Kleine-König wrote:
> > On Sun, Dec 15, 2019 at 08:36:12PM +0000, Jeff LaBundy wrote:
> > > On Tue, Dec 10, 2019 at 08:22:27AM +0100, Uwe Kleine-König wrote:
> > > > On Tue, Dec 10, 2019 at 12:03:02AM +0000, Jeff LaBundy wrote:
> > > > > On Mon, Dec 09, 2019 at 08:32:06AM +0100, Uwe Kleine-König wrote:
> > > > > > On Mon, Dec 09, 2019 at 12:38:36AM +0000, 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>
> > > > > > > ---
> > > > > > > Changes in v2:
> > > > > > >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> > > > > > >   - Added 'Limitations' section to introductory comments
> > > > > > >   - Replaced 'error' with 'ret' throughout
> > > > > > >   - Added const qualifier to state argument of iqs620_pwm_apply and removed all
> > > > > > >     modifications to the variable's contents
> > > > > > >   - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
> > > > > > >     polarity is inverted or the requested period is below 1 ms, respectively
> > > > > > >   - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
> > > > > > >   - Added iqs620_pwm_get_state
> > > > > > >   - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
> > > > > > >   - Moved notifier unregistration to already present iqs620_pwm_remove, which
> > > > > > >     eliminated the need for a device-managed action and ready flag
> > > > > > >   - Added a comment in iqs620_pwm_probe to explain the order of operations
> > > > > > >   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> > > > > > > 
> > > > > > >  drivers/pwm/Kconfig       |  10 +++
> > > > > > >  drivers/pwm/Makefile      |   1 +
> > > > > > >  drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
> > > > > > >  3 files changed, 217 insertions(+)
> > > > > > >  create mode 100644 drivers/pwm/pwm-iqs620a.c
> > > > > > > 
> > > > > > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > > > > > > index bd21655..60bcf6c 100644
> > > > > > > --- a/drivers/pwm/Kconfig
> > > > > > > +++ b/drivers/pwm/Kconfig
> > > > > > > @@ -222,6 +222,16 @@ config PWM_IMX_TPM
> > > > > > >  	  To compile this driver as a module, choose M here: the module
> > > > > > >  	  will be called pwm-imx-tpm.
> > > > > > > 
> > > > > > > +config PWM_IQS620A
> > > > > > > +	tristate "Azoteq IQS620A PWM support"
> > > > > > > +	depends on MFD_IQS62X || COMPILE_TEST
> > > > > > > +	help
> > > > > > > +	  Generic PWM framework driver for the Azoteq IQS620A multi-function
> > > > > > > +	  sensor.
> > > > > > > +
> > > > > > > +	  To compile this driver as a module, choose M here: the module will
> > > > > > > +	  be called pwm-iqs620a.
> > > > > > > +
> > > > > > >  config PWM_JZ4740
> > > > > > >  	tristate "Ingenic JZ47xx PWM support"
> > > > > > >  	depends on MACH_INGENIC
> > > > > > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> > > > > > > index 9a47507..a59c710 100644
> > > > > > > --- a/drivers/pwm/Makefile
> > > > > > > +++ b/drivers/pwm/Makefile
> > > > > > > @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> > > > > > >  obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
> > > > > > >  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
> > > > > > >  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> > > > > > > +obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
> > > > > > >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> > > > > > >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> > > > > > >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > > > > > > diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
> > > > > > > new file mode 100644
> > > > > > > index 0000000..1ea11b9
> > > > > > > --- /dev/null
> > > > > > > +++ b/drivers/pwm/pwm-iqs620a.c
> > > > > > > @@ -0,0 +1,206 @@
> > > > > > > +// SPDX-License-Identifier: GPL-2.0+
> > > > > > > +/*
> > > > > > > + * Azoteq IQS620A PWM Generator
> > > > > > > + *
> > > > > > > + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> > > > > > > + *
> > > > > > > + * Limitations:
> > > > > > > + * - The period is not guaranteed to run to completion when the duty cycle is
> > > > > > > + *   changed or the output is disabled.
> > > > > > 
> > > > > > Do you know more details here? "not guaranteed" means that the new
> > > > > > period starts immediately when duty_cycle or the enabled bit is written?
> > > > > > 
> > > > > 
> > > > > Increasing the duty cycle on-the-fly (e.g. 25% to 75%) results in the
> > > > > following behavior (depending on where the I2C write falls):
> > > > > 
> > > > >                        I2C write
> > > > >    __        __        __  V_    ______    ______    ______    __
> > > > > __|  |______|  |______|  |_|x|__|      |__|      |__|      |__|
> > > > >   ^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^
> > > > > 
> > > > > The PWM continues to tick at 1 ms, but the currently running period suffers
> > > > > an extraneous pulse as the output is abruptly set high to "catch up" to the
> > > > > new duty cycle.
> > > > > 
> > > > > A similar behavior can occur if the duty cycle is decreased, meaning the
> > > > > output is abruptly set low if the I2C transaction completes in what has
> > > > > suddenly become the inactive region of the currently running period.
> > > > > 
> > > > > The PWM seems to be a simple counter that rolls over at a period of 1 ms.
> > > > > Both the counter and the IQS620_PWM_DUTY_CYCLE register effectively go to
> > > > > a comparator whose output is ANDed with IQS620_PWR_SETTINGS_PWM_OUT which
> > > > > then drives the PWM output.
> > > > > 
> > > > > As such, if either IQS620_PWM_DUTY_CYCLE or IQS620_PWR_SETTINGS_PWM_OUT
> > > > > change, so may the PWM output state depending on the counter's value at
> > > > > the time the I2C write is completed within the 1-ms continuous loop.
> > > > > 
> > > > > For v3 I will update the note as follows:
> > > > > 
> > > > > - Changes in duty cycle or enable/disable state are immediately reflected
> > > > >   by the PWM output and are not aligned to the start of any period.
> > > > 
> > > > I'd like to see a bit more information in the driver. Something about
> > > > the 1ms rhythm being unaffected by the duty_cycle and enable setting.
> > > > Maybe:
> > > > 
> > > >  - The periods run continuously with a fixed length of 1 ms which is
> > > >    unaffected by register updates. Writing duty cycle or enable
> > > >    registers gets active immediately which might result in glitches.
> > > > 
> > > > ?
> > > > 
> > > 
> > > I adjusted the wording a bit as per my preference and settled on the
> > > following:
> > > 
> > >   - The period is fixed to 1 ms and is generated continuously despite changes
> > >     to the duty cycle or enable/disable state.
> > >   - Changes to the duty cycle or enable/disable state take effect immediately
> > >     and may result in a glitch during the period in which the change is made.
> > > 
> > > I believe these capture the spirit of your message; please let me know if
> > > you have any concerns.
> > 
> > That's fine.
> > 
> > > Upon further experimentation, I found that disabling the output (which v2
> > > does so as to simulate a 0% duty cycle) does not actively drive zero, but
> > > rather places the output in a high-impedance state with only the device's
> > > own internal leakage eventually discharging the pin.
> > 
> > But maybe this is still the best you can do in this case. @Thierry, what
> > do you think?
> > 
> > > This is fundamentally different than actively driving the pin low to make
> > > a 0% duty cycle, which does not appear to be possible at all. Therefore I
> > > have removed the control of IQS620_PWR_SETTINGS_PWM_OUT based on the duty
> > > cycle requested by the user and reverted to the behavior of v1, where the
> > > duty cycle requested by the user is mapped only to IQS620_PWM_DUTY_CYCLE.
> > > 
> > > As such, I have also added a third bullet point similar to what you first
> > > suggested following v1:
> > > 
> > >   - The device cannot generate a 0% duty cycle.
> > 
> > Then this would be:
> > 
> >   - The device cannot actively drive a 0% duty cycle. The driver is
> >     disabled for small duty_cycles relying on a pull down on the board.
> > 
> > But maybe it would be more prudent to do this only if the board
> > configuration suggests this is save?!
> > 
> 
> Given the policy for the actual duty cycle generated by the hardware not to
> exceed that which is requested by the user, it seems there are ultimately 3
> options for duty_cycle below 1 / 256 ms:
> 
> 1. Return -EINVAL.
> 2. Disable the output as in v2.
> 3. Add an optional boolean in the dts that identifies whether a pull-down is
>    present; default to option (1) but use option (2) if the boolean is there.
> 
> I don't like option (1) because consumers (including leds-pwm) do in fact ask
> for a 0% duty cycle which would make iqs620_pwm_apply return an error. Things
> happen to still work since leds-pwm does not check pwm_config's return value,
> but I don't want to rely on this coincidence.

People implementing PWM drivers seems to mostly care about leds-pwm :-)
With that only nearly hitting the requested state isn't that bad. But if
you control a step motor that positions a laser, you want to be sure
that the request to stop moving it actually worked.

> Option (2) is better, but I know from experience that board designers do not
> consult driver comments and the requirement to add a pull-down may be easily
> missed as it is not discussed in the data sheet (which is where that sort of
> information belongs, in my opinion).

Hmm, well, actually I think the problem happened earlier when the
hardware designer considered providing 0% to be not important.

> Option (3) seems like overkill for such a simple PWM, and ultimately doesn't
> add any value because I don't want to allow option (1) behavior in any case.
> Whether the PWM is disabled because it is truly disabled or to simulate a 0%
> duty cycle as in option (2), the pull-down is ultimately required regardless
> of whether or not the data sheet happens to go into such detail.

Actually I like option 3 best.
 
> Therefore I have opted to carry forward option (2) from v2 to v3. I reworded
> the third limitation a bit as follows:
> 
> - The device cannot generate a 0% duty cycle. For duty cycles below 1 / 256
>   ms, the output is disabled and relies upon an external pull-down resistor
>   to hold the GPIO3/LTX pin low.
> 
> I did reach out to the vendor and asked them to consider recommending a pull-
> down resistor in a future revision of the data sheet, although at the time of
> this writing I have not heard back.

Good.

> > > 	/*
> > > 	 * The duty cycle generated by the device is calculated as follows:
> > > 	 *
> > > 	 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> > > 	 *
> > > 	 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
> > > 	 * (inclusive). Therefore the lowest duty cycle the device can generate
> > > 	 * while the output is enabled is 1 / 256 ms.
> > > 	 */
> > > 	duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS - 1;
> > 
> > Hmm, this is violating the policy to implement a value not bigger than
> > requested with state->duty_cycle == 0. I see this has downsides to not
> > simply cheat here, but only claiming to have implemented 0% can hurt,
> > too. pwm-rcar returns -EINVAL in this case.
> > 
> 
> That's a great point and is addressed by sticking with option (2) described
> above. Here is what I've got for v3:
> 
> static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> 			    const struct pwm_state *state)
> {
> 	struct iqs620_pwm_private *iqs620_pwm;
> 	struct iqs62x_core *iqs62x;
> 	int duty_scale, ret;
> 
> 	if (state->polarity != PWM_POLARITY_NORMAL)
> 		return -ENOTSUPP;
> 
> 	if (state->period < IQS620_PWM_PERIOD_NS)
> 		return -EINVAL;
> 
> 	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> 	iqs62x = iqs620_pwm->iqs62x;
> 
> 	mutex_lock(&iqs620_pwm->lock);
> 
> 	/*
> 	 * The duty cycle generated by the device is calculated as follows:
> 	 *
> 	 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> 	 *
> 	 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
> 	 * (inclusive). Therefore the lowest duty cycle the device can generate
> 	 * while the output is enabled is 1 / 256 ms.
> 	 *
> 	 * For lower duty cycles (e.g. 0), the PWM output is simply disabled to
> 	 * allow an on-board pull-down resistor to hold the GPIO3/LTX pin low.
> 	 */
> 	duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS;
> 
> 	if (!state->enabled || !duty_scale) {
> 		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> 					 IQS620_PWR_SETTINGS_PWM_OUT, 0);
> 		if (ret)
> 			goto err_mutex;
> 	}
> 
> 	if (duty_scale) {
> 		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> 				   min(duty_scale - 1, 0xFF));
> 		if (ret)
> 			goto err_mutex;
> 	}
> 
> 	if (state->enabled && duty_scale)
> 		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> 					 IQS620_PWR_SETTINGS_PWM_OUT, 0xFF);
> 
> err_mutex:
> 	mutex_unlock(&iqs620_pwm->lock);
> 
> 	return ret;
> }

Looks ok. (Even though it implements (2) which isn't my favorite :-)

> And for the get_state callback:
> 
> static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> 				 struct pwm_state *state)
> {
> 	struct iqs620_pwm_private *iqs620_pwm;
> 	struct iqs62x_core *iqs62x;
> 	unsigned int val;
> 	int ret;
> 
> 	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> 	iqs62x = iqs620_pwm->iqs62x;
> 
> 	mutex_lock(&iqs620_pwm->lock);
> 
> 	ret = regmap_read(iqs62x->map, IQS620_PWR_SETTINGS, &val);
> 	if (ret)
> 		goto err_mutex;
> 	state->enabled = val & IQS620_PWR_SETTINGS_PWM_OUT;
> 
> 	ret = regmap_read(iqs62x->map, IQS620_PWM_DUTY_CYCLE, &val);
> 	if (ret)
> 		goto err_mutex;
> 	state->duty_cycle = DIV_ROUND_UP((val + 1) * IQS620_PWM_PERIOD_NS, 256);
> 	state->period = IQS620_PWM_PERIOD_NS;
> 
> err_mutex:
> 	mutex_unlock(&iqs620_pwm->lock);
> 
> 	if (ret)
> 		dev_err(iqs620_pwm->chip.dev, "Failed to get state: %d\n", ret);
> }
> 
> If you and/or Thierry have any concerns, please let me know.

Looks good, too. Maybe add a comment like:

	/*
	 * As the hardware cannot implement "enabled with
	 * duty_cycle == 0", we're reporting "disabled with
	 * duty_cycle = 1/256 ms" after 0% was requested. This is ugly
	 * but the best we can achieve.
	 */

> > > 	ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > > 			   clamp(duty_scale, 0, 0xFF));
> > > 	if (ret)
> > > 		return ret;
> > 
> > I understand your motivation to configure the duty cycle also when the
> > the request has enabled=false, but a strange side effect is that a
> > failure to configure the dutycycle with .enabled=false isn't really a
> > problem, is it?
> > (This is not a request to change anything, it's only expression of my
> > frustration that we cannot get away without strange effects.)
> > 
> 
> True, but it would definitely be a problem in case 01ccf903edd6 returns and
> we're relying on the device's own registers to hold the PWM state.

You can assume it won't come back as is. There are too many drivers that
suffer the same problem. My goal is to let the core not depend on the
lowlevel drivers to memorize a duty-cycle for disabled PWMs. The details
are not yet thought out. Obvious options are:

 - cache the value in the core
 - make consumers not depend on that

> Thinking about this more, I agree with your earlier comment that a means to
> get the actual (quantized) state needs to be a new API function (of integer
> type). Since chip->ops->get_state is void, there is no way for the callback
> to warn the core that a register read failed and the PWM state may be junk.

Yeah, this is something I intend to change, too. .get_state should
return a status code in the long run.

Best regards
Uwe

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

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

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

Hi Uwe,

On Fri, Dec 20, 2019 at 09:59:48AM +0100, Uwe Kleine-König wrote:
> Hello Jeff,
> 
> On Fri, Dec 20, 2019 at 03:19:31AM +0000, Jeff LaBundy wrote:
> > I apologize for my delayed replies as I have been traveling.
> 
> No problem, I didn't hold my breath :-)
> 
> > On Mon, Dec 16, 2019 at 10:19:12AM +0100, Uwe Kleine-König wrote:
> > > On Sun, Dec 15, 2019 at 08:36:12PM +0000, Jeff LaBundy wrote:
> > > > On Tue, Dec 10, 2019 at 08:22:27AM +0100, Uwe Kleine-König wrote:
> > > > > On Tue, Dec 10, 2019 at 12:03:02AM +0000, Jeff LaBundy wrote:
> > > > > > On Mon, Dec 09, 2019 at 08:32:06AM +0100, Uwe Kleine-König wrote:
> > > > > > > On Mon, Dec 09, 2019 at 12:38:36AM +0000, 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>
> > > > > > > > ---
> > > > > > > > Changes in v2:
> > > > > > > >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> > > > > > > >   - Added 'Limitations' section to introductory comments
> > > > > > > >   - Replaced 'error' with 'ret' throughout
> > > > > > > >   - Added const qualifier to state argument of iqs620_pwm_apply and removed all
> > > > > > > >     modifications to the variable's contents
> > > > > > > >   - Updated iqs620_pwm_apply to return -ENOTSUPP or -EINVAL if the requested
> > > > > > > >     polarity is inverted or the requested period is below 1 ms, respectively
> > > > > > > >   - Updated iqs620_pwm_apply to disable the PWM output if duty cycle is zero
> > > > > > > >   - Added iqs620_pwm_get_state
> > > > > > > >   - Eliminated tabbed alignment of pwm_ops and platform_driver struct members
> > > > > > > >   - Moved notifier unregistration to already present iqs620_pwm_remove, which
> > > > > > > >     eliminated the need for a device-managed action and ready flag
> > > > > > > >   - Added a comment in iqs620_pwm_probe to explain the order of operations
> > > > > > > >   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> > > > > > > > 
> > > > > > > >  drivers/pwm/Kconfig       |  10 +++
> > > > > > > >  drivers/pwm/Makefile      |   1 +
> > > > > > > >  drivers/pwm/pwm-iqs620a.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++
> > > > > > > >  3 files changed, 217 insertions(+)
> > > > > > > >  create mode 100644 drivers/pwm/pwm-iqs620a.c
> > > > > > > > 
> > > > > > > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > > > > > > > index bd21655..60bcf6c 100644
> > > > > > > > --- a/drivers/pwm/Kconfig
> > > > > > > > +++ b/drivers/pwm/Kconfig
> > > > > > > > @@ -222,6 +222,16 @@ config PWM_IMX_TPM
> > > > > > > >  	  To compile this driver as a module, choose M here: the module
> > > > > > > >  	  will be called pwm-imx-tpm.
> > > > > > > > 
> > > > > > > > +config PWM_IQS620A
> > > > > > > > +	tristate "Azoteq IQS620A PWM support"
> > > > > > > > +	depends on MFD_IQS62X || COMPILE_TEST
> > > > > > > > +	help
> > > > > > > > +	  Generic PWM framework driver for the Azoteq IQS620A multi-function
> > > > > > > > +	  sensor.
> > > > > > > > +
> > > > > > > > +	  To compile this driver as a module, choose M here: the module will
> > > > > > > > +	  be called pwm-iqs620a.
> > > > > > > > +
> > > > > > > >  config PWM_JZ4740
> > > > > > > >  	tristate "Ingenic JZ47xx PWM support"
> > > > > > > >  	depends on MACH_INGENIC
> > > > > > > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> > > > > > > > index 9a47507..a59c710 100644
> > > > > > > > --- a/drivers/pwm/Makefile
> > > > > > > > +++ b/drivers/pwm/Makefile
> > > > > > > > @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> > > > > > > >  obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
> > > > > > > >  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
> > > > > > > >  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> > > > > > > > +obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
> > > > > > > >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> > > > > > > >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> > > > > > > >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > > > > > > > diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
> > > > > > > > new file mode 100644
> > > > > > > > index 0000000..1ea11b9
> > > > > > > > --- /dev/null
> > > > > > > > +++ b/drivers/pwm/pwm-iqs620a.c
> > > > > > > > @@ -0,0 +1,206 @@
> > > > > > > > +// SPDX-License-Identifier: GPL-2.0+
> > > > > > > > +/*
> > > > > > > > + * Azoteq IQS620A PWM Generator
> > > > > > > > + *
> > > > > > > > + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> > > > > > > > + *
> > > > > > > > + * Limitations:
> > > > > > > > + * - The period is not guaranteed to run to completion when the duty cycle is
> > > > > > > > + *   changed or the output is disabled.
> > > > > > > 
> > > > > > > Do you know more details here? "not guaranteed" means that the new
> > > > > > > period starts immediately when duty_cycle or the enabled bit is written?
> > > > > > > 
> > > > > > 
> > > > > > Increasing the duty cycle on-the-fly (e.g. 25% to 75%) results in the
> > > > > > following behavior (depending on where the I2C write falls):
> > > > > > 
> > > > > >                        I2C write
> > > > > >    __        __        __  V_    ______    ______    ______    __
> > > > > > __|  |______|  |______|  |_|x|__|      |__|      |__|      |__|
> > > > > >   ^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^---1ms---^
> > > > > > 
> > > > > > The PWM continues to tick at 1 ms, but the currently running period suffers
> > > > > > an extraneous pulse as the output is abruptly set high to "catch up" to the
> > > > > > new duty cycle.
> > > > > > 
> > > > > > A similar behavior can occur if the duty cycle is decreased, meaning the
> > > > > > output is abruptly set low if the I2C transaction completes in what has
> > > > > > suddenly become the inactive region of the currently running period.
> > > > > > 
> > > > > > The PWM seems to be a simple counter that rolls over at a period of 1 ms.
> > > > > > Both the counter and the IQS620_PWM_DUTY_CYCLE register effectively go to
> > > > > > a comparator whose output is ANDed with IQS620_PWR_SETTINGS_PWM_OUT which
> > > > > > then drives the PWM output.
> > > > > > 
> > > > > > As such, if either IQS620_PWM_DUTY_CYCLE or IQS620_PWR_SETTINGS_PWM_OUT
> > > > > > change, so may the PWM output state depending on the counter's value at
> > > > > > the time the I2C write is completed within the 1-ms continuous loop.
> > > > > > 
> > > > > > For v3 I will update the note as follows:
> > > > > > 
> > > > > > - Changes in duty cycle or enable/disable state are immediately reflected
> > > > > >   by the PWM output and are not aligned to the start of any period.
> > > > > 
> > > > > I'd like to see a bit more information in the driver. Something about
> > > > > the 1ms rhythm being unaffected by the duty_cycle and enable setting.
> > > > > Maybe:
> > > > > 
> > > > >  - The periods run continuously with a fixed length of 1 ms which is
> > > > >    unaffected by register updates. Writing duty cycle or enable
> > > > >    registers gets active immediately which might result in glitches.
> > > > > 
> > > > > ?
> > > > > 
> > > > 
> > > > I adjusted the wording a bit as per my preference and settled on the
> > > > following:
> > > > 
> > > >   - The period is fixed to 1 ms and is generated continuously despite changes
> > > >     to the duty cycle or enable/disable state.
> > > >   - Changes to the duty cycle or enable/disable state take effect immediately
> > > >     and may result in a glitch during the period in which the change is made.
> > > > 
> > > > I believe these capture the spirit of your message; please let me know if
> > > > you have any concerns.
> > > 
> > > That's fine.
> > > 
> > > > Upon further experimentation, I found that disabling the output (which v2
> > > > does so as to simulate a 0% duty cycle) does not actively drive zero, but
> > > > rather places the output in a high-impedance state with only the device's
> > > > own internal leakage eventually discharging the pin.
> > > 
> > > But maybe this is still the best you can do in this case. @Thierry, what
> > > do you think?
> > > 
> > > > This is fundamentally different than actively driving the pin low to make
> > > > a 0% duty cycle, which does not appear to be possible at all. Therefore I
> > > > have removed the control of IQS620_PWR_SETTINGS_PWM_OUT based on the duty
> > > > cycle requested by the user and reverted to the behavior of v1, where the
> > > > duty cycle requested by the user is mapped only to IQS620_PWM_DUTY_CYCLE.
> > > > 
> > > > As such, I have also added a third bullet point similar to what you first
> > > > suggested following v1:
> > > > 
> > > >   - The device cannot generate a 0% duty cycle.
> > > 
> > > Then this would be:
> > > 
> > >   - The device cannot actively drive a 0% duty cycle. The driver is
> > >     disabled for small duty_cycles relying on a pull down on the board.
> > > 
> > > But maybe it would be more prudent to do this only if the board
> > > configuration suggests this is save?!
> > > 
> > 
> > Given the policy for the actual duty cycle generated by the hardware not to
> > exceed that which is requested by the user, it seems there are ultimately 3
> > options for duty_cycle below 1 / 256 ms:
> > 
> > 1. Return -EINVAL.
> > 2. Disable the output as in v2.
> > 3. Add an optional boolean in the dts that identifies whether a pull-down is
> >    present; default to option (1) but use option (2) if the boolean is there.
> > 
> > I don't like option (1) because consumers (including leds-pwm) do in fact ask
> > for a 0% duty cycle which would make iqs620_pwm_apply return an error. Things
> > happen to still work since leds-pwm does not check pwm_config's return value,
> > but I don't want to rely on this coincidence.
> 
> People implementing PWM drivers seems to mostly care about leds-pwm :-)
> With that only nearly hitting the requested state isn't that bad. But if
> you control a step motor that positions a laser, you want to be sure
> that the request to stop moving it actually worked.
> 
> > Option (2) is better, but I know from experience that board designers do not
> > consult driver comments and the requirement to add a pull-down may be easily
> > missed as it is not discussed in the data sheet (which is where that sort of
> > information belongs, in my opinion).
> 
> Hmm, well, actually I think the problem happened earlier when the
> hardware designer considered providing 0% to be not important.
> 

I heard back from the vendor today; they've acknowledged the limitation and
are considering adding support for 0% in a future ROM spin. In the meantime,
they've agreed to describe the high-impedance behavior in the data sheet as
well as include the pull-down resistor in an example schematic.

> > Option (3) seems like overkill for such a simple PWM, and ultimately doesn't
> > add any value because I don't want to allow option (1) behavior in any case.
> > Whether the PWM is disabled because it is truly disabled or to simulate a 0%
> > duty cycle as in option (2), the pull-down is ultimately required regardless
> > of whether or not the data sheet happens to go into such detail.
> 
> Actually I like option 3 best.
>  

Based on your other feedback, I'm moving forward under the impression that
you'll still accept option (2); please let me know if I have misunderstood
(thank you for being flexible).

My argument is that even if duty cycle is limited to 1 / 256 ms within the
"no pull-down present" option, the output will still be disabled anyway if
state->enabled = false. In that case, the pull-down is required to prevent
noise from coupling onto the high-impedance pin (which will likely be tied
to the high-impedance gate of a MOSFET) and flickering an LED.

Stated another way, I do not feel option (3) is suitable because the pull-
down is in fact not optional, but required in my opinion.

> > Therefore I have opted to carry forward option (2) from v2 to v3. I reworded
> > the third limitation a bit as follows:
> > 
> > - The device cannot generate a 0% duty cycle. For duty cycles below 1 / 256
> >   ms, the output is disabled and relies upon an external pull-down resistor
> >   to hold the GPIO3/LTX pin low.
> > 
> > I did reach out to the vendor and asked them to consider recommending a pull-
> > down resistor in a future revision of the data sheet, although at the time of
> > this writing I have not heard back.
> 
> Good.
> 
> > > > 	/*
> > > > 	 * The duty cycle generated by the device is calculated as follows:
> > > > 	 *
> > > > 	 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> > > > 	 *
> > > > 	 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
> > > > 	 * (inclusive). Therefore the lowest duty cycle the device can generate
> > > > 	 * while the output is enabled is 1 / 256 ms.
> > > > 	 */
> > > > 	duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS - 1;
> > > 
> > > Hmm, this is violating the policy to implement a value not bigger than
> > > requested with state->duty_cycle == 0. I see this has downsides to not
> > > simply cheat here, but only claiming to have implemented 0% can hurt,
> > > too. pwm-rcar returns -EINVAL in this case.
> > > 
> > 
> > That's a great point and is addressed by sticking with option (2) described
> > above. Here is what I've got for v3:
> > 
> > static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > 			    const struct pwm_state *state)
> > {
> > 	struct iqs620_pwm_private *iqs620_pwm;
> > 	struct iqs62x_core *iqs62x;
> > 	int duty_scale, ret;
> > 
> > 	if (state->polarity != PWM_POLARITY_NORMAL)
> > 		return -ENOTSUPP;
> > 
> > 	if (state->period < IQS620_PWM_PERIOD_NS)
> > 		return -EINVAL;
> > 
> > 	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > 	iqs62x = iqs620_pwm->iqs62x;
> > 
> > 	mutex_lock(&iqs620_pwm->lock);
> > 
> > 	/*
> > 	 * The duty cycle generated by the device is calculated as follows:
> > 	 *
> > 	 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
> > 	 *
> > 	 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
> > 	 * (inclusive). Therefore the lowest duty cycle the device can generate
> > 	 * while the output is enabled is 1 / 256 ms.
> > 	 *
> > 	 * For lower duty cycles (e.g. 0), the PWM output is simply disabled to
> > 	 * allow an on-board pull-down resistor to hold the GPIO3/LTX pin low.
> > 	 */
> > 	duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS;
> > 
> > 	if (!state->enabled || !duty_scale) {
> > 		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > 					 IQS620_PWR_SETTINGS_PWM_OUT, 0);
> > 		if (ret)
> > 			goto err_mutex;
> > 	}
> > 
> > 	if (duty_scale) {
> > 		ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > 				   min(duty_scale - 1, 0xFF));
> > 		if (ret)
> > 			goto err_mutex;
> > 	}
> > 
> > 	if (state->enabled && duty_scale)
> > 		ret = regmap_update_bits(iqs62x->map, IQS620_PWR_SETTINGS,
> > 					 IQS620_PWR_SETTINGS_PWM_OUT, 0xFF);
> > 
> > err_mutex:
> > 	mutex_unlock(&iqs620_pwm->lock);
> > 
> > 	return ret;
> > }
> 
> Looks ok. (Even though it implements (2) which isn't my favorite :-)
> 
> > And for the get_state callback:
> > 
> > static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> > 				 struct pwm_state *state)
> > {
> > 	struct iqs620_pwm_private *iqs620_pwm;
> > 	struct iqs62x_core *iqs62x;
> > 	unsigned int val;
> > 	int ret;
> > 
> > 	iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
> > 	iqs62x = iqs620_pwm->iqs62x;
> > 
> > 	mutex_lock(&iqs620_pwm->lock);
> > 
> > 	ret = regmap_read(iqs62x->map, IQS620_PWR_SETTINGS, &val);
> > 	if (ret)
> > 		goto err_mutex;
> > 	state->enabled = val & IQS620_PWR_SETTINGS_PWM_OUT;
> > 
> > 	ret = regmap_read(iqs62x->map, IQS620_PWM_DUTY_CYCLE, &val);
> > 	if (ret)
> > 		goto err_mutex;
> > 	state->duty_cycle = DIV_ROUND_UP((val + 1) * IQS620_PWM_PERIOD_NS, 256);
> > 	state->period = IQS620_PWM_PERIOD_NS;
> > 
> > err_mutex:
> > 	mutex_unlock(&iqs620_pwm->lock);
> > 
> > 	if (ret)
> > 		dev_err(iqs620_pwm->chip.dev, "Failed to get state: %d\n", ret);
> > }
> > 
> > If you and/or Thierry have any concerns, please let me know.
> 
> Looks good, too. Maybe add a comment like:
> 
> 	/*
> 	 * As the hardware cannot implement "enabled with
> 	 * duty_cycle == 0", we're reporting "disabled with
> 	 * duty_cycle = 1/256 ms" after 0% was requested. This is ugly
> 	 * but the best we can achieve.
> 	 */
> 

Sure thing, will do.

> > > > 	ret = regmap_write(iqs62x->map, IQS620_PWM_DUTY_CYCLE,
> > > > 			   clamp(duty_scale, 0, 0xFF));
> > > > 	if (ret)
> > > > 		return ret;
> > > 
> > > I understand your motivation to configure the duty cycle also when the
> > > the request has enabled=false, but a strange side effect is that a
> > > failure to configure the dutycycle with .enabled=false isn't really a
> > > problem, is it?
> > > (This is not a request to change anything, it's only expression of my
> > > frustration that we cannot get away without strange effects.)
> > > 
> > 
> > True, but it would definitely be a problem in case 01ccf903edd6 returns and
> > we're relying on the device's own registers to hold the PWM state.
> 
> You can assume it won't come back as is. There are too many drivers that
> suffer the same problem. My goal is to let the core not depend on the
> lowlevel drivers to memorize a duty-cycle for disabled PWMs. The details
> are not yet thought out. Obvious options are:
> 
>  - cache the value in the core
>  - make consumers not depend on that
> 
> > Thinking about this more, I agree with your earlier comment that a means to
> > get the actual (quantized) state needs to be a new API function (of integer
> > type). Since chip->ops->get_state is void, there is no way for the callback
> > to warn the core that a register read failed and the PWM state may be junk.
> 
> Yeah, this is something I intend to change, too. .get_state should
> return a status code in the long run.
> 

Makes sense.

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

I'll add a comment in get_state and send out a v3 after the holidays.

Kind regards,
Jeff LaBundy

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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2019-12-21  3:28                 ` Jeff LaBundy
@ 2019-12-22 21:48                   ` Uwe Kleine-König
  2020-01-01 22:39                     ` Jeff LaBundy
  0 siblings, 1 reply; 30+ messages in thread
From: Uwe Kleine-König @ 2019-12-22 21:48 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: mark.rutland, devicetree, lars, kernel, linux-pwm, linux-iio,
	dmitry.torokhov, robh+dt, thierry.reding, pmeerw, linux-input,
	lee.jones, jic23, knaack.h

Hello Jeff,

On Sat, Dec 21, 2019 at 03:28:01AM +0000, Jeff LaBundy wrote:
> I heard back from the vendor today; they've acknowledged the limitation and
> are considering adding support for 0% in a future ROM spin. In the meantime,
> they've agreed to describe the high-impedance behavior in the data sheet as
> well as include the pull-down resistor in an example schematic.

Oh wow, seems like a good vendor then. :-)

> > > Option (3) seems like overkill for such a simple PWM, and ultimately doesn't
> > > add any value because I don't want to allow option (1) behavior in any case.
> > > Whether the PWM is disabled because it is truly disabled or to simulate a 0%
> > > duty cycle as in option (2), the pull-down is ultimately required regardless
> > > of whether or not the data sheet happens to go into such detail.
> > 
> > Actually I like option 3 best.
> >  
> 
> Based on your other feedback, I'm moving forward under the impression that
> you'll still accept option (2); please let me know if I have misunderstood
> (thank you for being flexible).

Yeah, that's fine. If in the end it shows that this is a bad idea we can
still change to (3).

Best regards
Uwe

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

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

* Re: [PATCH v2 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625
  2019-12-20  4:00     ` Jeff LaBundy
@ 2019-12-24 21:55       ` Rob Herring
  2020-01-01 21:32         ` Jeff LaBundy
  0 siblings, 1 reply; 30+ messages in thread
From: Rob Herring @ 2019-12-24 21:55 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, mark.rutland

On Thu, Dec 19, 2019 at 9:00 PM Jeff LaBundy <jeff@labundy.com> wrote:
>
> Hi Rob,
>
> Thank you for your prompt review and your kind words. A couple of questions
> and comments for you below.
>
> On Wed, Dec 18, 2019 at 05:52:52PM -0600, Rob Herring wrote:
> > On Mon, Dec 09, 2019 at 12:38:32AM +0000, Jeff LaBundy wrote:
> > > This patch adds device tree bindings for the Azoteq IQS620A, IQS621,
> > > IQS622, IQS624 and IQS625 multi-function sensors.
> > >
> > > A total of three bindings are presented (one MFD and two child nodes);
> > > they are submitted as a single patch because the child node bindings
> > > have no meaning in the absence of the MFD binding.
> > >
> > > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > > ---
> > > Changes in v2:
> > >   - Removed "prox" child node and moved "keys" and "pwm" child nodes to their
> > >     own bindings
> > >   - Replaced linux,fw-file property with more common firmware-name property
> > >   - Converted all bindings to YAML
> >
> > Good job for first go.
> >
> > >
> > >  .../devicetree/bindings/input/iqs62x-keys.yaml     | 126 +++++++++++++++
> > >  Documentation/devicetree/bindings/mfd/iqs62x.yaml  | 177 +++++++++++++++++++++
> > >  .../devicetree/bindings/pwm/iqs620a-pwm.yaml       |  30 ++++
> > >  3 files changed, 333 insertions(+)
> > >  create mode 100644 Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> > >  create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.yaml
> > >  create mode 100644 Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
> >
> > A couple of minor things below. With those fixed:
> >
> > Reviewed-by: Rob Herring <robh@kernel.org>
> >
> > >
> > > diff --git a/Documentation/devicetree/bindings/input/iqs62x-keys.yaml b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> > > new file mode 100644
> > > index 0000000..e9b54e0
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> > > @@ -0,0 +1,126 @@
> > > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > > +%YAML 1.2
> > > +---
> > > +$id: http://devicetree.org/schemas/input/iqs62x-keys.yaml#
> > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > +
> > > +title: Azoteq IQS620A/621/622/624/625 Keys and Switches
> > > +
> > > +maintainers:
> > > +  - Jeff LaBundy <jeff@labundy.com>
> > > +
> > > +description: |
> > > +  The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors
> > > +  feature a variety of self-capacitive, mutual-inductive and Hall-effect sens-
> > > +  ing capabilities that can facilitate a variety of contactless key and switch
> > > +  applications.
> > > +
> > > +  These functions are collectively represented by a "keys" child node from the
> > > +  parent MFD driver. See Documentation/devicetree/bindings/mfd/iqs62x.yaml for
> > > +  further details and examples. Sensor hardware configuration (self-capacitive
> > > +  vs. mutual-inductive, etc.) is selected based on the device's firmware.
> > > +
> > > +properties:
> > > +  compatible:
> > > +    enum:
> > > +      - azoteq,iqs620a-keys
> > > +      - azoteq,iqs621-keys
> > > +      - azoteq,iqs622-keys
> > > +      - azoteq,iqs624-keys
> > > +      - azoteq,iqs625-keys
> > > +
> > > +  linux,keycodes:
> > > +    allOf:
> > > +      - $ref: /schemas/types.yaml#/definitions/uint32-array
> > > +      - minItems: 1
> > > +        maxItems: 16
> > > +    description: |
> > > +      Specifies the numeric keycodes associated with each available touch or
> > > +      proximity event according to the following table. An 'x' indicates the
> > > +      event is supported for a given device. Specify 0 for unused events.
> > > +
> > > +      -------------------------------------------------------------------------
> > > +      | #  | Event              | IQS620A | IQS621 | IQS622 | IQS624 | IQS625 |
> > > +      -------------------------------------------------------------------------
> > > +      | 0  | CH0 Touch          |    x    |    x   |    x   |    x   |    x   |
> > > +      |    | Antenna 1 Touch*   |    x    |        |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 1  | CH0 Proximity      |    x    |    x   |    x   |    x   |    x   |
> > > +      |    | Antenna 1 Prox.*   |    x    |        |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 2  | CH1 Touch          |    x    |    x   |    x   |    x   |    x   |
> > > +      |    | Ant. 1 Deep Touch* |    x    |        |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 3  | CH1 Proximity      |    x    |    x   |    x   |    x   |    x   |
> > > +      -------------------------------------------------------------------------
> > > +      | 4  | CH2 Touch          |    x    |        |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 5  | CH2 Proximity      |    x    |        |        |        |        |
> > > +      |    | Antenna 2 Prox.*   |    x    |        |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 6  | Metal (+) Touch**  |    x    |    x   |        |        |        |
> > > +      |    | Ant. 2 Deep Touch* |    x    |        |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 7  | Metal (+) Prox.**  |    x    |    x   |        |        |        |
> > > +      |    | Antenna 2 Touch*   |    x    |        |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 8  | Metal (-) Touch**  |    x    |    x   |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 9  | Metal (-) Prox.**  |    x    |    x   |        |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 10 | SAR Active***      |    x    |        |    x   |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 11 | SAR Quick Rel.***  |    x    |        |    x   |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 12 | SAR Movement***    |    x    |        |    x   |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 13 | SAR Filter Halt*** |    x    |        |    x   |        |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 14 | Wheel Up           |         |        |        |    x   |        |
> > > +      -------------------------------------------------------------------------
> > > +      | 15 | Wheel Down         |         |        |        |    x   |        |
> > > +      -------------------------------------------------------------------------
> > > +      *   Two-channel SAR. Replaces CH0-2 plus metal touch and proximity events
> > > +          if enabled via firmware.
> > > +      **  "+" and "-" refer to the polarity of a channel's delta (LTA - counts),
> > > +          where "LTA" is defined as the channel's long-term average.
> > > +      *** One-channel SAR. Replaces CH0-2 touch and proximity events if enabled
> > > +          via firmware.
> > > +
> > > +required:
> > > +  - compatible
> > > +  - linux,keycodes
> >
> > Add:
> >
> > additionalProperties: false
> >
>
> When I add this, the dt_binding_check step complains that the hall switch child nodes
> used in the examples are unrecognized, e.g.:
>
> iqs620a@44: keys: 'hall-switch-south' does not match any of the regexes: 'pinctrl-[0-9]+'
>
> When I originally encountered this, I found that the mdio-mux child node in [0] seems
> to be a similar example and omits additionalProperties, which is why I originally did
> that here. Do you have any advice on how to proceed?

That's because the properties are under an if/then. Your options are
split the schema into 2 files to eliminate the if/then or just define
"^hall-switch-(north|south)$" with just 'true' outside the if/then. A
variation on the 2nd option is invert the if/then and make the schema
false. Then the 'main' schema defines the full superset of properties
and the if/then just filters out ones that don't apply in some cases.

>
> > > +
> > > +if:
> > > +  properties:
> > > +    compatible:
> > > +      contains:
> > > +        enum:
> > > +          - azoteq,iqs620a-keys
> > > +          - azoteq,iqs621-keys
> > > +          - azoteq,iqs622-keys
> > > +then:
> > > +  patternProperties:
> > > +    "^hall-switch-(north|south)$":
> > > +      type: object
> > > +      description:
> > > +        Represents north/south-field Hall-effect sensor touch or proximity
> > > +        events. Note that north/south-field orientation is reversed on the
> > > +        IQS620AXzCSR device due to its flip-chip package.
> > > +
> > > +      properties:
> > > +        linux,code:
> > > +          $ref: /schemas/types.yaml#/definitions/uint32
> > > +          description: Numeric switch code associated with the event.
> > > +
> > > +        azoteq,use-prox:
> > > +          $ref: /schemas/types.yaml#/definitions/flag
> > > +          description:
> > > +            If present, specifies that Hall-effect sensor reporting should
> > > +            use the device's wide-range proximity threshold instead of its
> > > +            close-range touch threshold (default).
> > > +
> > > +      required:
> > > +        - linux,code
> > > +
>
> Do you think I should specify additionalProperties: false within these child nodes?

Yes.

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

* Re: [PATCH v2 1/7] dt-bindings: Add bindings for Azoteq IQS620A/621/622/624/625
  2019-12-24 21:55       ` Rob Herring
@ 2020-01-01 21:32         ` Jeff LaBundy
  0 siblings, 0 replies; 30+ messages in thread
From: Jeff LaBundy @ 2020-01-01 21:32 UTC (permalink / raw)
  To: Rob Herring
  Cc: lee.jones, dmitry.torokhov, thierry.reding, jic23, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, mark.rutland

Hi Rob,

On Tue, Dec 24, 2019 at 02:55:41PM -0700, Rob Herring wrote:
> On Thu, Dec 19, 2019 at 9:00 PM Jeff LaBundy <jeff@labundy.com> wrote:
> >
> > Hi Rob,
> >
> > Thank you for your prompt review and your kind words. A couple of questions
> > and comments for you below.
> >
> > On Wed, Dec 18, 2019 at 05:52:52PM -0600, Rob Herring wrote:
> > > On Mon, Dec 09, 2019 at 12:38:32AM +0000, Jeff LaBundy wrote:
> > > > This patch adds device tree bindings for the Azoteq IQS620A, IQS621,
> > > > IQS622, IQS624 and IQS625 multi-function sensors.
> > > >
> > > > A total of three bindings are presented (one MFD and two child nodes);
> > > > they are submitted as a single patch because the child node bindings
> > > > have no meaning in the absence of the MFD binding.
> > > >
> > > > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > > > ---
> > > > Changes in v2:
> > > >   - Removed "prox" child node and moved "keys" and "pwm" child nodes to their
> > > >     own bindings
> > > >   - Replaced linux,fw-file property with more common firmware-name property
> > > >   - Converted all bindings to YAML
> > >
> > > Good job for first go.
> > >
> > > >
> > > >  .../devicetree/bindings/input/iqs62x-keys.yaml     | 126 +++++++++++++++
> > > >  Documentation/devicetree/bindings/mfd/iqs62x.yaml  | 177 +++++++++++++++++++++
> > > >  .../devicetree/bindings/pwm/iqs620a-pwm.yaml       |  30 ++++
> > > >  3 files changed, 333 insertions(+)
> > > >  create mode 100644 Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> > > >  create mode 100644 Documentation/devicetree/bindings/mfd/iqs62x.yaml
> > > >  create mode 100644 Documentation/devicetree/bindings/pwm/iqs620a-pwm.yaml
> > >
> > > A couple of minor things below. With those fixed:
> > >
> > > Reviewed-by: Rob Herring <robh@kernel.org>
> > >
> > > >
> > > > diff --git a/Documentation/devicetree/bindings/input/iqs62x-keys.yaml b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> > > > new file mode 100644
> > > > index 0000000..e9b54e0
> > > > --- /dev/null
> > > > +++ b/Documentation/devicetree/bindings/input/iqs62x-keys.yaml
> > > > @@ -0,0 +1,126 @@
> > > > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > > > +%YAML 1.2
> > > > +---
> > > > +$id: http://devicetree.org/schemas/input/iqs62x-keys.yaml#
> > > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > > +
> > > > +title: Azoteq IQS620A/621/622/624/625 Keys and Switches
> > > > +
> > > > +maintainers:
> > > > +  - Jeff LaBundy <jeff@labundy.com>
> > > > +
> > > > +description: |
> > > > +  The Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors
> > > > +  feature a variety of self-capacitive, mutual-inductive and Hall-effect sens-
> > > > +  ing capabilities that can facilitate a variety of contactless key and switch
> > > > +  applications.
> > > > +
> > > > +  These functions are collectively represented by a "keys" child node from the
> > > > +  parent MFD driver. See Documentation/devicetree/bindings/mfd/iqs62x.yaml for
> > > > +  further details and examples. Sensor hardware configuration (self-capacitive
> > > > +  vs. mutual-inductive, etc.) is selected based on the device's firmware.
> > > > +
> > > > +properties:
> > > > +  compatible:
> > > > +    enum:
> > > > +      - azoteq,iqs620a-keys
> > > > +      - azoteq,iqs621-keys
> > > > +      - azoteq,iqs622-keys
> > > > +      - azoteq,iqs624-keys
> > > > +      - azoteq,iqs625-keys
> > > > +
> > > > +  linux,keycodes:
> > > > +    allOf:
> > > > +      - $ref: /schemas/types.yaml#/definitions/uint32-array
> > > > +      - minItems: 1
> > > > +        maxItems: 16
> > > > +    description: |
> > > > +      Specifies the numeric keycodes associated with each available touch or
> > > > +      proximity event according to the following table. An 'x' indicates the
> > > > +      event is supported for a given device. Specify 0 for unused events.
> > > > +
> > > > +      -------------------------------------------------------------------------
> > > > +      | #  | Event              | IQS620A | IQS621 | IQS622 | IQS624 | IQS625 |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 0  | CH0 Touch          |    x    |    x   |    x   |    x   |    x   |
> > > > +      |    | Antenna 1 Touch*   |    x    |        |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 1  | CH0 Proximity      |    x    |    x   |    x   |    x   |    x   |
> > > > +      |    | Antenna 1 Prox.*   |    x    |        |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 2  | CH1 Touch          |    x    |    x   |    x   |    x   |    x   |
> > > > +      |    | Ant. 1 Deep Touch* |    x    |        |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 3  | CH1 Proximity      |    x    |    x   |    x   |    x   |    x   |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 4  | CH2 Touch          |    x    |        |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 5  | CH2 Proximity      |    x    |        |        |        |        |
> > > > +      |    | Antenna 2 Prox.*   |    x    |        |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 6  | Metal (+) Touch**  |    x    |    x   |        |        |        |
> > > > +      |    | Ant. 2 Deep Touch* |    x    |        |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 7  | Metal (+) Prox.**  |    x    |    x   |        |        |        |
> > > > +      |    | Antenna 2 Touch*   |    x    |        |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 8  | Metal (-) Touch**  |    x    |    x   |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 9  | Metal (-) Prox.**  |    x    |    x   |        |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 10 | SAR Active***      |    x    |        |    x   |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 11 | SAR Quick Rel.***  |    x    |        |    x   |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 12 | SAR Movement***    |    x    |        |    x   |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 13 | SAR Filter Halt*** |    x    |        |    x   |        |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 14 | Wheel Up           |         |        |        |    x   |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      | 15 | Wheel Down         |         |        |        |    x   |        |
> > > > +      -------------------------------------------------------------------------
> > > > +      *   Two-channel SAR. Replaces CH0-2 plus metal touch and proximity events
> > > > +          if enabled via firmware.
> > > > +      **  "+" and "-" refer to the polarity of a channel's delta (LTA - counts),
> > > > +          where "LTA" is defined as the channel's long-term average.
> > > > +      *** One-channel SAR. Replaces CH0-2 touch and proximity events if enabled
> > > > +          via firmware.
> > > > +
> > > > +required:
> > > > +  - compatible
> > > > +  - linux,keycodes
> > >
> > > Add:
> > >
> > > additionalProperties: false
> > >
> >
> > When I add this, the dt_binding_check step complains that the hall switch child nodes
> > used in the examples are unrecognized, e.g.:
> >
> > iqs620a@44: keys: 'hall-switch-south' does not match any of the regexes: 'pinctrl-[0-9]+'
> >
> > When I originally encountered this, I found that the mdio-mux child node in [0] seems
> > to be a similar example and omits additionalProperties, which is why I originally did
> > that here. Do you have any advice on how to proceed?
> 
> That's because the properties are under an if/then. Your options are
> split the schema into 2 files to eliminate the if/then or just define
> "^hall-switch-(north|south)$" with just 'true' outside the if/then. A
> variation on the 2nd option is invert the if/then and make the schema
> false. Then the 'main' schema defines the full superset of properties
> and the if/then just filters out ones that don't apply in some cases.
> 

Thank you for this detailed explanation; it all makes sense now. For v3
I've opted for the variant of option (2) because it allows for a single
binding as I originally intended, and prompts dt_binding_check to throw
an error if a dts author mistakenly defines the hall-switch-north/south
nodes within either of the two devices that don't support that feature.
I also find it simpler and more intuitive.

> >
> > > > +
> > > > +if:
> > > > +  properties:
> > > > +    compatible:
> > > > +      contains:
> > > > +        enum:
> > > > +          - azoteq,iqs620a-keys
> > > > +          - azoteq,iqs621-keys
> > > > +          - azoteq,iqs622-keys
> > > > +then:
> > > > +  patternProperties:
> > > > +    "^hall-switch-(north|south)$":
> > > > +      type: object
> > > > +      description:
> > > > +        Represents north/south-field Hall-effect sensor touch or proximity
> > > > +        events. Note that north/south-field orientation is reversed on the
> > > > +        IQS620AXzCSR device due to its flip-chip package.
> > > > +
> > > > +      properties:
> > > > +        linux,code:
> > > > +          $ref: /schemas/types.yaml#/definitions/uint32
> > > > +          description: Numeric switch code associated with the event.
> > > > +
> > > > +        azoteq,use-prox:
> > > > +          $ref: /schemas/types.yaml#/definitions/flag
> > > > +          description:
> > > > +            If present, specifies that Hall-effect sensor reporting should
> > > > +            use the device's wide-range proximity threshold instead of its
> > > > +            close-range touch threshold (default).
> > > > +
> > > > +      required:
> > > > +        - linux,code
> > > > +
> >
> > Do you think I should specify additionalProperties: false within these child nodes?
> 
> Yes.

Sure thing, will do.

FYI, since these additional changes are quite small and I believe I've
implemented them exactly as you've suggested, I plan on retaining your
Reviewed-by for v3 which I'll be sending out soon. However if you find
that I have misinterpreted anything, please let me know and I will fix
it promptly.

Wishing you a Happy New Year,
Jeff LaBundy

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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2019-12-22 21:48                   ` Uwe Kleine-König
@ 2020-01-01 22:39                     ` Jeff LaBundy
  2020-01-07 11:19                       ` Uwe Kleine-König
  0 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2020-01-01 22:39 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: mark.rutland, devicetree, lars, kernel, linux-pwm, linux-iio,
	dmitry.torokhov, robh+dt, thierry.reding, pmeerw, linux-input,
	lee.jones, jic23, knaack.h

Hi Uwe,

On Sun, Dec 22, 2019 at 10:48:51PM +0100, Uwe Kleine-König wrote:
> Hello Jeff,
> 
> On Sat, Dec 21, 2019 at 03:28:01AM +0000, Jeff LaBundy wrote:
> > I heard back from the vendor today; they've acknowledged the limitation and
> > are considering adding support for 0% in a future ROM spin. In the meantime,
> > they've agreed to describe the high-impedance behavior in the data sheet as
> > well as include the pull-down resistor in an example schematic.
> 
> Oh wow, seems like a good vendor then. :-)
> 
> > > > Option (3) seems like overkill for such a simple PWM, and ultimately doesn't
> > > > add any value because I don't want to allow option (1) behavior in any case.
> > > > Whether the PWM is disabled because it is truly disabled or to simulate a 0%
> > > > duty cycle as in option (2), the pull-down is ultimately required regardless
> > > > of whether or not the data sheet happens to go into such detail.
> > > 
> > > Actually I like option 3 best.
> > >  
> > 
> > Based on your other feedback, I'm moving forward under the impression that
> > you'll still accept option (2); please let me know if I have misunderstood
> > (thank you for being flexible).
> 
> Yeah, that's fine. If in the end it shows that this is a bad idea we can
> still change to (3).
> 

Sounds great. As soon as 5.5-rc5 lands this weekend, I'll rebase v3 and
send it out.

I failed to catch this in my previous reply, but the comment I've added
to iqs620_pwm_get_state actually reads as follows:

/*
 * Since the device cannot generate a 0% duty cycle, requests to do so
 * force subsequent calls to iqs620_pwm_get_state to report the output
 * as disabled with duty cycle equal to that which was in use prior to
 * the request. This is not ideal, but is the best compromise based on
 * the capabilities of the device.
 */

This matches the present implementation, not your proposed comment that
claims duty cycle is clamped to 1 / 256 ms following a request for a 0%
duty cycle.

This seems OK since the concept of a duty cycle or period aren't really
relevant if the output is disabled in my opinion. However if you prefer
I update iqs620_pwm_apply to clamp duty cycle to 1 / 256 ms (instead of
leaving it untouched) in this case, please let me know.

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

Wishing you a Happy New Year,
Jeff LaBundy

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

* Re: [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors
  2019-12-15 16:53   ` Jonathan Cameron
@ 2020-01-01 22:51     ` Jeff LaBundy
  2020-01-02  7:57       ` Lee Jones
  0 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2020-01-01 22:51 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: lee.jones, dmitry.torokhov, thierry.reding, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland

Hi Jonathan,

Thank you for your continued support on this project.

On Sun, Dec 15, 2019 at 04:53:28PM +0000, Jonathan Cameron wrote:
> On Mon, 9 Dec 2019 00:38:41 +0000
> 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.
> > 
> > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> 
> Looks good
> 
> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> 
> My current assumption is that Lee will take this lot via an immutable branch
> in MFD once it's ready.  Shout if a different path makes sense.

Same here. @Lee, please let us know if you disagree.

> > ---
> > Changes in v2:
> >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> >   - Replaced 'error' with 'ret' throughout
> >   - Added iqs624_pos_angle_en and iqs624_pos_angle_get to remove duplicate
> >     logic previously used throughout
> >   - Refactored the logic in iqs624_pos_notifier and added a lock to safely
> >     evaluate variables that may change in response to user action
> >   - Refactored the logic in iqs624_pos_read_raw
> >   - Added a lock to iqs624_pos_read_event_config to account for cases in which
> >     the corresponding hardware state is in the process of being updated
> >   - Refactored the logic in iqs624_pos_write_event_config and read the initial
> >     angle in case it changed since having first been read in iqs624_pos_init
> >   - Removed iqs624_pos_init as its logic has since been absorbed elsewhere
> >   - Removed devm_add_action_or_reset failure message
> >   - Eliminated tabbed alignment of platform_driver struct members
> >   - Changed Kconfig "depends on" logic to MFD_IQS62X || COMPILE_TEST
> > 
> >  drivers/iio/Kconfig               |   1 +
> >  drivers/iio/Makefile              |   1 +
> >  drivers/iio/position/Kconfig      |  19 +++
> >  drivers/iio/position/Makefile     |   7 +
> >  drivers/iio/position/iqs624-pos.c | 284 ++++++++++++++++++++++++++++++++++++++
> >  5 files changed, 312 insertions(+)
> >  create mode 100644 drivers/iio/position/Kconfig
> >  create mode 100644 drivers/iio/position/Makefile
> >  create mode 100644 drivers/iio/position/iqs624-pos.c
> > 
> > diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> > index 5bd5185..d5c073a 100644
> > --- a/drivers/iio/Kconfig
> > +++ b/drivers/iio/Kconfig
> > @@ -88,6 +88,7 @@ source "drivers/iio/orientation/Kconfig"
> >  if IIO_TRIGGER
> >     source "drivers/iio/trigger/Kconfig"
> >  endif #IIO_TRIGGER
> > +source "drivers/iio/position/Kconfig"
> >  source "drivers/iio/potentiometer/Kconfig"
> >  source "drivers/iio/potentiostat/Kconfig"
> >  source "drivers/iio/pressure/Kconfig"
> > diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> > index bff682a..1712011 100644
> > --- a/drivers/iio/Makefile
> > +++ b/drivers/iio/Makefile
> > @@ -31,6 +31,7 @@ obj-y += light/
> >  obj-y += magnetometer/
> >  obj-y += multiplexer/
> >  obj-y += orientation/
> > +obj-y += position/
> >  obj-y += potentiometer/
> >  obj-y += potentiostat/
> >  obj-y += pressure/
> > diff --git a/drivers/iio/position/Kconfig b/drivers/iio/position/Kconfig
> > new file mode 100644
> > index 0000000..eda67f0
> > --- /dev/null
> > +++ b/drivers/iio/position/Kconfig
> > @@ -0,0 +1,19 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +#
> > +# Linear and angular position sensors
> > +#
> > +# When adding new entries keep the list in alphabetical order
> > +
> > +menu "Linear and angular position sensors"
> > +
> > +config IQS624_POS
> > +	tristate "Azoteq IQS624/625 angular position sensors"
> > +	depends on MFD_IQS62X || COMPILE_TEST
> > +	help
> > +	  Say Y here if you want to build support for the Azoteq IQS624
> > +	  and IQS625 angular position sensors.
> > +
> > +	  To compile this driver as a module, choose M here: the module
> > +	  will be called iqs624-pos.
> > +
> > +endmenu
> > diff --git a/drivers/iio/position/Makefile b/drivers/iio/position/Makefile
> > new file mode 100644
> > index 0000000..3cbe7a7
> > --- /dev/null
> > +++ b/drivers/iio/position/Makefile
> > @@ -0,0 +1,7 @@
> > +#
> > +# Makefile for IIO linear and angular position sensors
> > +#
> > +
> > +# When adding new entries keep the list in alphabetical order
> > +
> > +obj-$(CONFIG_IQS624_POS)	+= iqs624-pos.o
> > diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c
> > new file mode 100644
> > index 0000000..af629bf5
> > --- /dev/null
> > +++ b/drivers/iio/position/iqs624-pos.c
> > @@ -0,0 +1,284 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Azoteq IQS624/625 Angular Position Sensors
> > + *
> > + * Copyright (C) 2019 Jeff LaBundy <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 angle_en;
> > +	u16 angle;
> > +};
> > +
> > +static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en)
> > +{
> > +	unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT;
> > +
> > +	/*
> > +	 * The IQS625 reports angular position in the form of coarse intervals,
> > +	 * so only interval change events are unmasked. Conversely, the IQS624
> > +	 * reports angular position down to one degree of resolution, so wheel
> > +	 * movement events are unmasked instead.
> > +	 */
> > +	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
> > +		event_mask = IQS624_HALL_UI_INT_EVENT;
> > +
> > +	return regmap_update_bits(iqs62x->map, IQS624_HALL_UI, event_mask,
> > +				  angle_en ? 0 : 0xFF);
> > +}
> > +
> > +static int iqs624_pos_notifier(struct notifier_block *notifier,
> > +			       unsigned long event_flags, void *context)
> > +{
> > +	struct iqs62x_event_data *event_data = context;
> > +	struct iqs624_pos_private *iqs624_pos;
> > +	struct iqs62x_core *iqs62x;
> > +	struct iio_dev *indio_dev;
> > +	u16 angle = event_data->ui_data;
> > +	s64 timestamp;
> > +	int ret;
> > +
> > +	iqs624_pos = container_of(notifier, struct iqs624_pos_private,
> > +				  notifier);
> > +	indio_dev = iio_priv_to_dev(iqs624_pos);
> > +	timestamp = iio_get_time_ns(indio_dev);
> > +
> > +	iqs62x = iqs624_pos->iqs62x;
> > +	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
> > +		angle = event_data->interval;
> > +
> > +	mutex_lock(&iqs624_pos->lock);
> > +
> > +	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
> > +		ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en);
> > +		if (ret) {
> > +			dev_err(indio_dev->dev.parent,
> > +				"Failed to re-initialize device: %d\n", ret);
> > +			ret = NOTIFY_BAD;
> > +		} else {
> > +			ret = NOTIFY_OK;
> > +		}
> > +	} else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) {
> > +		iio_push_event(indio_dev,
> > +			       IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0,
> > +						    IIO_EV_TYPE_CHANGE,
> > +						    IIO_EV_DIR_NONE),
> > +			       timestamp);
> > +
> > +		iqs624_pos->angle = angle;
> > +		ret = NOTIFY_OK;
> > +	} else {
> > +		ret = NOTIFY_DONE;
> > +	}
> > +
> > +	mutex_unlock(&iqs624_pos->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static void iqs624_pos_notifier_unregister(void *context)
> > +{
> > +	struct iqs624_pos_private *iqs624_pos = context;
> > +	struct iio_dev *indio_dev = iio_priv_to_dev(iqs624_pos);
> > +	int ret;
> > +
> > +	ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh,
> > +						 &iqs624_pos->notifier);
> > +	if (ret)
> > +		dev_err(indio_dev->dev.parent,
> > +			"Failed to unregister notifier: %d\n", ret);
> > +}
> > +
> > +static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val)
> > +{
> > +	int ret;
> > +	__le16 val_buf;
> > +
> > +	if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
> > +		return regmap_read(iqs62x->map, iqs62x->dev_desc->interval,
> > +				   val);
> > +
> > +	ret = regmap_raw_read(iqs62x->map, IQS624_POS_DEG_OUT, &val_buf,
> > +			      sizeof(val_buf));
> > +	if (ret)
> > +		return ret;
> > +
> > +	*val = le16_to_cpu(val_buf);
> > +
> > +	return 0;
> > +}
> > +
> > +static int iqs624_pos_read_raw(struct iio_dev *indio_dev,
> > +			       struct iio_chan_spec const *chan,
> > +			       int *val, int *val2, long mask)
> > +{
> > +	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
> > +	struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
> > +	unsigned int scale = 1;
> > +	int ret;
> > +
> > +	switch (mask) {
> > +	case IIO_CHAN_INFO_RAW:
> > +		ret = iqs624_pos_angle_get(iqs62x, val);
> > +		if (ret)
> > +			return ret;
> > +
> > +		return IIO_VAL_INT;
> > +
> > +	case IIO_CHAN_INFO_SCALE:
> > +		if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) {
> > +			ret = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV,
> > +					  &scale);
> > +			if (ret)
> > +				return ret;
> > +		}
> > +
> > +		*val = scale * IQS624_POS_SCALE1;
> > +		*val2 = IQS624_POS_SCALE2;
> > +		return IIO_VAL_FRACTIONAL;
> > +
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> > +
> > +static int iqs624_pos_read_event_config(struct iio_dev *indio_dev,
> > +					const struct iio_chan_spec *chan,
> > +					enum iio_event_type type,
> > +					enum iio_event_direction dir)
> > +{
> > +	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
> > +	int ret;
> > +
> > +	mutex_lock(&iqs624_pos->lock);
> > +	ret = iqs624_pos->angle_en;
> > +	mutex_unlock(&iqs624_pos->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static int iqs624_pos_write_event_config(struct iio_dev *indio_dev,
> > +					 const struct iio_chan_spec *chan,
> > +					 enum iio_event_type type,
> > +					 enum iio_event_direction dir,
> > +					 int state)
> > +{
> > +	struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
> > +	struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
> > +	unsigned int val;
> > +	int ret;
> > +
> > +	mutex_lock(&iqs624_pos->lock);
> > +
> > +	ret = iqs624_pos_angle_get(iqs62x, &val);
> > +	if (ret)
> > +		goto err_mutex;
> > +
> > +	ret = iqs624_pos_angle_en(iqs62x, state);
> > +	if (ret)
> > +		goto err_mutex;
> > +
> > +	iqs624_pos->angle = val;
> > +	iqs624_pos->angle_en = state;
> > +
> > +err_mutex:
> > +	mutex_unlock(&iqs624_pos->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct iio_info iqs624_pos_info = {
> > +	.read_raw = &iqs624_pos_read_raw,
> > +	.read_event_config = iqs624_pos_read_event_config,
> > +	.write_event_config = iqs624_pos_write_event_config,
> > +};
> > +
> > +static const struct iio_event_spec iqs624_pos_events[] = {
> > +	{
> > +		.type = IIO_EV_TYPE_CHANGE,
> > +		.dir = IIO_EV_DIR_NONE,
> > +		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > +	},
> > +};
> > +
> > +static const struct iio_chan_spec iqs624_pos_channels[] = {
> > +	{
> > +		.type = IIO_ANGL,
> > +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> > +				      BIT(IIO_CHAN_INFO_SCALE),
> > +		.event_spec = iqs624_pos_events,
> > +		.num_event_specs = ARRAY_SIZE(iqs624_pos_events),
> > +	},
> > +};
> > +
> > +static int iqs624_pos_probe(struct platform_device *pdev)
> > +{
> > +	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
> > +	struct iqs624_pos_private *iqs624_pos;
> > +	struct iio_dev *indio_dev;
> > +	int ret;
> > +
> > +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos));
> > +	if (!indio_dev)
> > +		return -ENOMEM;
> > +
> > +	iqs624_pos = iio_priv(indio_dev);
> > +	iqs624_pos->iqs62x = iqs62x;
> > +
> > +	indio_dev->modes = INDIO_DIRECT_MODE;
> > +	indio_dev->dev.parent = &pdev->dev;
> > +	indio_dev->channels = iqs624_pos_channels;
> > +	indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels);
> > +	indio_dev->name = iqs62x->dev_desc->dev_name;
> > +	indio_dev->info = &iqs624_pos_info;
> > +
> > +	mutex_init(&iqs624_pos->lock);
> > +
> > +	iqs624_pos->notifier.notifier_call = iqs624_pos_notifier;
> > +	ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh,
> > +					       &iqs624_pos->notifier);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = devm_add_action_or_reset(&pdev->dev,
> > +				       iqs624_pos_notifier_unregister,
> > +				       iqs624_pos);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return devm_iio_device_register(&pdev->dev, indio_dev);
> > +}
> > +
> > +static struct platform_driver iqs624_pos_platform_driver = {
> > +	.driver = {
> > +		.name = IQS624_DRV_NAME_POS,
> > +	},
> > +	.probe = iqs624_pos_probe,
> > +};
> > +module_platform_driver(iqs624_pos_platform_driver);
> > +
> > +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
> > +MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" IQS624_DRV_NAME_POS);
> > --
> > 2.7.4
> > 
> 

Wishing you a Happy New Year,
Jeff LaBundy

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

* Re: [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors
  2020-01-01 22:51     ` Jeff LaBundy
@ 2020-01-02  7:57       ` Lee Jones
  0 siblings, 0 replies; 30+ messages in thread
From: Lee Jones @ 2020-01-02  7:57 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: Jonathan Cameron, dmitry.torokhov, thierry.reding, devicetree,
	linux-input, u.kleine-koenig, linux-pwm, knaack.h, lars, pmeerw,
	linux-iio, robh+dt, mark.rutland

On Wed, 01 Jan 2020, Jeff LaBundy wrote:

> Hi Jonathan,
> 
> Thank you for your continued support on this project.
> 
> On Sun, Dec 15, 2019 at 04:53:28PM +0000, Jonathan Cameron wrote:
> > On Mon, 9 Dec 2019 00:38:41 +0000
> > 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.
> > > 
> > > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > 
> > Looks good
> > 
> > Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> > 
> > My current assumption is that Lee will take this lot via an immutable branch
> > in MFD once it's ready.  Shout if a different path makes sense.
> 
> Same here. @Lee, please let us know if you disagree.

That's fine.

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

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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2020-01-01 22:39                     ` Jeff LaBundy
@ 2020-01-07 11:19                       ` Uwe Kleine-König
  2020-01-10  4:29                         ` Jeff LaBundy
  0 siblings, 1 reply; 30+ messages in thread
From: Uwe Kleine-König @ 2020-01-07 11:19 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: mark.rutland, devicetree, lars, pmeerw, linux-pwm, linux-iio,
	dmitry.torokhov, robh+dt, thierry.reding, kernel, linux-input,
	lee.jones, jic23, knaack.h

Hi Jeff,

On Wed, Jan 01, 2020 at 10:39:36PM +0000, Jeff LaBundy wrote:
> On Sun, Dec 22, 2019 at 10:48:51PM +0100, Uwe Kleine-König wrote:
> > On Sat, Dec 21, 2019 at 03:28:01AM +0000, Jeff LaBundy wrote:
> > > Based on your other feedback, I'm moving forward under the impression that
> > > you'll still accept option (2); please let me know if I have misunderstood
> > > (thank you for being flexible).
> > 
> > Yeah, that's fine. If in the end it shows that this is a bad idea we can
> > still change to (3).
> 
> Sounds great. As soon as 5.5-rc5 lands this weekend, I'll rebase v3 and
> send it out.
> 
> I failed to catch this in my previous reply, but the comment I've added
> to iqs620_pwm_get_state actually reads as follows:
> 
> /*
>  * Since the device cannot generate a 0% duty cycle, requests to do so
>  * force subsequent calls to iqs620_pwm_get_state to report the output
>  * as disabled with duty cycle equal to that which was in use prior to
>  * the request. This is not ideal, but is the best compromise based on
>  * the capabilities of the device.
>  */
> 
> This matches the present implementation, not your proposed comment that
> claims duty cycle is clamped to 1 / 256 ms following a request for a 0%
> duty cycle.

Yeah, if that's the mechanism that is actually implemented, that's fine
of course.

> This seems OK since the concept of a duty cycle or period aren't really
> relevant if the output is disabled in my opinion. However if you prefer
> I update iqs620_pwm_apply to clamp duty cycle to 1 / 256 ms (instead of
> leaving it untouched) in this case, please let me know.

For a disabled PWM the duty_cycle and period are not relevant, for an
enabled PWM running with 0% the period matters (at least in theory)
however.

Best regards
Uwe

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

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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2020-01-07 11:19                       ` Uwe Kleine-König
@ 2020-01-10  4:29                         ` Jeff LaBundy
  2020-01-10  7:25                           ` Uwe Kleine-König
  0 siblings, 1 reply; 30+ messages in thread
From: Jeff LaBundy @ 2020-01-10  4:29 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: mark.rutland, devicetree, lars, pmeerw, linux-pwm, linux-iio,
	dmitry.torokhov, robh+dt, thierry.reding, kernel, linux-input,
	lee.jones, jic23, knaack.h

Hi Uwe,

On Tue, Jan 07, 2020 at 12:19:40PM +0100, Uwe Kleine-König wrote:
> Hi Jeff,
> 
> On Wed, Jan 01, 2020 at 10:39:36PM +0000, Jeff LaBundy wrote:
> > On Sun, Dec 22, 2019 at 10:48:51PM +0100, Uwe Kleine-König wrote:
> > > On Sat, Dec 21, 2019 at 03:28:01AM +0000, Jeff LaBundy wrote:
> > > > Based on your other feedback, I'm moving forward under the impression that
> > > > you'll still accept option (2); please let me know if I have misunderstood
> > > > (thank you for being flexible).
> > > 
> > > Yeah, that's fine. If in the end it shows that this is a bad idea we can
> > > still change to (3).
> > 
> > Sounds great. As soon as 5.5-rc5 lands this weekend, I'll rebase v3 and
> > send it out.
> > 
> > I failed to catch this in my previous reply, but the comment I've added
> > to iqs620_pwm_get_state actually reads as follows:
> > 
> > /*
> >  * Since the device cannot generate a 0% duty cycle, requests to do so
> >  * force subsequent calls to iqs620_pwm_get_state to report the output
> >  * as disabled with duty cycle equal to that which was in use prior to
> >  * the request. This is not ideal, but is the best compromise based on
> >  * the capabilities of the device.
> >  */
> > 
> > This matches the present implementation, not your proposed comment that
> > claims duty cycle is clamped to 1 / 256 ms following a request for a 0%
> > duty cycle.
> 
> Yeah, if that's the mechanism that is actually implemented, that's fine
> of course.
> 
> > This seems OK since the concept of a duty cycle or period aren't really
> > relevant if the output is disabled in my opinion. However if you prefer
> > I update iqs620_pwm_apply to clamp duty cycle to 1 / 256 ms (instead of
> > leaving it untouched) in this case, please let me know.
> 
> For a disabled PWM the duty_cycle and period are not relevant, for an
> enabled PWM running with 0% the period matters (at least in theory)
> however.
> 

Agreed in full. We should be covered here since we report the (fixed)
period in all cases.

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

I managed to send out v3 this past weekend; please let me know if you
have any further feedback or you find it to be satisfactory.

Kind regards,
Jeff LaBundy

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

* Re: [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator
  2020-01-10  4:29                         ` Jeff LaBundy
@ 2020-01-10  7:25                           ` Uwe Kleine-König
  0 siblings, 0 replies; 30+ messages in thread
From: Uwe Kleine-König @ 2020-01-10  7:25 UTC (permalink / raw)
  To: Jeff LaBundy
  Cc: mark.rutland, devicetree, lars, kernel, linux-pwm, linux-iio,
	dmitry.torokhov, robh+dt, thierry.reding, pmeerw, linux-input,
	lee.jones, jic23, knaack.h

Hello Jeff,

On Fri, Jan 10, 2020 at 04:29:08AM +0000, Jeff LaBundy wrote:
> I managed to send out v3 this past weekend; please let me know if you
> have any further feedback or you find it to be satisfactory.

Yeah, I'm aware. It's on my todo list to take a deeper look.

Best regards
Uwe

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

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

end of thread, other threads:[~2020-01-10  7:25 UTC | newest]

Thread overview: 30+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-09  0:38 [PATCH v2 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
2019-12-09  0:38 ` [PATCH v2 1/7] dt-bindings: Add bindings " Jeff LaBundy
2019-12-18 23:52   ` Rob Herring
2019-12-20  4:00     ` Jeff LaBundy
2019-12-24 21:55       ` Rob Herring
2020-01-01 21:32         ` Jeff LaBundy
2019-12-09  0:38 ` [PATCH v2 2/7] mfd: Add support " Jeff LaBundy
2019-12-09  0:38 ` [PATCH v2 3/7] input: keyboard: " Jeff LaBundy
2019-12-09  0:38 ` [PATCH v2 4/7] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
2019-12-09  7:32   ` Uwe Kleine-König
2019-12-10  0:03     ` Jeff LaBundy
2019-12-10  7:22       ` Uwe Kleine-König
2019-12-15 20:36         ` Jeff LaBundy
2019-12-16  9:19           ` Uwe Kleine-König
2019-12-20  3:19             ` Jeff LaBundy
2019-12-20  8:59               ` Uwe Kleine-König
2019-12-21  3:28                 ` Jeff LaBundy
2019-12-22 21:48                   ` Uwe Kleine-König
2020-01-01 22:39                     ` Jeff LaBundy
2020-01-07 11:19                       ` Uwe Kleine-König
2020-01-10  4:29                         ` Jeff LaBundy
2020-01-10  7:25                           ` Uwe Kleine-König
2019-12-09  0:38 ` [PATCH v2 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
2019-12-15 16:34   ` Jonathan Cameron
2019-12-09  0:38 ` [PATCH v2 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Jeff LaBundy
2019-12-15 16:47   ` Jonathan Cameron
2019-12-09  0:38 ` [PATCH v2 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Jeff LaBundy
2019-12-15 16:53   ` Jonathan Cameron
2020-01-01 22:51     ` Jeff LaBundy
2020-01-02  7:57       ` Lee Jones

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).