linux-iio.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Add driver for Nicera D3-323-AA PIR sensor
@ 2025-05-09 15:03 Waqar Hameed
  2025-05-09 15:03 ` [PATCH 2/3] dt-bindings: iio: proximity: Add " Waqar Hameed
                   ` (2 more replies)
  0 siblings, 3 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-05-09 15:03 UTC (permalink / raw)
  To: Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski
  Cc: kernel, linux-kernel, linux-iio, devicetree

Nicera D3-323-AA is a PIR sensor for human detection. It has support for
raw data measurements and detection notification. The communication
protocol is custom made and therefore needs to be GPIO bit banged.

Previously, there has been an attempt to add a driver for this device
[1]. However, that driver was written for the wrong sub-system. `hwmon`
is clearly not a suitable framework for a proximity device.

In this series, we add a driver for support for event notification for
detections through IIO (the more appropriate sub-system!). The various
settings have been mapped to existing `sysfs` ABIs in the IIO framework.

The public datasheet [2] is quite sparse. A more detailed version can be
obtained through the company.

[1] https://lore.kernel.org/lkml/20241212042412.702044-2-Hermes.Zhang@axis.com/
[2] https://www.endrich.com/Datenbl%C3%A4tter/Sensoren/D3-323-AA_e.pdf

Waqar Hameed (3):
  dt-bindings: vendor-prefixes: Add Nicera
  dt-bindings: iio: proximity: Add Nicera D3-323-AA PIR sensor
  iio: Add driver for Nicera D3-323-AA PIR sensor

 .../iio/proximity/nicera,d3323aa.yaml         |  67 ++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 drivers/iio/proximity/Kconfig                 |   9 +
 drivers/iio/proximity/Makefile                |   1 +
 drivers/iio/proximity/d3323aa.c               | 868 ++++++++++++++++++
 5 files changed, 947 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/proximity/nicera,d3323aa.yaml
 create mode 100644 drivers/iio/proximity/d3323aa.c


base-commit: d76bb1ebb5587f66b0f8b8099bfbb44722bc08b3
-- 
2.39.5


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

* [PATCH 2/3] dt-bindings: iio: proximity: Add Nicera D3-323-AA PIR sensor
  2025-05-09 15:03 [PATCH 0/3] Add driver for Nicera D3-323-AA PIR sensor Waqar Hameed
@ 2025-05-09 15:03 ` Waqar Hameed
  2025-05-09 15:06   ` Krzysztof Kozlowski
  2025-05-09 15:03 ` [PATCH 3/3] iio: Add driver for " Waqar Hameed
  2025-05-09 15:09 ` [PATCH 0/3] " Krzysztof Kozlowski
  2 siblings, 1 reply; 21+ messages in thread
From: Waqar Hameed @ 2025-05-09 15:03 UTC (permalink / raw)
  To: Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: kernel, linux-iio, devicetree, linux-kernel

Nicera D3-323-AA is a PIR sensor for human detection. It has support for
raw data measurements and detection notification. The communication
protocol is custom made and therefore needs to be GPIO bit banged.

Add devicetree bindings requiring the compatible string and the various
GPIOs needed for operation. Some of the GPIOs have multiple use-cases
depending on device state. Describe these thoroughly.

Signed-off-by: Waqar Hameed <waqar.hameed@axis.com>
---
 .../iio/proximity/nicera,d3323aa.yaml         | 67 +++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/proximity/nicera,d3323aa.yaml

diff --git a/Documentation/devicetree/bindings/iio/proximity/nicera,d3323aa.yaml b/Documentation/devicetree/bindings/iio/proximity/nicera,d3323aa.yaml
new file mode 100644
index 000000000000..1ff24dad0086
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/proximity/nicera,d3323aa.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/proximity/nicera,d3323aa.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nicera D3-323-AA PIR sensor
+
+maintainers:
+  - Waqar Hameed <waqar.hameed@axis.com>
+
+description: |
+  PIR sensor for human detection.
+  Datasheet: https://www.endrich.com/Datenbl%C3%A4tter/Sensoren/D3-323-AA_e.pdf
+
+properties:
+  compatible:
+    const: nicera,d3323aa
+
+  vdd-gpio:
+    maxItems: 1
+    description:
+      GPIO for supply voltage (1.8 to 5.5 V).
+      This GPIO will be driven low by the driver in order to cut the supply and
+      reset the device (driving it then back to high to power it on).
+
+  clk-vout-gpio:
+    maxItems: 1
+    description:
+      GPIO for clock and detection.
+      After reset, the device signals with two falling edges on this pin that it
+      is ready for configuration (within 1.2 s), which the driver listens for as
+      interrupts.
+      During configuration, it is used as clock for data reading and writing (on
+      data-gpio). The driver drives this pin with the frequency of 1 kHz (bit
+      banging).
+      After all this, the device is now in operational mode and signals on this
+      pin for any detections. The driver listens for this as interrupts.
+
+  data-gpio:
+    maxItems: 1
+    description:
+      GPIO for data reading and writing.
+      During configuration, configuration data will be written and read back
+      with bit banging (together with clk-vout-gpio as clock).
+      After this, during operational mode, the device will output serial data on
+      this GPIO. However, the driver currently doesn't read this.
+
+required:
+  - compatible
+  - vdd-gpio
+  - clk-vout-gpio
+  - data-gpio
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    proximity {
+        compatible = "nicera,d3323aa";
+        vdd-gpio = <&gpio 73 0>;
+        clk-vout-gpio = <&gpio 78 0>;
+        data-gpio = <&gpio 76 0>;
+    };
+...
-- 
2.39.5


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

* [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-09 15:03 [PATCH 0/3] Add driver for Nicera D3-323-AA PIR sensor Waqar Hameed
  2025-05-09 15:03 ` [PATCH 2/3] dt-bindings: iio: proximity: Add " Waqar Hameed
@ 2025-05-09 15:03 ` Waqar Hameed
  2025-05-11  7:57   ` Christophe JAILLET
  2025-05-11 12:14   ` Jonathan Cameron
  2025-05-09 15:09 ` [PATCH 0/3] " Krzysztof Kozlowski
  2 siblings, 2 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-05-09 15:03 UTC (permalink / raw)
  To: Jonathan Cameron, Lars-Peter Clausen; +Cc: kernel, linux-kernel, linux-iio

Nicera D3-323-AA is a PIR sensor for human detection. It has support for
raw data measurements and detection notification. The communication
protocol is custom made and therefore needs to be GPIO bit banged.

The device has two main settings that can be configured: a threshold
value for detection and a band-pass filter. The configurable parameters
for the band-pass filter are the high-pass and low-pass cutoff
frequencies and its peak gain. Map these settings to the corresponding
parameters in the `iio` framework.

The low-pass and high-pass cutoff frequencies are pairwise for the
different available filter types. Because of this, only allow to set the
low-pass cutoff frequency from `sysfs` and use that to configure the
corresponding high-pass cutoff frequency. This is sufficient to
unambiguously choose a filter type.

Raw data measurements can be obtained from the device. However, since we
rely on bit banging, it will be rather cumbersome with buffer support.
The main reason being that the data protocol has strict timing
requirements (it's serial like UART), and it's mainly used during
debugging since in real-world applications only the event notification
is of importance. Therefore, only add support for events (for now).

Signed-off-by: Waqar Hameed <waqar.hameed@axis.com>
---
 drivers/iio/proximity/Kconfig   |   9 +
 drivers/iio/proximity/Makefile  |   1 +
 drivers/iio/proximity/d3323aa.c | 868 ++++++++++++++++++++++++++++++++
 3 files changed, 878 insertions(+)
 create mode 100644 drivers/iio/proximity/d3323aa.c

diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig
index a562a78b7d0d..6070974c2c85 100644
--- a/drivers/iio/proximity/Kconfig
+++ b/drivers/iio/proximity/Kconfig
@@ -32,6 +32,15 @@ config CROS_EC_MKBP_PROXIMITY
 	  To compile this driver as a module, choose M here: the
 	  module will be called cros_ec_mkbp_proximity.
 
+config D3323AA
+	tristate "Nicera (Nippon Ceramic Co.) D3-323-AA PIR sensor"
+	depends on GPIOLIB
+	help
+	  Say Y here to build a driver for the Nicera D3-323-AA PIR sensor.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called d3323aa.
+
 config HX9023S
 	tristate "TYHX HX9023S SAR sensor"
 	select IIO_BUFFER
diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile
index c5e76995764a..152034d38c49 100644
--- a/drivers/iio/proximity/Makefile
+++ b/drivers/iio/proximity/Makefile
@@ -6,6 +6,7 @@
 # When adding new entries keep the list in alphabetical order
 obj-$(CONFIG_AS3935)		+= as3935.o
 obj-$(CONFIG_CROS_EC_MKBP_PROXIMITY) += cros_ec_mkbp_proximity.o
+obj-$(CONFIG_D3323AA)		+= d3323aa.o
 obj-$(CONFIG_HX9023S)		+= hx9023s.o
 obj-$(CONFIG_IRSD200)		+= irsd200.o
 obj-$(CONFIG_ISL29501)		+= isl29501.o
diff --git a/drivers/iio/proximity/d3323aa.c b/drivers/iio/proximity/d3323aa.c
new file mode 100644
index 000000000000..fa08b52636ba
--- /dev/null
+++ b/drivers/iio/proximity/d3323aa.c
@@ -0,0 +1,868 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Nicera D3-323-AA PIR sensor.
+ *
+ * Copyright (C) 2025 Axis Communications AB
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitmap.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+
+#define D3323AA_DRV_NAME "d3323aa"
+
+/*
+ * Register bitmap.
+ * For some reason the first bit is denoted as F37 in the datasheet, the second
+ * as F38 and so on. Note the gap between F60 and F64.
+ */
+#define D3323AA_REG_BIT_SLAVEA1		0	/* F37. */
+#define D3323AA_REG_BIT_SLAVEA2		1	/* F38. */
+#define D3323AA_REG_BIT_SLAVEA3		2	/* F39. */
+#define D3323AA_REG_BIT_SLAVEA4		3	/* F40. */
+#define D3323AA_REG_BIT_SLAVEA5		4	/* F41. */
+#define D3323AA_REG_BIT_SLAVEA6		5	/* F42. */
+#define D3323AA_REG_BIT_SLAVEA7		6	/* F43. */
+#define D3323AA_REG_BIT_SLAVEA8		7	/* F44. */
+#define D3323AA_REG_BIT_SLAVEA9		8	/* F45. */
+#define D3323AA_REG_BIT_SLAVEA10	9	/* F46. */
+#define D3323AA_REG_BIT_DETLVLABS0	10	/* F47. */
+#define D3323AA_REG_BIT_DETLVLABS1	11	/* F48. */
+#define D3323AA_REG_BIT_DETLVLABS2	12	/* F49. */
+#define D3323AA_REG_BIT_DETLVLABS3	13	/* F50. */
+#define D3323AA_REG_BIT_DETLVLABS4	14	/* F51. */
+#define D3323AA_REG_BIT_DETLVLABS5	15	/* F52. */
+#define D3323AA_REG_BIT_DETLVLABS6	16	/* F53. */
+#define D3323AA_REG_BIT_DETLVLABS7	17	/* F54. */
+#define D3323AA_REG_BIT_DSLP		18	/* F55. */
+#define D3323AA_REG_BIT_FSTEP0		19	/* F56. */
+#define D3323AA_REG_BIT_FSTEP1		20	/* F57. */
+#define D3323AA_REG_BIT_FILSEL0		21	/* F58. */
+#define D3323AA_REG_BIT_FILSEL1		22	/* F59. */
+#define D3323AA_REG_BIT_FILSEL2		23	/* F60. */
+#define D3323AA_REG_BIT_FDSET		24	/* F64. */
+#define D3323AA_REG_BIT_F65		25
+#define D3323AA_REG_BIT_F87		(D3323AA_REG_BIT_F65 + (87 - 65))
+
+#define D3323AA_REG_NR_BITS (D3323AA_REG_BIT_F87 - D3323AA_REG_BIT_SLAVEA1 + 1)
+#define D3323AA_THRESH_REG_NR_BITS                                             \
+	(D3323AA_REG_BIT_DETLVLABS7 - D3323AA_REG_BIT_DETLVLABS0 + 1)
+#define D3323AA_FILTER_TYPE_NR_BITS                                            \
+	(D3323AA_REG_BIT_FILSEL2 - D3323AA_REG_BIT_FILSEL0 + 1)
+#define D3323AA_FILTER_GAIN_REG_NR_BITS                                        \
+	(D3323AA_REG_BIT_FSTEP1 - D3323AA_REG_BIT_FSTEP0 + 1)
+
+#define D3323AA_THRESH_DEFAULT_VAL 56
+#define D3323AA_FILTER_GAIN_DEFAULT_VAL 2
+
+/*
+ * The pattern is 0b01101, but store it reversed (0b10110) due to writing from
+ * LSB on the wire (c.f. d3323aa_write_settings()).
+ */
+#define D3323AA_SETTING_END_PATTERN 0x16
+#define D3323AA_SETTING_END_PATTERN_NR_BITS 5
+
+/*
+ * Device should be ready for configuration after this many milliseconds.
+ * Datasheet mentions "approx. 1.2 s". Measurements show around 1.23 s,
+ * therefore add 100 ms of slack.
+ */
+#define D3323AA_RESET_TIMEOUT (1200 + 100)
+
+/*
+ * The configuration of the device (write and read) should be done within this
+ * many milliseconds.
+ */
+#define D3323AA_CONFIG_TIMEOUT 1400
+
+/* Number of IRQs needed for configuration stage after reset. */
+#define D3323AA_IRQ_RESET_COUNT 2
+
+/*
+ * High-pass filter cutoff frequency for the band-pass filter. There is a
+ * corresponding low-pass cutoff frequency for each of the filter types
+ * (denoted A, B, C and D in the datasheet). The index in this array matches
+ * that corresponding value in d3323aa_lp_filter_freq.
+ * Note that this represents a fractional value (e.g. the first value
+ * corresponds to 4 / 10 = 0.4 Hz).
+ */
+static const int d3323aa_hp_filter_freq[][2] = {
+	{ 4, 10 },
+	{ 3, 10 },
+	{ 3, 10 },
+	{ 1, 100 },
+};
+
+/*
+ * Low-pass filter cutoff frequency for the band-pass filter. There is a
+ * corresponding high-pass cutoff frequency for each of the filter types
+ * (denoted A, B, C and D in the datasheet). The index in this array matches
+ * that corresponding value in d3323aa_hp_filter_freq.
+ * Note that this represents a fractional value (e.g. the first value
+ * corresponds to 27 / 10 = 2.7 Hz).
+ */
+static const int d3323aa_lp_filter_freq[][2] = {
+	{ 27, 10 },
+	{ 15, 10 },
+	{ 5, 1 },
+	{ 100, 1 },
+};
+
+/*
+ * Register bitmap values for filter types (denoted A, B, C and D in the
+ * datasheet). The index in this array matches the corresponding value in
+ * d3323aa_lp_filter_freq (which in turn matches d3323aa_hp_filter_freq). For
+ * example, the first value 7 corresponds to 2.7 Hz low-pass and 0.4 Hz
+ * high-pass cutoff frequency.
+ */
+static const int d3323aa_lp_filter_regval[] = {
+	7,
+	0,
+	1,
+	2,
+};
+
+/*
+ * This is denoted as "step" in datasheet and corresponds to the gain at peak
+ * for the band-pass filter. The index in this array is the corresponding index
+ * in d3323aa_filter_gain_regval for the register bitmap value.
+ */
+static const int d3323aa_filter_gain[] = {
+	1,
+	2,
+	3,
+};
+
+/*
+ * Register bitmap values for the filter gain. The index in this array is the
+ * corresponding index in d3323aa_filter_gain for the gain value.
+ */
+static const u8 d3323aa_filter_gain_regval[] = {
+	1,
+	3,
+	0,
+};
+
+struct d3323aa_data {
+	struct completion reset_completion;
+	/*
+	 *  Since the setup process always requires a complete write of the
+	 *  _whole_ register bitmap, we need to synchronize it with a lock.
+	 */
+	struct mutex regmap_lock;
+	atomic_t irq_reset_count;
+	unsigned int irq;
+
+	struct device *dev;
+
+	/* Supply voltage. */
+	struct gpio_desc *gpiod_vdd;
+	/* Input clock or output detection signal (Vout). */
+	struct gpio_desc *gpiod_clk_vout;
+	/* Input (setting) or output data. */
+	struct gpio_desc *gpiod_data;
+
+	DECLARE_BITMAP(regmap, D3323AA_REG_NR_BITS);
+
+	/*
+	 * Indicator for operational mode (configuring or detecting), i.e.
+	 * d3323aa_irq_detection() registered or not.
+	 */
+	bool detecting;
+};
+
+static int d3323aa_read_settings(struct iio_dev *indio_dev,
+				 unsigned long *regmap)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	size_t i;
+	int ret;
+
+	/* Bit bang the clock and data pins. */
+	ret = gpiod_direction_output(data->gpiod_clk_vout, 0);
+	if (ret)
+		return ret;
+
+	ret = gpiod_direction_input(data->gpiod_data);
+	if (ret)
+		return ret;
+
+	dev_dbg(data->dev, "Reading settings...\n");
+
+	for (i = 0; i < D3323AA_REG_NR_BITS; ++i) {
+		/* Clock frequency needs to be 1 kHz. */
+		gpiod_set_value(data->gpiod_clk_vout, 1);
+		udelay(500);
+
+		/* The data seems to change when clock signal is high. */
+		if (gpiod_get_value(data->gpiod_data))
+			set_bit(i, regmap);
+
+		gpiod_set_value(data->gpiod_clk_vout, 0);
+		udelay(500);
+	}
+
+	/* The first bit (F37) is just dummy data. Discard it. */
+	clear_bit(0, regmap);
+
+	/* Datasheet says to wait 30 ms after reading the settings. */
+	msleep(30);
+
+	return 0;
+}
+
+static int d3323aa_write_settings(struct iio_dev *indio_dev,
+				  const unsigned long *regmap)
+{
+	DECLARE_BITMAP(end_pattern, D3323AA_SETTING_END_PATTERN_NR_BITS);
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	size_t i;
+	int ret;
+
+	/* Bit bang the clock and data pins. */
+	ret = gpiod_direction_output(data->gpiod_clk_vout, 0);
+	if (ret)
+		return ret;
+
+	ret = gpiod_direction_output(data->gpiod_data, 0);
+	if (ret)
+		return ret;
+
+	dev_dbg(data->dev, "Writing settings...\n");
+
+	/* First bit (F37) is not used when writing the register map. */
+	for (i = 1; i < D3323AA_REG_NR_BITS; ++i) {
+		gpiod_set_value(data->gpiod_data, test_bit(i, regmap));
+
+		/* Clock frequency needs to be 1 kHz. */
+		gpiod_set_value(data->gpiod_clk_vout, 1);
+		udelay(500);
+		gpiod_set_value(data->gpiod_clk_vout, 0);
+		udelay(500);
+	}
+
+	/* Compulsory end pattern. */
+	bitmap_write(end_pattern, D3323AA_SETTING_END_PATTERN, 0,
+		     D3323AA_SETTING_END_PATTERN_NR_BITS);
+	for (i = 0; i < D3323AA_SETTING_END_PATTERN_NR_BITS; ++i) {
+		gpiod_set_value(data->gpiod_data, test_bit(i, end_pattern));
+
+		gpiod_set_value(data->gpiod_clk_vout, 1);
+		udelay(500);
+		gpiod_set_value(data->gpiod_clk_vout, 0);
+		udelay(500);
+	}
+
+	/* Datasheet says to wait 30 ms after writing the settings. */
+	msleep(30);
+
+	return 0;
+}
+
+static irqreturn_t d3323aa_irq_detection(int irq, void *dev_id)
+{
+	struct iio_dev *indio_dev = dev_id;
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	enum iio_event_direction dir;
+	int val;
+
+	val = gpiod_get_value(data->gpiod_clk_vout);
+	if (val < 0) {
+		dev_err_ratelimited(data->dev,
+				    "Could not read from GPIO clk-vout (%d)\n",
+				    val);
+		return IRQ_HANDLED;
+	}
+
+	dir = val ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
+	iio_push_event(indio_dev,
+		       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
+					    IIO_EV_TYPE_THRESH, dir),
+		       iio_get_time_ns(indio_dev));
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t d3323aa_irq_reset(int irq, void *dev_id)
+{
+	struct iio_dev *indio_dev = dev_id;
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	int count = atomic_inc_return(&data->irq_reset_count);
+
+	if (count == D3323AA_IRQ_RESET_COUNT)
+		complete(&data->reset_completion);
+
+	return IRQ_HANDLED;
+}
+
+static int d3323aa_reset(struct iio_dev *indio_dev)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	long time;
+	int ret;
+
+	/*
+	 * Datasheet says VDD needs to be low at least for 30 ms. Let's add a
+	 * couple more to allow VDD to completely discharge as well.
+	 */
+	gpiod_set_value(data->gpiod_vdd, 0);
+	msleep(30 + 5);
+
+	/*
+	 * After setting VDD to high, the device signals with
+	 * D3323AA_IRQ_RESET_COUNT falling edges on CLK/Vout that it is now
+	 * ready for configuration. Datasheet says that this should happen
+	 * within D3323AA_RESET_TIMEOUT ms. Count these two edges within that
+	 * timeout.
+	 */
+	atomic_set(&data->irq_reset_count, 0);
+	reinit_completion(&data->reset_completion);
+
+	ret = gpiod_direction_input(data->gpiod_clk_vout);
+	if (ret)
+		return ret;
+
+	dev_dbg(data->dev, "Resetting...\n");
+
+	gpiod_set_value(data->gpiod_vdd, 1);
+
+	/*
+	 * Datasheet doesn't mention this but measurements have shown that
+	 * CLK/Vout signal slowly ramps up during the first 1.5 ms after reset.
+	 * This means that the digital signal will have bogus values during this
+	 * period. Let's wait for this ramp-up before counting the "real"
+	 * falling edges.
+	 */
+	usleep_range(2000, 5000);
+
+	if (data->detecting) {
+		/*
+		 * Device had previously been set up and was in operational
+		 * mode. Thus, free that detection IRQ handler before requesting
+		 * the reset IRQ handler.
+		 */
+		free_irq(data->irq, indio_dev);
+		data->detecting = false;
+	}
+
+	ret = request_irq(data->irq, d3323aa_irq_reset, IRQF_TRIGGER_FALLING,
+			  dev_name(data->dev), indio_dev);
+	if (ret)
+		return ret;
+
+	time = wait_for_completion_killable_timeout(
+		&data->reset_completion,
+		msecs_to_jiffies(D3323AA_RESET_TIMEOUT));
+	free_irq(data->irq, indio_dev);
+	if (time == 0) {
+		return -ETIMEDOUT;
+	} else if (time < 0) {
+		/* Got interrupted. */
+		return time;
+	}
+
+	dev_dbg(data->dev, "Reset completed\n");
+
+	return 0;
+}
+
+static int d3323aa_setup(struct iio_dev *indio_dev, const unsigned long *regmap)
+{
+	DECLARE_BITMAP(read_regmap, D3323AA_REG_NR_BITS);
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	unsigned long start_time;
+	int ret;
+
+	ret = d3323aa_reset(indio_dev);
+	if (ret) {
+		if (ret != -ERESTARTSYS)
+			dev_err(data->dev, "Could not reset device (%d)\n",
+				ret);
+
+		return ret;
+	}
+
+	/*
+	 * Datasheet says to wait 10 us before setting the configuration.
+	 * Moreover, the total configuration should be done within
+	 * D3323AA_CONFIG_TIMEOUT ms. Clock it.
+	 */
+	usleep_range(10, 20);
+	start_time = jiffies;
+
+	ret = d3323aa_write_settings(indio_dev, regmap);
+	if (ret) {
+		dev_err(data->dev, "Could not write settings (%d)\n", ret);
+		return ret;
+	}
+
+	ret = d3323aa_read_settings(indio_dev, read_regmap);
+	if (ret) {
+		dev_err(data->dev, "Could not read settings (%d)\n", ret);
+		return ret;
+	}
+
+	if (time_is_before_jiffies(start_time +
+				   msecs_to_jiffies(D3323AA_CONFIG_TIMEOUT))) {
+		dev_err(data->dev, "Could not set up configuration in time\n");
+		return -EAGAIN;
+	}
+
+	/* Check if settings were set successfully. */
+	if (!bitmap_equal(regmap, read_regmap, D3323AA_REG_NR_BITS)) {
+		dev_err(data->dev, "Settings data mismatch\n");
+		return -EIO;
+	}
+
+	/* Now in operational mode. */
+	ret = gpiod_direction_input(data->gpiod_clk_vout);
+	if (ret) {
+		dev_err(data->dev,
+			"Could not set GPIO clk-vout as input (%d)\n", ret);
+		return ret;
+	}
+
+	ret = gpiod_direction_input(data->gpiod_data);
+	if (ret) {
+		dev_err(data->dev, "Could not set GPIO data as input (%d)\n",
+			ret);
+		return ret;
+	}
+
+	ret = request_irq(data->irq, d3323aa_irq_detection,
+			  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			  dev_name(data->dev), indio_dev);
+	if (ret) {
+		dev_err(data->dev, "Could not request IRQ for detection (%d)\n",
+			ret);
+		return ret;
+	}
+
+	bitmap_copy(data->regmap, regmap, D3323AA_REG_NR_BITS);
+	data->detecting = true;
+
+	dev_dbg(data->dev, "Setup done\n");
+
+	return 0;
+}
+
+static int d3323aa_get_hp_filter_freq(unsigned long *regmap, int *val,
+				      int *val2)
+{
+	size_t idx;
+	u8 regval;
+
+	regval = bitmap_read(regmap, D3323AA_REG_BIT_FILSEL0,
+			     D3323AA_FILTER_TYPE_NR_BITS);
+	for (idx = 0; idx < ARRAY_SIZE(d3323aa_lp_filter_regval); ++idx) {
+		if (d3323aa_lp_filter_regval[idx] == regval)
+			break;
+	}
+
+	if (idx == ARRAY_SIZE(d3323aa_lp_filter_regval))
+		return -EINVAL;
+
+	*val = d3323aa_hp_filter_freq[idx][0];
+	*val2 = d3323aa_hp_filter_freq[idx][1];
+
+	return 0;
+}
+
+static int d3323aa_get_lp_filter_freq(unsigned long *regmap, int *val,
+				      int *val2)
+{
+	size_t idx;
+	u8 regval;
+
+	regval = bitmap_read(regmap, D3323AA_REG_BIT_FILSEL0,
+			     D3323AA_FILTER_TYPE_NR_BITS);
+	for (idx = 0; idx < ARRAY_SIZE(d3323aa_lp_filter_regval); ++idx) {
+		if (d3323aa_lp_filter_regval[idx] == regval)
+			break;
+	}
+
+	if (idx == ARRAY_SIZE(d3323aa_lp_filter_regval))
+		return -EINVAL;
+
+	*val = d3323aa_lp_filter_freq[idx][0];
+	*val2 = d3323aa_lp_filter_freq[idx][1];
+
+	return 0;
+}
+
+static int d3323aa_set_lp_filter_freq(unsigned long *regmap, const int val,
+				      int val2)
+{
+	size_t idx;
+
+	/* Truncate fractional part to one digit. */
+	val2 /= 100000;
+
+	for (idx = 0; idx < ARRAY_SIZE(d3323aa_lp_filter_freq); ++idx) {
+		int integer = d3323aa_lp_filter_freq[idx][0] /
+			      d3323aa_lp_filter_freq[idx][1];
+		int fract = d3323aa_lp_filter_freq[idx][0] %
+			    d3323aa_lp_filter_freq[idx][1];
+		if (val == integer && val2 == fract)
+			break;
+	}
+
+	if (idx == ARRAY_SIZE(d3323aa_lp_filter_freq))
+		return -ERANGE;
+
+	bitmap_write(regmap, d3323aa_lp_filter_regval[idx],
+		     D3323AA_REG_BIT_FILSEL0, D3323AA_FILTER_TYPE_NR_BITS);
+
+	return 0;
+}
+
+static int d3323aa_get_filter_gain(unsigned long *regmap, int *val)
+{
+	size_t idx;
+	u8 regval;
+
+	regval = bitmap_read(regmap, D3323AA_REG_BIT_FSTEP0,
+			     D3323AA_FILTER_GAIN_REG_NR_BITS);
+	for (idx = 0; idx < ARRAY_SIZE(d3323aa_filter_gain_regval); ++idx) {
+		if (d3323aa_filter_gain_regval[idx] == regval)
+			break;
+	}
+
+	if (idx == ARRAY_SIZE(d3323aa_filter_gain_regval))
+		return -EINVAL;
+
+	*val = d3323aa_filter_gain[idx];
+
+	return 0;
+}
+
+static int d3323aa_set_filter_gain(unsigned long *regmap, const int val)
+{
+	size_t idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(d3323aa_filter_gain); ++idx) {
+		if (d3323aa_filter_gain[idx] == val)
+			break;
+	}
+
+	if (idx == ARRAY_SIZE(d3323aa_filter_gain))
+		return -ERANGE;
+
+	bitmap_write(regmap, d3323aa_filter_gain_regval[idx],
+		     D3323AA_REG_BIT_FSTEP0, D3323AA_FILTER_GAIN_REG_NR_BITS);
+
+	return 0;
+}
+
+static void d3323aa_get_threshold(unsigned long *regmap, int *val)
+{
+	*val = bitmap_read(regmap, D3323AA_REG_BIT_DETLVLABS0,
+			   D3323AA_THRESH_REG_NR_BITS);
+}
+
+static int d3323aa_set_threshold(unsigned long *regmap, const int val)
+{
+	if (val > ((1 << D3323AA_THRESH_REG_NR_BITS) - 1))
+		return -ERANGE;
+
+	bitmap_write(regmap, val, D3323AA_REG_BIT_DETLVLABS0,
+		     D3323AA_THRESH_REG_NR_BITS);
+
+	return 0;
+}
+
+static int d3323aa_read_avail(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      const int **vals, int *type, int *length,
+			      long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+		*vals = (int *)d3323aa_hp_filter_freq;
+		*type = IIO_VAL_FRACTIONAL;
+		*length = 2 * ARRAY_SIZE(d3323aa_hp_filter_freq);
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+		*vals = (int *)d3323aa_lp_filter_freq;
+		*type = IIO_VAL_FRACTIONAL;
+		*length = 2 * ARRAY_SIZE(d3323aa_lp_filter_freq);
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		*vals = (int *)d3323aa_filter_gain;
+		*type = IIO_VAL_INT;
+		*length = ARRAY_SIZE(d3323aa_filter_gain);
+		return IIO_AVAIL_LIST;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int d3323aa_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	int ret;
+
+	guard(mutex)(&data->regmap_lock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+		ret = d3323aa_get_hp_filter_freq(data->regmap, val, val2);
+		if (ret)
+			return ret;
+		return IIO_VAL_FRACTIONAL;
+	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+		ret = d3323aa_get_lp_filter_freq(data->regmap, val, val2);
+		if (ret)
+			return ret;
+		return IIO_VAL_FRACTIONAL;
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		ret = d3323aa_get_filter_gain(data->regmap, val);
+		if (ret)
+			return ret;
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int d3323aa_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	DECLARE_BITMAP(regmap, D3323AA_REG_NR_BITS);
+	int ret;
+
+	guard(mutex)(&data->regmap_lock);
+
+	bitmap_copy(regmap, data->regmap, D3323AA_REG_NR_BITS);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+		/*
+		 * We only allow to set the low-pass cutoff frequency, since
+		 * that is the only way to unambigously choose the type of
+		 * band-pass filter. For example, both filter type B and C have
+		 * 0.3 Hz as high-pass cutoff frequency (see
+		 * d3323aa_hp_filter_freq).
+		 */
+		return -EINVAL;
+	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+		ret = d3323aa_set_lp_filter_freq(regmap, val, val2);
+		if (ret)
+			return ret;
+		break;
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		ret = d3323aa_set_filter_gain(regmap, val);
+		if (ret)
+			return ret;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return d3323aa_setup(indio_dev, regmap);
+}
+
+static int d3323aa_read_event(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 d3323aa_data *data = iio_priv(indio_dev);
+
+	guard(mutex)(&data->regmap_lock);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		d3323aa_get_threshold(data->regmap, val);
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int d3323aa_write_event(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 d3323aa_data *data = iio_priv(indio_dev);
+	DECLARE_BITMAP(regmap, D3323AA_REG_NR_BITS);
+	int ret;
+
+	guard(mutex)(&data->regmap_lock);
+
+	bitmap_copy(regmap, data->regmap, D3323AA_REG_NR_BITS);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		ret = d3323aa_set_threshold(regmap, val);
+		if (ret)
+			return ret;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return d3323aa_setup(indio_dev, regmap);
+}
+
+static const struct iio_info d3323aa_info = {
+	.read_avail = d3323aa_read_avail,
+	.read_raw = d3323aa_read_raw,
+	.write_raw = d3323aa_write_raw,
+	.read_event_value = d3323aa_read_event,
+	.write_event_value = d3323aa_write_event,
+};
+
+static const struct iio_event_spec d3323aa_event_spec[] = {
+	{
+		.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 d3323aa_channels[] = {
+	{
+		.type = IIO_PROXIMITY,
+		.info_mask_separate =
+			BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) |
+			BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
+			BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+		.info_mask_separate_available =
+			BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) |
+			BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
+			BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+		.event_spec = d3323aa_event_spec,
+		.num_event_specs = ARRAY_SIZE(d3323aa_event_spec),
+	},
+};
+
+static int d3323aa_probe(struct platform_device *pdev)
+{
+	DECLARE_BITMAP(default_regmap, D3323AA_REG_NR_BITS);
+	struct d3323aa_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
+	if (!indio_dev)
+		return dev_err_probe(&pdev->dev, -ENOMEM,
+				     "Could not allocate iio device\n");
+
+	data = iio_priv(indio_dev);
+	data->dev = &pdev->dev;
+	platform_set_drvdata(pdev, indio_dev);
+
+	init_completion(&data->reset_completion);
+
+	ret = devm_mutex_init(data->dev, &data->regmap_lock);
+	if (ret)
+		return dev_err_probe(data->dev, ret,
+				     "Could not initialize mutex (%d)\n", ret);
+
+	/* Request GPIOs. */
+	data->gpiod_vdd = devm_gpiod_get(data->dev, "vdd", GPIOD_OUT_LOW);
+	if (IS_ERR(data->gpiod_vdd))
+		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_vdd),
+				     "Could not get GPIO vdd (%ld)\n",
+				     PTR_ERR(data->gpiod_vdd));
+
+	data->gpiod_clk_vout =
+		devm_gpiod_get(data->dev, "clk-vout", GPIOD_OUT_LOW);
+	if (IS_ERR(data->gpiod_clk_vout))
+		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_clk_vout),
+				     "Could not get GPIO clk-vout (%ld)\n",
+				     PTR_ERR(data->gpiod_clk_vout));
+
+	data->gpiod_data = devm_gpiod_get(data->dev, "data", GPIOD_OUT_LOW);
+	if (IS_ERR(data->gpiod_data))
+		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_data),
+				     "Could not get GPIO data (%ld)\n",
+				     PTR_ERR(data->gpiod_data));
+
+	ret = gpiod_to_irq(data->gpiod_clk_vout);
+	if (ret < 0)
+		return dev_err_probe(data->dev, ret, "Could not get IRQ (%d)\n",
+				     ret);
+
+	data->irq = ret;
+
+	/* Do one setup with the default values. */
+	bitmap_zero(default_regmap, D3323AA_REG_NR_BITS);
+	d3323aa_set_threshold(default_regmap, D3323AA_THRESH_DEFAULT_VAL);
+	d3323aa_set_filter_gain(default_regmap,
+				D3323AA_FILTER_GAIN_DEFAULT_VAL);
+	ret = d3323aa_setup(indio_dev, default_regmap);
+	if (ret)
+		return ret;
+
+	indio_dev->info = &d3323aa_info;
+	indio_dev->name = D3323AA_DRV_NAME;
+	indio_dev->channels = d3323aa_channels;
+	indio_dev->num_channels = ARRAY_SIZE(d3323aa_channels);
+
+	ret = devm_iio_device_register(data->dev, indio_dev);
+	if (ret)
+		return dev_err_probe(data->dev, ret,
+				     "Could not register iio device (%d)\n",
+				     ret);
+
+	return 0;
+}
+
+static void d3323aa_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct d3323aa_data *data = iio_priv(indio_dev);
+
+	if (data->detecting)
+		free_irq(data->irq, indio_dev);
+}
+
+static const struct of_device_id d3323aa_of_match[] = {
+	{
+		.compatible = "nicera,d3323aa",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, d3323aa_of_match);
+
+static struct platform_driver d3323aa_driver = {
+	.probe = d3323aa_probe,
+	.remove = d3323aa_remove,
+	.driver = {
+		.name = D3323AA_DRV_NAME,
+		.of_match_table = d3323aa_of_match,
+	},
+};
+module_platform_driver(d3323aa_driver);
+
+MODULE_AUTHOR("Waqar Hameed <waqar.hameed@axis.com>");
+MODULE_DESCRIPTION("Nicera D3-323-AA PIR sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.39.5


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

* Re: [PATCH 2/3] dt-bindings: iio: proximity: Add Nicera D3-323-AA PIR sensor
  2025-05-09 15:03 ` [PATCH 2/3] dt-bindings: iio: proximity: Add " Waqar Hameed
@ 2025-05-09 15:06   ` Krzysztof Kozlowski
  2025-05-16 17:15     ` Waqar Hameed
  0 siblings, 1 reply; 21+ messages in thread
From: Krzysztof Kozlowski @ 2025-05-09 15:06 UTC (permalink / raw)
  To: Waqar Hameed, Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: kernel, linux-iio, devicetree, linux-kernel

On 09/05/2025 17:03, Waqar Hameed wrote:
> Nicera D3-323-AA is a PIR sensor for human detection. It has support for
> raw data measurements and detection notification. The communication
> protocol is custom made and therefore needs to be GPIO bit banged.
> 
> Add devicetree bindings requiring the compatible string and the various
> GPIOs needed for operation. Some of the GPIOs have multiple use-cases
> depending on device state. Describe these thoroughly.


Drop redundant parts of description. Describe the hardware. Entire last
paragraph is pretty pointless.

> +
> +properties:
> +  compatible:
> +    const: nicera,d3323aa
> +
> +  vdd-gpio:
> +    maxItems: 1
> +    description:
> +      GPIO for supply voltage (1.8 to 5.5 V).

This is not how pins are represented in the kernel. Either you have here
regulator (supply) or reset gpios. Plus 'gpio' suffix is not valid, btw.

Datasheet says this is a supply.

> +      This GPIO will be driven low by the driver in order to cut the supply and
> +      reset the device (driving it then back to high to power it on).
> +
> +  clk-vout-gpio:

No, for the similar reasons. Which pin is this?

> +    maxItems: 1
> +    description:
> +      GPIO for clock and detection.
> +      After reset, the device signals with two falling edges on this pin that it
> +      is ready for configuration (within 1.2 s), which the driver listens for as
> +      interrupts.
> +      During configuration, it is used as clock for data reading and writing (on
> +      data-gpio). The driver drives this pin with the frequency of 1 kHz (bit
> +      banging).
> +      After all this, the device is now in operational mode and signals on this
> +      pin for any detections. The driver listens for this as interrupts.
> +
> +  data-gpio:

There is no such pin.

> +    maxItems: 1
> +    description:
> +      GPIO for data reading and writing.
> +      During configuration, configuration data will be written and read back
> +      with bit banging (together with clk-vout-gpio as clock).
> +      After this, during operational mode, the device will output serial data on
> +      this GPIO. However, the driver currently doesn't read this.
> +
> +required:
> +  - compatible
> +  - vdd-gpio
> +  - clk-vout-gpio
> +  - data-gpio
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>

So you included that header

> +
> +    proximity {
> +        compatible = "nicera,d3323aa";
> +        vdd-gpio = <&gpio 73 0>;
> +        clk-vout-gpio = <&gpio 78 0>;
> +        data-gpio = <&gpio 76 0>;

But where are you using it?

> +    };
> +...


Best regards,
Krzysztof

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

* Re: [PATCH 0/3] Add driver for Nicera D3-323-AA PIR sensor
  2025-05-09 15:03 [PATCH 0/3] Add driver for Nicera D3-323-AA PIR sensor Waqar Hameed
  2025-05-09 15:03 ` [PATCH 2/3] dt-bindings: iio: proximity: Add " Waqar Hameed
  2025-05-09 15:03 ` [PATCH 3/3] iio: Add driver for " Waqar Hameed
@ 2025-05-09 15:09 ` Krzysztof Kozlowski
  2025-05-16 17:14   ` Waqar Hameed
  2 siblings, 1 reply; 21+ messages in thread
From: Krzysztof Kozlowski @ 2025-05-09 15:09 UTC (permalink / raw)
  To: Waqar Hameed, Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski
  Cc: kernel, linux-kernel, linux-iio, devicetree

On 09/05/2025 17:03, Waqar Hameed wrote:
> Nicera D3-323-AA is a PIR sensor for human detection. It has support for
> raw data measurements and detection notification. The communication
> protocol is custom made and therefore needs to be GPIO bit banged.
> 
> Previously, there has been an attempt to add a driver for this device
> [1]. However, that driver was written for the wrong sub-system. `hwmon`

So that's a v2. Mark your patches correctly.

> is clearly not a suitable framework for a proximity device.
> 
> In this series, we add a driver for support for event notification for
> detections through IIO (the more appropriate sub-system!). The various
> settings have been mapped to existing `sysfs` ABIs in the IIO framework.
> 
> The public datasheet [2] is quite sparse. A more detailed version can be
> obtained through the company.
> 
> [1] https://lore.kernel.org/lkml/20241212042412.702044-2-Hermes.Zhang@axis.com/
Read the comments given in that review:
https://lore.kernel.org/lkml/wy7nyg3cztixe5y5rg4kbsbbly32h547hwumwwvrfme4fdgsj5@znfpypleebrb/

You repeated same mistakes, which means I did same review second time
which is waste of my time.

Best regards,
Krzysztof

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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-09 15:03 ` [PATCH 3/3] iio: Add driver for " Waqar Hameed
@ 2025-05-11  7:57   ` Christophe JAILLET
  2025-05-16 17:16     ` Waqar Hameed
  2025-05-11 12:14   ` Jonathan Cameron
  1 sibling, 1 reply; 21+ messages in thread
From: Christophe JAILLET @ 2025-05-11  7:57 UTC (permalink / raw)
  To: waqar.hameed; +Cc: jic23, kernel, lars, linux-iio, linux-kernel

Le 09/05/2025 à 17:03, Waqar Hameed a écrit :
> Nicera D3-323-AA is a PIR sensor for human detection. It has support for
> raw data measurements and detection notification. The communication
> protocol is custom made and therefore needs to be GPIO bit banged.
> 
> The device has two main settings that can be configured: a threshold
> value for detection and a band-pass filter. The configurable parameters
> for the band-pass filter are the high-pass and low-pass cutoff
> frequencies and its peak gain. Map these settings to the corresponding
> parameters in the `iio` framework.
> 
> The low-pass and high-pass cutoff frequencies are pairwise for the
> different available filter types. Because of this, only allow to set the
> low-pass cutoff frequency from `sysfs` and use that to configure the
> corresponding high-pass cutoff frequency. This is sufficient to
> unambiguously choose a filter type.
> 
> Raw data measurements can be obtained from the device. However, since we
> rely on bit banging, it will be rather cumbersome with buffer support.
> The main reason being that the data protocol has strict timing
> requirements (it's serial like UART), and it's mainly used during
> debugging since in real-world applications only the event notification
> is of importance. Therefore, only add support for events (for now).
> 
> Signed-off-by: Waqar Hameed <waqar.hameed-VrBV9hrLPhE@public.gmane.org>
> ---

Hi,

...

> +static int d3323aa_set_lp_filter_freq(unsigned long *regmap, const int val,
> +				      int val2)
> +{
> +	size_t idx;
> +
> +	/* Truncate fractional part to one digit. */
> +	val2 /= 100000;
> +
> +	for (idx = 0; idx < ARRAY_SIZE(d3323aa_lp_filter_freq); ++idx) {
> +		int integer = d3323aa_lp_filter_freq[idx][0] /
> +			      d3323aa_lp_filter_freq[idx][1];
> +		int fract = d3323aa_lp_filter_freq[idx][0] %
> +			    d3323aa_lp_filter_freq[idx][1];

Missing newline.

> +		if (val == integer && val2 == fract)
> +			break;
> +	}
> +
> +	if (idx == ARRAY_SIZE(d3323aa_lp_filter_freq))
> +		return -ERANGE;
> +
> +	bitmap_write(regmap, d3323aa_lp_filter_regval[idx],
> +		     D3323AA_REG_BIT_FILSEL0, D3323AA_FILTER_TYPE_NR_BITS);
> +
> +	return 0;
> +}

...

> +static int d3323aa_probe(struct platform_device *pdev)
> +{
> +	DECLARE_BITMAP(default_regmap, D3323AA_REG_NR_BITS);
> +	struct d3323aa_data *data;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
> +	if (!indio_dev)
> +		return dev_err_probe(&pdev->dev, -ENOMEM,
> +				     "Could not allocate iio device\n");
> +
> +	data = iio_priv(indio_dev);
> +	data->dev = &pdev->dev;
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	init_completion(&data->reset_completion);
> +
> +	ret = devm_mutex_init(data->dev, &data->regmap_lock);
> +	if (ret)
> +		return dev_err_probe(data->dev, ret,
> +				     "Could not initialize mutex (%d)\n", ret);

No need to repeat the error code in the message, when using dev_err_probe().

Same for all calls below.

> +
> +	/* Request GPIOs. */
> +	data->gpiod_vdd = devm_gpiod_get(data->dev, "vdd", GPIOD_OUT_LOW);
> +	if (IS_ERR(data->gpiod_vdd))
> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_vdd),
> +				     "Could not get GPIO vdd (%ld)\n",
> +				     PTR_ERR(data->gpiod_vdd));
> +
> +	data->gpiod_clk_vout =
> +		devm_gpiod_get(data->dev, "clk-vout", GPIOD_OUT_LOW);
> +	if (IS_ERR(data->gpiod_clk_vout))
> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_clk_vout),
> +				     "Could not get GPIO clk-vout (%ld)\n",
> +				     PTR_ERR(data->gpiod_clk_vout));
> +
> +	data->gpiod_data = devm_gpiod_get(data->dev, "data", GPIOD_OUT_LOW);
> +	if (IS_ERR(data->gpiod_data))
> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_data),
> +				     "Could not get GPIO data (%ld)\n",
> +				     PTR_ERR(data->gpiod_data));
> +
> +	ret = gpiod_to_irq(data->gpiod_clk_vout);
> +	if (ret < 0)
> +		return dev_err_probe(data->dev, ret, "Could not get IRQ (%d)\n",
> +				     ret);
> +
> +	data->irq = ret;
> +
> +	/* Do one setup with the default values. */
> +	bitmap_zero(default_regmap, D3323AA_REG_NR_BITS);
> +	d3323aa_set_threshold(default_regmap, D3323AA_THRESH_DEFAULT_VAL);
> +	d3323aa_set_filter_gain(default_regmap,
> +				D3323AA_FILTER_GAIN_DEFAULT_VAL);
> +	ret = d3323aa_setup(indio_dev, default_regmap);
> +	if (ret)
> +		return ret;
> +
> +	indio_dev->info = &d3323aa_info;
> +	indio_dev->name = D3323AA_DRV_NAME;
> +	indio_dev->channels = d3323aa_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(d3323aa_channels);
> +
> +	ret = devm_iio_device_register(data->dev, indio_dev);
> +	if (ret)
> +		return dev_err_probe(data->dev, ret,
> +				     "Could not register iio device (%d)\n",
> +				     ret);
> +
> +	return 0;
> +}

...

CJ


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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-09 15:03 ` [PATCH 3/3] iio: Add driver for " Waqar Hameed
  2025-05-11  7:57   ` Christophe JAILLET
@ 2025-05-11 12:14   ` Jonathan Cameron
  2025-05-16 17:16     ` Waqar Hameed
  1 sibling, 1 reply; 21+ messages in thread
From: Jonathan Cameron @ 2025-05-11 12:14 UTC (permalink / raw)
  To: Waqar Hameed; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio

On Fri, 9 May 2025 17:03:03 +0200
Waqar Hameed <waqar.hameed@axis.com> wrote:

> Nicera D3-323-AA is a PIR sensor for human detection. It has support for
> raw data measurements and detection notification. The communication
> protocol is custom made and therefore needs to be GPIO bit banged.
> 
> The device has two main settings that can be configured: a threshold
> value for detection and a band-pass filter. The configurable parameters
> for the band-pass filter are the high-pass and low-pass cutoff
> frequencies and its peak gain. Map these settings to the corresponding
> parameters in the `iio` framework.
> 
> The low-pass and high-pass cutoff frequencies are pairwise for the
> different available filter types. Because of this, only allow to set the
> low-pass cutoff frequency from `sysfs` and use that to configure the
> corresponding high-pass cutoff frequency. This is sufficient to
> unambiguously choose a filter type.
> 
> Raw data measurements can be obtained from the device. However, since we
> rely on bit banging, it will be rather cumbersome with buffer support.
> The main reason being that the data protocol has strict timing
> requirements (it's serial like UART), and it's mainly used during
> debugging since in real-world applications only the event notification
> is of importance. Therefore, only add support for events (for now).
> 
> Signed-off-by: Waqar Hameed <waqar.hameed@axis.com>
Hi Waqar,

Biggest suggestion in here is hide those bitmaps. They are of interest
only at point of reading and writing. I wouldn't use them for device state
elsewhere as they make for much less readable code than using simple
fields in iio_priv() for the state elements and building up the bitmaps
only where needed.

Jonathan

> diff --git a/drivers/iio/proximity/d3323aa.c b/drivers/iio/proximity/d3323aa.c
> new file mode 100644
> index 000000000000..fa08b52636ba
> --- /dev/null
> +++ b/drivers/iio/proximity/d3323aa.c
> @@ -0,0 +1,868 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Driver for Nicera D3-323-AA PIR sensor.
> + *
> + * Copyright (C) 2025 Axis Communications AB
> + */
> +
> +#include <linux/atomic.h>
> +#include <linux/bitmap.h>
> +#include <linux/cleanup.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/jiffies.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> +
> +#define D3323AA_DRV_NAME "d3323aa"

Put that inline where used.  A define like this both implies that various values
must be the same when they need not be and means that we have to go find the
define to fine out what they are set to.  Just setting the strings directly
tends to end up more readable.

> +
> +/*
> + * Register bitmap.
> + * For some reason the first bit is denoted as F37 in the datasheet, the second
> + * as F38 and so on. Note the gap between F60 and F64.
> + */
> +#define D3323AA_REG_BIT_SLAVEA1		0	/* F37. */
> +#define D3323AA_REG_BIT_SLAVEA2		1	/* F38. */
> +#define D3323AA_REG_BIT_SLAVEA3		2	/* F39. */
> +#define D3323AA_REG_BIT_SLAVEA4		3	/* F40. */
> +#define D3323AA_REG_BIT_SLAVEA5		4	/* F41. */
> +#define D3323AA_REG_BIT_SLAVEA6		5	/* F42. */
> +#define D3323AA_REG_BIT_SLAVEA7		6	/* F43. */
> +#define D3323AA_REG_BIT_SLAVEA8		7	/* F44. */
> +#define D3323AA_REG_BIT_SLAVEA9		8	/* F45. */
Perhaps these can be represented as masks using GENMASK() rather than
bits.  A lot of this will be hidden away if you follow suggesting to
only expose that you are using a bitmap to bitbang in the read/write
functions.

> +#define D3323AA_REG_BIT_SLAVEA10	9	/* F46. */
> +#define D3323AA_REG_BIT_DETLVLABS0	10	/* F47. */
> +#define D3323AA_REG_BIT_DETLVLABS1	11	/* F48. */
> +#define D3323AA_REG_BIT_DETLVLABS2	12	/* F49. */
> +#define D3323AA_REG_BIT_DETLVLABS3	13	/* F50. */
> +#define D3323AA_REG_BIT_DETLVLABS4	14	/* F51. */
> +#define D3323AA_REG_BIT_DETLVLABS5	15	/* F52. */
> +#define D3323AA_REG_BIT_DETLVLABS6	16	/* F53. */
> +#define D3323AA_REG_BIT_DETLVLABS7	17	/* F54. */
> +#define D3323AA_REG_BIT_DSLP		18	/* F55. */
> +#define D3323AA_REG_BIT_FSTEP0		19	/* F56. */
> +#define D3323AA_REG_BIT_FSTEP1		20	/* F57. */
> +#define D3323AA_REG_BIT_FILSEL0		21	/* F58. */
> +#define D3323AA_REG_BIT_FILSEL1		22	/* F59. */
> +#define D3323AA_REG_BIT_FILSEL2		23	/* F60. */
> +#define D3323AA_REG_BIT_FDSET		24	/* F64. */
> +#define D3323AA_REG_BIT_F65		25
> +#define D3323AA_REG_BIT_F87		(D3323AA_REG_BIT_F65 + (87 - 65))
> +
> +#define D3323AA_REG_NR_BITS (D3323AA_REG_BIT_F87 - D3323AA_REG_BIT_SLAVEA1 + 1)
> +#define D3323AA_THRESH_REG_NR_BITS                                             \
> +	(D3323AA_REG_BIT_DETLVLABS7 - D3323AA_REG_BIT_DETLVLABS0 + 1)
> +#define D3323AA_FILTER_TYPE_NR_BITS                                            \
> +	(D3323AA_REG_BIT_FILSEL2 - D3323AA_REG_BIT_FILSEL0 + 1)
> +#define D3323AA_FILTER_GAIN_REG_NR_BITS                                        \
> +	(D3323AA_REG_BIT_FSTEP1 - D3323AA_REG_BIT_FSTEP0 + 1)

> +/*
> + * High-pass filter cutoff frequency for the band-pass filter. There is a
> + * corresponding low-pass cutoff frequency for each of the filter types
> + * (denoted A, B, C and D in the datasheet). The index in this array matches
> + * that corresponding value in d3323aa_lp_filter_freq.
> + * Note that this represents a fractional value (e.g. the first value
> + * corresponds to 4 / 10 = 0.4 Hz).
> + */
> +static const int d3323aa_hp_filter_freq[][2] = {
> +	{ 4, 10 },
> +	{ 3, 10 },
> +	{ 3, 10 },
> +	{ 1, 100 },
> +};
> +
> +/*
> + * Low-pass filter cutoff frequency for the band-pass filter. There is a
> + * corresponding high-pass cutoff frequency for each of the filter types
> + * (denoted A, B, C and D in the datasheet). The index in this array matches
> + * that corresponding value in d3323aa_hp_filter_freq.
> + * Note that this represents a fractional value (e.g. the first value
> + * corresponds to 27 / 10 = 2.7 Hz).
> + */
> +static const int d3323aa_lp_filter_freq[][2] = {
> +	{ 27, 10 },
> +	{ 15, 10 },
> +	{ 5, 1 },
> +	{ 100, 1 },
> +};
> +
> +/*
> + * Register bitmap values for filter types (denoted A, B, C and D in the
> + * datasheet). The index in this array matches the corresponding value in
> + * d3323aa_lp_filter_freq (which in turn matches d3323aa_hp_filter_freq). For
> + * example, the first value 7 corresponds to 2.7 Hz low-pass and 0.4 Hz
> + * high-pass cutoff frequency.
> + */
> +static const int d3323aa_lp_filter_regval[] = {
> +	7,
> +	0,
> +	1,
> +	2,
> +};
> +
> +/*
> + * This is denoted as "step" in datasheet and corresponds to the gain at peak
> + * for the band-pass filter. The index in this array is the corresponding index
> + * in d3323aa_filter_gain_regval for the register bitmap value.
> + */
> +static const int d3323aa_filter_gain[] = {
> +	1,
> +	2,
> +	3,
> +};
	1, 2, 3,
is fine for such a short list.

> +
> +/*
> + * Register bitmap values for the filter gain. The index in this array is the
> + * corresponding index in d3323aa_filter_gain for the gain value.
> + */
> +static const u8 d3323aa_filter_gain_regval[] = {
> +	1,
> +	3,
> +	0,
> +};
> +
> +struct d3323aa_data {
> +	struct completion reset_completion;
> +	/*
> +	 *  Since the setup process always requires a complete write of the
> +	 *  _whole_ register bitmap, we need to synchronize it with a lock.
> +	 */
> +	struct mutex regmap_lock;
> +	atomic_t irq_reset_count;
> +	unsigned int irq;
> +
> +	struct device *dev;
> +
> +	/* Supply voltage. */
> +	struct gpio_desc *gpiod_vdd;
> +	/* Input clock or output detection signal (Vout). */

I'd rename. Vout kind of suggests a variable voltage. This seems to just
be a level signal.

> +	struct gpio_desc *gpiod_clk_vout;
> +	/* Input (setting) or output data. */
> +	struct gpio_desc *gpiod_data;
> +
> +	DECLARE_BITMAP(regmap, D3323AA_REG_NR_BITS);
> +
> +	/*
> +	 * Indicator for operational mode (configuring or detecting), i.e.
> +	 * d3323aa_irq_detection() registered or not.
> +	 */
> +	bool detecting;
> +};
> +
> +static int d3323aa_read_settings(struct iio_dev *indio_dev,
> +				 unsigned long *regmap)
> +{
> +	struct d3323aa_data *data = iio_priv(indio_dev);
> +	size_t i;
> +	int ret;
> +
> +	/* Bit bang the clock and data pins. */
> +	ret = gpiod_direction_output(data->gpiod_clk_vout, 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = gpiod_direction_input(data->gpiod_data);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(data->dev, "Reading settings...\n");
> +
> +	for (i = 0; i < D3323AA_REG_NR_BITS; ++i) {
> +		/* Clock frequency needs to be 1 kHz. */
> +		gpiod_set_value(data->gpiod_clk_vout, 1);
> +		udelay(500);
> +
> +		/* The data seems to change when clock signal is high. */
> +		if (gpiod_get_value(data->gpiod_data))
> +			set_bit(i, regmap);
> +
> +		gpiod_set_value(data->gpiod_clk_vout, 0);
> +		udelay(500);
> +	}
> +
> +	/* The first bit (F37) is just dummy data. Discard it. */
> +	clear_bit(0, regmap);
> +
> +	/* Datasheet says to wait 30 ms after reading the settings. */
> +	msleep(30);
> +
> +	return 0;
> +}
> +
> +static int d3323aa_write_settings(struct iio_dev *indio_dev,
> +				  const unsigned long *regmap)

Rename regmap. regmap means some specific stuff in the kernel register_bm or something
like that avoids that potential confusion.

However, it would be much easier to read this driver if only this
function and the read one knew about the bitmap stuff.  Inside the reset
of the driver just store and pass around a structure with the various fields.
Then in here use that to build up the bitmap locally and write to the device.
The opposite in read which decodes the bitmap into those fields.

That will make for a more standard and easier to review driver.

> +{
> +	DECLARE_BITMAP(end_pattern, D3323AA_SETTING_END_PATTERN_NR_BITS);
> +	struct d3323aa_data *data = iio_priv(indio_dev);
> +	size_t i;
> +	int ret;
> +
> +	/* Bit bang the clock and data pins. */
> +	ret = gpiod_direction_output(data->gpiod_clk_vout, 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = gpiod_direction_output(data->gpiod_data, 0);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(data->dev, "Writing settings...\n");
> +
> +	/* First bit (F37) is not used when writing the register map. */
> +	for (i = 1; i < D3323AA_REG_NR_BITS; ++i) {
> +		gpiod_set_value(data->gpiod_data, test_bit(i, regmap));
> +
> +		/* Clock frequency needs to be 1 kHz. */
> +		gpiod_set_value(data->gpiod_clk_vout, 1);
> +		udelay(500);
> +		gpiod_set_value(data->gpiod_clk_vout, 0);
> +		udelay(500);
> +	}
> +
> +	/* Compulsory end pattern. */
> +	bitmap_write(end_pattern, D3323AA_SETTING_END_PATTERN, 0,
> +		     D3323AA_SETTING_END_PATTERN_NR_BITS);
 
Why not use a larger bitmap with padding for this end pattern
and just write this into that?  At which point this can all be one loop.

> +	for (i = 0; i < D3323AA_SETTING_END_PATTERN_NR_BITS; ++i) {
> +		gpiod_set_value(data->gpiod_data, test_bit(i, end_pattern));
> +
> +		gpiod_set_value(data->gpiod_clk_vout, 1);
> +		udelay(500);
> +		gpiod_set_value(data->gpiod_clk_vout, 0);
> +		udelay(500);
> +	}
> +
> +	/* Datasheet says to wait 30 ms after writing the settings. */
> +	msleep(30);
> +
> +	return 0;
> +}
> +
> +static irqreturn_t d3323aa_irq_detection(int irq, void *dev_id)
> +{
> +	struct iio_dev *indio_dev = dev_id;
> +	struct d3323aa_data *data = iio_priv(indio_dev);
> +	enum iio_event_direction dir;
> +	int val;
> +
> +	val = gpiod_get_value(data->gpiod_clk_vout);

Ideally I'd like a setup where we can wire the interrupt side of this
to one pin and the gpio needed for writing to another.  In practice
they may well be the same pin but that does introduced a bunch of races
and dependency on what the interrupt controller does when an irq
is disabled.

Using one pin as an irq and as a data line is prone to some nasty
corner cases though it works on some SoCs.


> +	if (val < 0) {
> +		dev_err_ratelimited(data->dev,
> +				    "Could not read from GPIO clk-vout (%d)\n",
> +				    val);
> +		return IRQ_HANDLED;
> +	}
> +
> +	dir = val ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
> +	iio_push_event(indio_dev,
> +		       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
> +					    IIO_EV_TYPE_THRESH, dir),
> +		       iio_get_time_ns(indio_dev));
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t d3323aa_irq_reset(int irq, void *dev_id)
> +{
> +	struct iio_dev *indio_dev = dev_id;
> +	struct d3323aa_data *data = iio_priv(indio_dev);
> +	int count = atomic_inc_return(&data->irq_reset_count);
> +

As below. It should be easy enough to have a unified handler and avoid
the need to unregister / reregister the irq.

> +	if (count == D3323AA_IRQ_RESET_COUNT)
> +		complete(&data->reset_completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int d3323aa_reset(struct iio_dev *indio_dev)
> +{
> +	struct d3323aa_data *data = iio_priv(indio_dev);
> +	long time;
> +	int ret;
> +
> +	/*
> +	 * Datasheet says VDD needs to be low at least for 30 ms. Let's add a
> +	 * couple more to allow VDD to completely discharge as well.
> +	 */
> +	gpiod_set_value(data->gpiod_vdd, 0);
> +	msleep(30 + 5);
> +
> +	/*
> +	 * After setting VDD to high, the device signals with
> +	 * D3323AA_IRQ_RESET_COUNT falling edges on CLK/Vout that it is now
> +	 * ready for configuration. Datasheet says that this should happen
> +	 * within D3323AA_RESET_TIMEOUT ms. Count these two edges within that
> +	 * timeout.
> +	 */
> +	atomic_set(&data->irq_reset_count, 0);
> +	reinit_completion(&data->reset_completion);
> +
> +	ret = gpiod_direction_input(data->gpiod_clk_vout);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(data->dev, "Resetting...\n");
> +
> +	gpiod_set_value(data->gpiod_vdd, 1);
> +
> +	/*
> +	 * Datasheet doesn't mention this but measurements have shown that
> +	 * CLK/Vout signal slowly ramps up during the first 1.5 ms after reset.
> +	 * This means that the digital signal will have bogus values during this
> +	 * period. Let's wait for this ramp-up before counting the "real"
> +	 * falling edges.
> +	 */
> +	usleep_range(2000, 5000);
> +
> +	if (data->detecting) {
> +		/*
> +		 * Device had previously been set up and was in operational
> +		 * mode. Thus, free that detection IRQ handler before requesting
> +		 * the reset IRQ handler.
> +		 */
> +		free_irq(data->irq, indio_dev);
> +		data->detecting = false;
> +	}
> +
> +	ret = request_irq(data->irq, d3323aa_irq_reset, IRQF_TRIGGER_FALLING,
> +			  dev_name(data->dev), indio_dev);

Can you keep to a single handler with a check on device state?  This
dance with releasing and requesting irqs is rather complex when all you need
to know is what the irq means and that is state the driver knows.


> +	if (ret)
> +		return ret;
> +
> +	time = wait_for_completion_killable_timeout(
> +		&data->reset_completion,
> +		msecs_to_jiffies(D3323AA_RESET_TIMEOUT));
> +	free_irq(data->irq, indio_dev);
> +	if (time == 0) {
> +		return -ETIMEDOUT;
> +	} else if (time < 0) {
> +		/* Got interrupted. */
> +		return time;
> +	}
> +
> +	dev_dbg(data->dev, "Reset completed\n");
> +
> +	return 0;
> +}
> +
> +static int d3323aa_setup(struct iio_dev *indio_dev, const unsigned long *regmap)
> +{
> +	DECLARE_BITMAP(read_regmap, D3323AA_REG_NR_BITS);
> +	struct d3323aa_data *data = iio_priv(indio_dev);
> +	unsigned long start_time;
> +	int ret;
> +
> +	ret = d3323aa_reset(indio_dev);
> +	if (ret) {
> +		if (ret != -ERESTARTSYS)
> +			dev_err(data->dev, "Could not reset device (%d)\n",
> +				ret);
> +
> +		return ret;
> +	}
> +
> +	/*
> +	 * Datasheet says to wait 10 us before setting the configuration.
> +	 * Moreover, the total configuration should be done within
> +	 * D3323AA_CONFIG_TIMEOUT ms. Clock it.
> +	 */
> +	usleep_range(10, 20);
> +	start_time = jiffies;
> +
> +	ret = d3323aa_write_settings(indio_dev, regmap);
> +	if (ret) {
> +		dev_err(data->dev, "Could not write settings (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	ret = d3323aa_read_settings(indio_dev, read_regmap);
> +	if (ret) {
> +		dev_err(data->dev, "Could not read settings (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	if (time_is_before_jiffies(start_time +
> +				   msecs_to_jiffies(D3323AA_CONFIG_TIMEOUT))) {
> +		dev_err(data->dev, "Could not set up configuration in time\n");
> +		return -EAGAIN;
> +	}
> +
> +	/* Check if settings were set successfully. */
> +	if (!bitmap_equal(regmap, read_regmap, D3323AA_REG_NR_BITS)) {
> +		dev_err(data->dev, "Settings data mismatch\n");
> +		return -EIO;
> +	}
> +
> +	/* Now in operational mode. */
> +	ret = gpiod_direction_input(data->gpiod_clk_vout);
> +	if (ret) {
> +		dev_err(data->dev,
> +			"Could not set GPIO clk-vout as input (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	ret = gpiod_direction_input(data->gpiod_data);
> +	if (ret) {
> +		dev_err(data->dev, "Could not set GPIO data as input (%d)\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	ret = request_irq(data->irq, d3323aa_irq_detection,
> +			  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,

Why both ways?  Add a comment.

> +			  dev_name(data->dev), indio_dev);
> +	if (ret) {
> +		dev_err(data->dev, "Could not request IRQ for detection (%d)\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	bitmap_copy(data->regmap, regmap, D3323AA_REG_NR_BITS);
> +	data->detecting = true;
> +
> +	dev_dbg(data->dev, "Setup done\n");
> +
> +	return 0;
> +}

> +
> +static int d3323aa_write_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan, int val,
> +			     int val2, long mask)
> +{
> +	struct d3323aa_data *data = iio_priv(indio_dev);
> +	DECLARE_BITMAP(regmap, D3323AA_REG_NR_BITS);
> +	int ret;
> +
> +	guard(mutex)(&data->regmap_lock);
> +
> +	bitmap_copy(regmap, data->regmap, D3323AA_REG_NR_BITS);
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
> +		/*
> +		 * We only allow to set the low-pass cutoff frequency, since
> +		 * that is the only way to unambigously choose the type of
> +		 * band-pass filter. For example, both filter type B and C have
> +		 * 0.3 Hz as high-pass cutoff frequency (see
> +		 * d3323aa_hp_filter_freq).

This is somewhat unintiutive userspace inteface.  I'd try and do the best
possible in response to a userspace request.  So if the lp frequency is
already set to one of the values that matches requested hp then use that.
If it's not then pick the one that has best match lp (so nearest) on
assumption userspace is probably about to update the lp side anyway.

Slight fiddlier code than you have here, but a better attempt to figure
out what the users is after than rejecting writes.

> +		 */
> +		return -EINVAL;
> +	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> +		ret = d3323aa_set_lp_filter_freq(regmap, val, val2);
> +		if (ret)
> +			return ret;
> +		break;
> +	case IIO_CHAN_INFO_HARDWAREGAIN:
> +		ret = d3323aa_set_filter_gain(regmap, val);
> +		if (ret)
> +			return ret;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return d3323aa_setup(indio_dev, regmap);

I'd bury this call in set_lp_filter() and set_fillter_gain() so they
actually do the setting.

> +}

> +
> +static int d3323aa_probe(struct platform_device *pdev)
> +{
> +	DECLARE_BITMAP(default_regmap, D3323AA_REG_NR_BITS);
> +	struct d3323aa_data *data;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
> +	if (!indio_dev)
> +		return dev_err_probe(&pdev->dev, -ENOMEM,
> +				     "Could not allocate iio device\n");
> +
> +	data = iio_priv(indio_dev);
> +	data->dev = &pdev->dev;
> +	platform_set_drvdata(pdev, indio_dev);

Should be unnecessary with the suggested change below.


> +
> +	init_completion(&data->reset_completion);
> +
> +	ret = devm_mutex_init(data->dev, &data->regmap_lock);
> +	if (ret)
> +		return dev_err_probe(data->dev, ret,
> +				     "Could not initialize mutex (%d)\n", ret);

As already pointed out in other review - look at what dev_err_probe()
does.

> +
> +	/* Request GPIOs. */

This sort of code structure comment adds nothing useful. We can see
you are requesting gpios from the code.  As such, all it can do is
become wrong in future if code is reorganised.  Hence drop the comment.

> +	data->gpiod_vdd = devm_gpiod_get(data->dev, "vdd", GPIOD_OUT_LOW);

This seems to be a supply, not a gpio.  Use the regulator framework.
Make sure to request exclusive use though so you can turn it on and off.
_get_exclusive() etc should ensure no one else is using it.


> +	if (IS_ERR(data->gpiod_vdd))
> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_vdd),
> +				     "Could not get GPIO vdd (%ld)\n",
> +				     PTR_ERR(data->gpiod_vdd));
> +
> +	data->gpiod_clk_vout =
> +		devm_gpiod_get(data->dev, "clk-vout", GPIOD_OUT_LOW);
> +	if (IS_ERR(data->gpiod_clk_vout))
> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_clk_vout),
> +				     "Could not get GPIO clk-vout (%ld)\n",
> +				     PTR_ERR(data->gpiod_clk_vout));
> +
> +	data->gpiod_data = devm_gpiod_get(data->dev, "data", GPIOD_OUT_LOW);
> +	if (IS_ERR(data->gpiod_data))
> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_data),
> +				     "Could not get GPIO data (%ld)\n",
> +				     PTR_ERR(data->gpiod_data));
> +
> +	ret = gpiod_to_irq(data->gpiod_clk_vout);
> +	if (ret < 0)
> +		return dev_err_probe(data->dev, ret, "Could not get IRQ (%d)\n",
> +				     ret);
> +
> +	data->irq = ret;
> +
> +	/* Do one setup with the default values. */
> +	bitmap_zero(default_regmap, D3323AA_REG_NR_BITS);
> +	d3323aa_set_threshold(default_regmap, D3323AA_THRESH_DEFAULT_VAL);
> +	d3323aa_set_filter_gain(default_regmap,
> +				D3323AA_FILTER_GAIN_DEFAULT_VAL);
> +	ret = d3323aa_setup(indio_dev, default_regmap);
> +	if (ret)
> +		return ret;
> +
> +	indio_dev->info = &d3323aa_info;
> +	indio_dev->name = D3323AA_DRV_NAME
> +	indio_dev->channels = d3323aa_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(d3323aa_channels);
> +
> +	ret = devm_iio_device_register(data->dev, indio_dev);
> +	if (ret)
> +		return dev_err_probe(data->dev, ret,
> +				     "Could not register iio device (%d)\n",
> +				     ret);
> +
> +	return 0;
> +}
> +
> +static void d3323aa_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct d3323aa_data *data = iio_priv(indio_dev);
> +
> +	if (data->detecting)
> +		free_irq(data->irq, indio_dev);

So this is an ordering mess.  You should never mix devm handling and
non devm handling - once something happens in probe that is non devm
you stop using devm_* from that point on.

The irq handling in here is complex, but not on fast paths. As such I'd
do devm_irq_request() and if you need to free it do so with devm_free_irq()
in the places where you immediately re request it.  However see above.
That should allow you to to drop this remove callabck.

> +}
> +
> +static const struct of_device_id d3323aa_of_match[] = {
> +	{
> +		.compatible = "nicera,d3323aa",
> +	},
> +	{}
	{ }

Is now standard format for these in IIO.

> +};
> +MODULE_DEVICE_TABLE(of, d3323aa_of_match);
> +
> +static struct platform_driver d3323aa_driver = {
> +	.probe = d3323aa_probe,
> +	.remove = d3323aa_remove,
> +	.driver = {
> +		.name = D3323AA_DRV_NAME,
> +		.of_match_table = d3323aa_of_match,
> +	},
> +};
> +module_platform_driver(d3323aa_driver);
> +
> +MODULE_AUTHOR("Waqar Hameed <waqar.hameed@axis.com>");
> +MODULE_DESCRIPTION("Nicera D3-323-AA PIR sensor driver");
> +MODULE_LICENSE("GPL");


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

* Re: [PATCH 0/3] Add driver for Nicera D3-323-AA PIR sensor
  2025-05-09 15:09 ` [PATCH 0/3] " Krzysztof Kozlowski
@ 2025-05-16 17:14   ` Waqar Hameed
  0 siblings, 0 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-05-16 17:14 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski, kernel, linux-kernel, linux-iio, devicetree

On Fri, May 09, 2025 at 17:09 +0200 Krzysztof Kozlowski <krzk@kernel.org> wrote:

> On 09/05/2025 17:03, Waqar Hameed wrote:
>> Nicera D3-323-AA is a PIR sensor for human detection. It has support for
>> raw data measurements and detection notification. The communication
>> protocol is custom made and therefore needs to be GPIO bit banged.
>> 
>> Previously, there has been an attempt to add a driver for this device
>> [1]. However, that driver was written for the wrong sub-system. `hwmon`
>
> So that's a v2. Mark your patches correctly.

I figured that since it was a complete rewrite (and from another
author), I'd start a new series. But I also understand your point.

To not confuse others, I'll mark the next one as V2 instead (if that's
fine with you).

>
>> is clearly not a suitable framework for a proximity device.
>> 
>> In this series, we add a driver for support for event notification for
>> detections through IIO (the more appropriate sub-system!). The various
>> settings have been mapped to existing `sysfs` ABIs in the IIO framework.
>> 
>> The public datasheet [2] is quite sparse. A more detailed version can be
>> obtained through the company.
>> 
>> [1] https://lore.kernel.org/lkml/20241212042412.702044-2-Hermes.Zhang@axis.com/
> Read the comments given in that review:
> https://lore.kernel.org/lkml/wy7nyg3cztixe5y5rg4kbsbbly32h547hwumwwvrfme4fdgsj5@znfpypleebrb/
>
> You repeated same mistakes, which means I did same review second time
> which is waste of my time.

I'm really sorry! I actually completely missed your response there. 

Thank you again for reviewing! I know it's a lot of work sometimes...

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

* Re: [PATCH 2/3] dt-bindings: iio: proximity: Add Nicera D3-323-AA PIR sensor
  2025-05-09 15:06   ` Krzysztof Kozlowski
@ 2025-05-16 17:15     ` Waqar Hameed
  2025-05-20  6:36       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 21+ messages in thread
From: Waqar Hameed @ 2025-05-16 17:15 UTC (permalink / raw)
  To: Krzysztof Koowski
  Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, kernel, linux-iio, devicetree,
	linux-kernel

On Fri, May 09, 2025 at 17:06 +0200 Krzysztof Kozlowski <krzk@kernel.org> wrote:

> On 09/05/2025 17:03, Waqar Hameed wrote:
>> Nicera D3-323-AA is a PIR sensor for human detection. It has support for
>> raw data measurements and detection notification. The communication
>> protocol is custom made and therefore needs to be GPIO bit banged.
>> 
>> Add devicetree bindings requiring the compatible string and the various
>> GPIOs needed for operation. Some of the GPIOs have multiple use-cases
>> depending on device state. Describe these thoroughly.
>
>
> Drop redundant parts of description. Describe the hardware. 

I'll reformulate and incorporate some information of the pins and how it
is used in the hardware.

> Entire last paragraph is pretty pointless.

I'll remove it then. (Some sub-system maintainers want a description of
what the patch does, in imperative form. But I can see why it might not
add any value when it comes to dt-bindings.)

>
>> +
>> +properties:
>> +  compatible:
>> +    const: nicera,d3323aa
>> +
>> +  vdd-gpio:
>> +    maxItems: 1
>> +    description:
>> +      GPIO for supply voltage (1.8 to 5.5 V).
>
> This is not how pins are represented in the kernel. Either you have here
> regulator (supply) or reset gpios. 

I'll change it to `vdd-supply`.

> Plus 'gpio' suffix is not valid, btw.

I actually `grep`ed before writing this to see if there were other
dt-bindings with this suffix. Because the GPIO framework supports both
`gpio` and `gpios` as suffixes (see `gpio_suffixes[]` in `gpiolib.c`).
However, since `-gpios` are clearly in majority, we should go for that.

[...]

>> +      This GPIO will be driven low by the driver in order to cut the supply and
>> +      reset the device (driving it then back to high to power it on).
>> +
>> +  clk-vout-gpio:
>
> No, for the similar reasons. Which pin is this?

This pin is a little weird actually. As described below, right after
power on, it is used as an interrupt to signal "ready for
configuration". Then, it used as a bit banged clock signal for
configuration. Finally, it is back as interrupt pin for threshold PIR
sensor detections.

So, I'm not really sure what to call this (just opted for what it's
called in the data sheet: "Vout (CLK)"). Just `clk-gpios` wouldn't be
correct either, right? Should we prefix it with the vendor `nicera,`? Or
any other suggestions?

>
>> +    maxItems: 1
>> +    description:
>> +      GPIO for clock and detection.
>> +      After reset, the device signals with two falling edges on this pin that it
>> +      is ready for configuration (within 1.2 s), which the driver listens for as
>> +      interrupts.
>> +      During configuration, it is used as clock for data reading and writing (on
>> +      data-gpio). The driver drives this pin with the frequency of 1 kHz (bit
>> +      banging).
>> +      After all this, the device is now in operational mode and signals on this
>> +      pin for any detections. The driver listens for this as interrupts.
>> +
>> +  data-gpio:
>
> There is no such pin.

You mean to change it to `data-gpios`? (There are some using that, e.g.
`sensirion,sht15.yaml`).

>
>> +    maxItems: 1
>> +    description:
>> +      GPIO for data reading and writing.
>> +      During configuration, configuration data will be written and read back
>> +      with bit banging (together with clk-vout-gpio as clock).
>> +      After this, during operational mode, the device will output serial data on
>> +      this GPIO. However, the driver currently doesn't read this.
>> +
>> +required:
>> +  - compatible
>> +  - vdd-gpio
>> +  - clk-vout-gpio
>> +  - data-gpio
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/gpio/gpio.h>
>
> So you included that header
>
>> +
>> +    proximity {
>> +        compatible = "nicera,d3323aa";
>> +        vdd-gpio = <&gpio 73 0>;
>> +        clk-vout-gpio = <&gpio 78 0>;
>> +        data-gpio = <&gpio 76 0>;
>
> But where are you using it?

True, I'll add `GPIO_ACTIVE_*` to the properties.

[...]


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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-11  7:57   ` Christophe JAILLET
@ 2025-05-16 17:16     ` Waqar Hameed
  0 siblings, 0 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-05-16 17:16 UTC (permalink / raw)
  To: Christophe JAILLET; +Cc: jic23, kernel, lars, linux-iio, linux-kernel

On Sun, May 11, 2025 at 09:57 +0200 Christophe JAILLET <christophe.jaillet@wanadoo.fr> wrote:

[...]

> Hi,

Hi Christophe! Thank you for your review!

>
> ...
>
>> +static int d3323aa_set_lp_filter_freq(unsigned long *regmap, const int val,
>> +				      int val2)
>> +{
>> +	size_t idx;
>> +
>> +	/* Truncate fractional part to one digit. */
>> +	val2 /= 100000;
>> +
>> +	for (idx = 0; idx < ARRAY_SIZE(d3323aa_lp_filter_freq); ++idx) {
>> +		int integer = d3323aa_lp_filter_freq[idx][0] /
>> +			      d3323aa_lp_filter_freq[idx][1];
>> +		int fract = d3323aa_lp_filter_freq[idx][0] %
>> +			    d3323aa_lp_filter_freq[idx][1];
>
> Missing newline.

Sure, I'll add one.

>
>> +		if (val == integer && val2 == fract)
>> +			break;
>> +	}
>> +
>> +	if (idx == ARRAY_SIZE(d3323aa_lp_filter_freq))
>> +		return -ERANGE;
>> +
>> +	bitmap_write(regmap, d3323aa_lp_filter_regval[idx],
>> +		     D3323AA_REG_BIT_FILSEL0, D3323AA_FILTER_TYPE_NR_BITS);
>> +
>> +	return 0;
>> +}
>
> ...
>
>> +static int d3323aa_probe(struct platform_device *pdev)
>> +{
>> +	DECLARE_BITMAP(default_regmap, D3323AA_REG_NR_BITS);
>> +	struct d3323aa_data *data;
>> +	struct iio_dev *indio_dev;
>> +	int ret;
>> +
>> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
>> +	if (!indio_dev)
>> +		return dev_err_probe(&pdev->dev, -ENOMEM,
>> +				     "Could not allocate iio device\n");
>> +
>> +	data = iio_priv(indio_dev);
>> +	data->dev = &pdev->dev;
>> +	platform_set_drvdata(pdev, indio_dev);
>> +
>> +	init_completion(&data->reset_completion);
>> +
>> +	ret = devm_mutex_init(data->dev, &data->regmap_lock);
>> +	if (ret)
>> +		return dev_err_probe(data->dev, ret,
>> +				     "Could not initialize mutex (%d)\n", ret);
>
> No need to repeat the error code in the message, when using dev_err_probe().
>
> Same for all calls below.

Ah, of course! I'll remove it. (There is actually another driver that is
doing this, I'll update that one as well...)

>
>> +
>> +	/* Request GPIOs. */
>> +	data->gpiod_vdd = devm_gpiod_get(data->dev, "vdd", GPIOD_OUT_LOW);
>> +	if (IS_ERR(data->gpiod_vdd))
>> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_vdd),
>> +				     "Could not get GPIO vdd (%ld)\n",
>> +				     PTR_ERR(data->gpiod_vdd));
>> +
>> +	data->gpiod_clk_vout =
>> +		devm_gpiod_get(data->dev, "clk-vout", GPIOD_OUT_LOW);
>> +	if (IS_ERR(data->gpiod_clk_vout))
>> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_clk_vout),
>> +				     "Could not get GPIO clk-vout (%ld)\n",
>> +				     PTR_ERR(data->gpiod_clk_vout));
>> +
>> +	data->gpiod_data = devm_gpiod_get(data->dev, "data", GPIOD_OUT_LOW);
>> +	if (IS_ERR(data->gpiod_data))
>> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_data),
>> +				     "Could not get GPIO data (%ld)\n",
>> +				     PTR_ERR(data->gpiod_data));
>> +
>> +	ret = gpiod_to_irq(data->gpiod_clk_vout);
>> +	if (ret < 0)
>> +		return dev_err_probe(data->dev, ret, "Could not get IRQ (%d)\n",
>> +				     ret);
>> +
>> +	data->irq = ret;
>> +
>> +	/* Do one setup with the default values. */
>> +	bitmap_zero(default_regmap, D3323AA_REG_NR_BITS);
>> +	d3323aa_set_threshold(default_regmap, D3323AA_THRESH_DEFAULT_VAL);
>> +	d3323aa_set_filter_gain(default_regmap,
>> +				D3323AA_FILTER_GAIN_DEFAULT_VAL);
>> +	ret = d3323aa_setup(indio_dev, default_regmap);
>> +	if (ret)
>> +		return ret;
>> +
>> +	indio_dev->info = &d3323aa_info;
>> +	indio_dev->name = D3323AA_DRV_NAME;
>> +	indio_dev->channels = d3323aa_channels;
>> +	indio_dev->num_channels = ARRAY_SIZE(d3323aa_channels);
>> +
>> +	ret = devm_iio_device_register(data->dev, indio_dev);
>> +	if (ret)
>> +		return dev_err_probe(data->dev, ret,
>> +				     "Could not register iio device (%d)\n",
>> +				     ret);
>> +
>> +	return 0;
>> +}
>
> ...
>
> CJ

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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-11 12:14   ` Jonathan Cameron
@ 2025-05-16 17:16     ` Waqar Hameed
  2025-05-18 17:38       ` Jonathan Cameron
  2025-06-14 22:05       ` Waqar Hameed
  0 siblings, 2 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-05-16 17:16 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio

On Sun, May 11, 2025 at 13:14 +0100 Jonathan Cameron <jic23@kernel.org> wrote:

[...]

> Hi Waqar,
>
> Biggest suggestion in here is hide those bitmaps. They are of interest
> only at point of reading and writing. I wouldn't use them for device state
> elsewhere as they make for much less readable code than using simple
> fields in iio_priv() for the state elements and building up the bitmaps
> only where needed.

Hi Jonathan!

Thank you for thorough (as usual) review!

[...]

>> +#define D3323AA_DRV_NAME "d3323aa"
>
> Put that inline where used.  A define like this both implies that various values
> must be the same when they need not be and means that we have to go find the
> define to fine out what they are set to.  Just setting the strings directly
> tends to end up more readable.

Sure, we can do that. (There are a bunch of IIO-drivers doing this, so I
just thought that was the "convention".)

>> +
>> +/*
>> + * Register bitmap.
>> + * For some reason the first bit is denoted as F37 in the datasheet, the second
>> + * as F38 and so on. Note the gap between F60 and F64.
>> + */
>> +#define D3323AA_REG_BIT_SLAVEA1		0	/* F37. */
>> +#define D3323AA_REG_BIT_SLAVEA2		1	/* F38. */
>> +#define D3323AA_REG_BIT_SLAVEA3		2	/* F39. */
>> +#define D3323AA_REG_BIT_SLAVEA4		3	/* F40. */
>> +#define D3323AA_REG_BIT_SLAVEA5		4	/* F41. */
>> +#define D3323AA_REG_BIT_SLAVEA6		5	/* F42. */
>> +#define D3323AA_REG_BIT_SLAVEA7		6	/* F43. */
>> +#define D3323AA_REG_BIT_SLAVEA8		7	/* F44. */
>> +#define D3323AA_REG_BIT_SLAVEA9		8	/* F45. */
> Perhaps these can be represented as masks using GENMASK() rather than
> bits.  A lot of this will be hidden away if you follow suggesting to
> only expose that you are using a bitmap to bitbang in the read/write
> functions.

Yes, that would be the natural thing to do when moving the bitmap stuff
to the read/write functions (as answered below).

[...]

>> +/*
>> + * This is denoted as "step" in datasheet and corresponds to the gain at peak
>> + * for the band-pass filter. The index in this array is the corresponding index
>> + * in d3323aa_filter_gain_regval for the register bitmap value.
>> + */
>> +static const int d3323aa_filter_gain[] = {
>> +	1,
>> +	2,
>> +	3,
>> +};
> 	1, 2, 3,
> is fine for such a short list.

Sure, I'll change the corresponding array below as well then.

>> +
>> +/*
>> + * Register bitmap values for the filter gain. The index in this array is the
>> + * corresponding index in d3323aa_filter_gain for the gain value.
>> + */
>> +static const u8 d3323aa_filter_gain_regval[] = {
>> +	1,
>> +	3,
>> +	0,
>> +};
>> +
>> +struct d3323aa_data {
>> +	struct completion reset_completion;
>> +	/*
>> +	 *  Since the setup process always requires a complete write of the
>> +	 *  _whole_ register bitmap, we need to synchronize it with a lock.
>> +	 */
>> +	struct mutex regmap_lock;
>> +	atomic_t irq_reset_count;
>> +	unsigned int irq;
>> +
>> +	struct device *dev;
>> +
>> +	/* Supply voltage. */
>> +	struct gpio_desc *gpiod_vdd;
>> +	/* Input clock or output detection signal (Vout). */
>
> I'd rename. Vout kind of suggests a variable voltage. This seems to just
> be a level signal.

>> +	struct gpio_desc *gpiod_clk_vout;

Yeah, it's a weird pin with multiple use-cases... I just named it
according to what the datasheet calls it. What about
`gpiod_clk_detection`?

>> +	/* Input (setting) or output data. */
>> +	struct gpio_desc *gpiod_data;
>> +
>> +	DECLARE_BITMAP(regmap, D3323AA_REG_NR_BITS);
>> +
>> +	/*
>> +	 * Indicator for operational mode (configuring or detecting), i.e.
>> +	 * d3323aa_irq_detection() registered or not.
>> +	 */
>> +	bool detecting;
>> +};
>> +
>> +static int d3323aa_read_settings(struct iio_dev *indio_dev,
>> +				 unsigned long *regmap)
>> +{
>> +	struct d3323aa_data *data = iio_priv(indio_dev);
>> +	size_t i;
>> +	int ret;
>> +
>> +	/* Bit bang the clock and data pins. */
>> +	ret = gpiod_direction_output(data->gpiod_clk_vout, 0);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = gpiod_direction_input(data->gpiod_data);
>> +	if (ret)
>> +		return ret;
>> +
>> +	dev_dbg(data->dev, "Reading settings...\n");
>> +
>> +	for (i = 0; i < D3323AA_REG_NR_BITS; ++i) {
>> +		/* Clock frequency needs to be 1 kHz. */
>> +		gpiod_set_value(data->gpiod_clk_vout, 1);
>> +		udelay(500);
>> +
>> +		/* The data seems to change when clock signal is high. */
>> +		if (gpiod_get_value(data->gpiod_data))
>> +			set_bit(i, regmap);
>> +
>> +		gpiod_set_value(data->gpiod_clk_vout, 0);
>> +		udelay(500);
>> +	}
>> +
>> +	/* The first bit (F37) is just dummy data. Discard it. */
>> +	clear_bit(0, regmap);
>> +
>> +	/* Datasheet says to wait 30 ms after reading the settings. */
>> +	msleep(30);
>> +
>> +	return 0;
>> +}
>> +
>> +static int d3323aa_write_settings(struct iio_dev *indio_dev,
>> +				  const unsigned long *regmap)
>
> Rename regmap. regmap means some specific stuff in the kernel register_bm or something
> like that avoids that potential confusion.
>
> However, it would be much easier to read this driver if only this
> function and the read one knew about the bitmap stuff.  Inside the reset
> of the driver just store and pass around a structure with the various fields.
> Then in here use that to build up the bitmap locally and write to the device.
> The opposite in read which decodes the bitmap into those fields.
>
> That will make for a more standard and easier to review driver.

Hm, maybe it would be easier to understand it if we remove the bitmap
from `struct d3323aa_data` and instead only store the relevant state
variables. We still would need to have the `regmap_lock` but to instead
protect all of the variables.

Let's go for that then! I actually don't have any strong arguments (or
opinions) against it.

>> +{
>> +	DECLARE_BITMAP(end_pattern, D3323AA_SETTING_END_PATTERN_NR_BITS);
>> +	struct d3323aa_data *data = iio_priv(indio_dev);
>> +	size_t i;
>> +	int ret;
>> +
>> +	/* Bit bang the clock and data pins. */
>> +	ret = gpiod_direction_output(data->gpiod_clk_vout, 0);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = gpiod_direction_output(data->gpiod_data, 0);
>> +	if (ret)
>> +		return ret;
>> +
>> +	dev_dbg(data->dev, "Writing settings...\n");
>> +
>> +	/* First bit (F37) is not used when writing the register map. */
>> +	for (i = 1; i < D3323AA_REG_NR_BITS; ++i) {
>> +		gpiod_set_value(data->gpiod_data, test_bit(i, regmap));
>> +
>> +		/* Clock frequency needs to be 1 kHz. */
>> +		gpiod_set_value(data->gpiod_clk_vout, 1);
>> +		udelay(500);
>> +		gpiod_set_value(data->gpiod_clk_vout, 0);
>> +		udelay(500);
>> +	}
>> +
>> +	/* Compulsory end pattern. */
>> +	bitmap_write(end_pattern, D3323AA_SETTING_END_PATTERN, 0,
>> +		     D3323AA_SETTING_END_PATTERN_NR_BITS);
>  
> Why not use a larger bitmap with padding for this end pattern
> and just write this into that?  At which point this can all be one loop.

Will do! It will be natural to do that after moving the bitmap stuff and
only scope it here.

>> +	for (i = 0; i < D3323AA_SETTING_END_PATTERN_NR_BITS; ++i) {
>> +		gpiod_set_value(data->gpiod_data, test_bit(i, end_pattern));
>> +
>> +		gpiod_set_value(data->gpiod_clk_vout, 1);
>> +		udelay(500);
>> +		gpiod_set_value(data->gpiod_clk_vout, 0);
>> +		udelay(500);
>> +	}
>> +
>> +	/* Datasheet says to wait 30 ms after writing the settings. */
>> +	msleep(30);
>> +
>> +	return 0;
>> +}
>> +
>> +static irqreturn_t d3323aa_irq_detection(int irq, void *dev_id)
>> +{
>> +	struct iio_dev *indio_dev = dev_id;
>> +	struct d3323aa_data *data = iio_priv(indio_dev);
>> +	enum iio_event_direction dir;
>> +	int val;
>> +
>> +	val = gpiod_get_value(data->gpiod_clk_vout);
>
> Ideally I'd like a setup where we can wire the interrupt side of this
> to one pin and the gpio needed for writing to another.  In practice
> they may well be the same pin but that does introduced a bunch of races
> and dependency on what the interrupt controller does when an irq
> is disabled.
>
> Using one pin as an irq and as a data line is prone to some nasty
> corner cases though it works on some SoCs.

Hm, that's an interesting idea. However, as you say there might then be
other issues to take care of.

On the other hand, the scope of the clock bit bang is quite small (only in
`*read/write_settings()`). So I guess we just leave it then?

>
>
>> +	if (val < 0) {
>> +		dev_err_ratelimited(data->dev,
>> +				    "Could not read from GPIO clk-vout (%d)\n",
>> +				    val);
>> +		return IRQ_HANDLED;
>> +	}
>> +
>> +	dir = val ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
>> +	iio_push_event(indio_dev,
>> +		       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
>> +					    IIO_EV_TYPE_THRESH, dir),
>> +		       iio_get_time_ns(indio_dev));
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t d3323aa_irq_reset(int irq, void *dev_id)
>> +{
>> +	struct iio_dev *indio_dev = dev_id;
>> +	struct d3323aa_data *data = iio_priv(indio_dev);
>> +	int count = atomic_inc_return(&data->irq_reset_count);
>> +
>
> As below. It should be easy enough to have a unified handler and avoid
> the need to unregister / reregister the irq.

Alright (see longer response below).

>> +	if (count == D3323AA_IRQ_RESET_COUNT)
>> +		complete(&data->reset_completion);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int d3323aa_reset(struct iio_dev *indio_dev)
>> +{
>> +	struct d3323aa_data *data = iio_priv(indio_dev);
>> +	long time;
>> +	int ret;
>> +
>> +	/*
>> +	 * Datasheet says VDD needs to be low at least for 30 ms. Let's add a
>> +	 * couple more to allow VDD to completely discharge as well.
>> +	 */
>> +	gpiod_set_value(data->gpiod_vdd, 0);
>> +	msleep(30 + 5);
>> +
>> +	/*
>> +	 * After setting VDD to high, the device signals with
>> +	 * D3323AA_IRQ_RESET_COUNT falling edges on CLK/Vout that it is now
>> +	 * ready for configuration. Datasheet says that this should happen
>> +	 * within D3323AA_RESET_TIMEOUT ms. Count these two edges within that
>> +	 * timeout.
>> +	 */
>> +	atomic_set(&data->irq_reset_count, 0);
>> +	reinit_completion(&data->reset_completion);
>> +
>> +	ret = gpiod_direction_input(data->gpiod_clk_vout);
>> +	if (ret)
>> +		return ret;
>> +
>> +	dev_dbg(data->dev, "Resetting...\n");
>> +
>> +	gpiod_set_value(data->gpiod_vdd, 1);
>> +
>> +	/*
>> +	 * Datasheet doesn't mention this but measurements have shown that
>> +	 * CLK/Vout signal slowly ramps up during the first 1.5 ms after reset.
>> +	 * This means that the digital signal will have bogus values during this
>> +	 * period. Let's wait for this ramp-up before counting the "real"
>> +	 * falling edges.
>> +	 */
>> +	usleep_range(2000, 5000);
>> +
>> +	if (data->detecting) {
>> +		/*
>> +		 * Device had previously been set up and was in operational
>> +		 * mode. Thus, free that detection IRQ handler before requesting
>> +		 * the reset IRQ handler.
>> +		 */
>> +		free_irq(data->irq, indio_dev);
>> +		data->detecting = false;
>> +	}
>> +
>> +	ret = request_irq(data->irq, d3323aa_irq_reset, IRQF_TRIGGER_FALLING,
>> +			  dev_name(data->dev), indio_dev);
>
> Can you keep to a single handler with a check on device state?  This
> dance with releasing and requesting irqs is rather complex when all you need
> to know is what the irq means and that is state the driver knows.

We can do that. Ironically, I opted for separating them just for the
sake of simplicity, and thought it would be easier to understand this
way :)

>> +	if (ret)
>> +		return ret;
>> +
>> +	time = wait_for_completion_killable_timeout(
>> +		&data->reset_completion,
>> +		msecs_to_jiffies(D3323AA_RESET_TIMEOUT));
>> +	free_irq(data->irq, indio_dev);
>> +	if (time == 0) {
>> +		return -ETIMEDOUT;
>> +	} else if (time < 0) {
>> +		/* Got interrupted. */
>> +		return time;
>> +	}
>> +
>> +	dev_dbg(data->dev, "Reset completed\n");
>> +
>> +	return 0;
>> +}
>> +
>> +static int d3323aa_setup(struct iio_dev *indio_dev, const unsigned long *regmap)
>> +{
>> +	DECLARE_BITMAP(read_regmap, D3323AA_REG_NR_BITS);
>> +	struct d3323aa_data *data = iio_priv(indio_dev);
>> +	unsigned long start_time;
>> +	int ret;
>> +
>> +	ret = d3323aa_reset(indio_dev);
>> +	if (ret) {
>> +		if (ret != -ERESTARTSYS)
>> +			dev_err(data->dev, "Could not reset device (%d)\n",
>> +				ret);
>> +
>> +		return ret;
>> +	}
>> +
>> +	/*
>> +	 * Datasheet says to wait 10 us before setting the configuration.
>> +	 * Moreover, the total configuration should be done within
>> +	 * D3323AA_CONFIG_TIMEOUT ms. Clock it.
>> +	 */
>> +	usleep_range(10, 20);
>> +	start_time = jiffies;
>> +
>> +	ret = d3323aa_write_settings(indio_dev, regmap);
>> +	if (ret) {
>> +		dev_err(data->dev, "Could not write settings (%d)\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = d3323aa_read_settings(indio_dev, read_regmap);
>> +	if (ret) {
>> +		dev_err(data->dev, "Could not read settings (%d)\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	if (time_is_before_jiffies(start_time +
>> +				   msecs_to_jiffies(D3323AA_CONFIG_TIMEOUT))) {
>> +		dev_err(data->dev, "Could not set up configuration in time\n");
>> +		return -EAGAIN;
>> +	}
>> +
>> +	/* Check if settings were set successfully. */
>> +	if (!bitmap_equal(regmap, read_regmap, D3323AA_REG_NR_BITS)) {
>> +		dev_err(data->dev, "Settings data mismatch\n");
>> +		return -EIO;
>> +	}
>> +
>> +	/* Now in operational mode. */
>> +	ret = gpiod_direction_input(data->gpiod_clk_vout);
>> +	if (ret) {
>> +		dev_err(data->dev,
>> +			"Could not set GPIO clk-vout as input (%d)\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = gpiod_direction_input(data->gpiod_data);
>> +	if (ret) {
>> +		dev_err(data->dev, "Could not set GPIO data as input (%d)\n",
>> +			ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = request_irq(data->irq, d3323aa_irq_detection,
>> +			  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
>
> Why both ways?  Add a comment.

Good point! I'll add a comment.

>
>> +			  dev_name(data->dev), indio_dev);
>> +	if (ret) {
>> +		dev_err(data->dev, "Could not request IRQ for detection (%d)\n",
>> +			ret);
>> +		return ret;
>> +	}
>> +
>> +	bitmap_copy(data->regmap, regmap, D3323AA_REG_NR_BITS);
>> +	data->detecting = true;
>> +
>> +	dev_dbg(data->dev, "Setup done\n");
>> +
>> +	return 0;
>> +}
>
>> +
>> +static int d3323aa_write_raw(struct iio_dev *indio_dev,
>> +			     struct iio_chan_spec const *chan, int val,
>> +			     int val2, long mask)
>> +{
>> +	struct d3323aa_data *data = iio_priv(indio_dev);
>> +	DECLARE_BITMAP(regmap, D3323AA_REG_NR_BITS);
>> +	int ret;
>> +
>> +	guard(mutex)(&data->regmap_lock);
>> +
>> +	bitmap_copy(regmap, data->regmap, D3323AA_REG_NR_BITS);
>> +
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
>> +		/*
>> +		 * We only allow to set the low-pass cutoff frequency, since
>> +		 * that is the only way to unambigously choose the type of
>> +		 * band-pass filter. For example, both filter type B and C have
>> +		 * 0.3 Hz as high-pass cutoff frequency (see
>> +		 * d3323aa_hp_filter_freq).
>
> This is somewhat unintiutive userspace inteface.  I'd try and do the best
> possible in response to a userspace request.  So if the lp frequency is
> already set to one of the values that matches requested hp then use that.
> If it's not then pick the one that has best match lp (so nearest) on
> assumption userspace is probably about to update the lp side anyway.
>
> Slight fiddlier code than you have here, but a better attempt to figure
> out what the users is after than rejecting writes.

To be honest, I wasn't super keen on doing this either and figured you
would have some suggestions on it. It was the easiest way to just set a
policy instead :)

Your suggestion is reasonable. Let's do that!

>> +		 */
>> +		return -EINVAL;
>> +	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
>> +		ret = d3323aa_set_lp_filter_freq(regmap, val, val2);
>> +		if (ret)
>> +			return ret;
>> +		break;
>> +	case IIO_CHAN_INFO_HARDWAREGAIN:
>> +		ret = d3323aa_set_filter_gain(regmap, val);
>> +		if (ret)
>> +			return ret;
>> +		break;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +
>> +	return d3323aa_setup(indio_dev, regmap);
>
> I'd bury this call in set_lp_filter() and set_fillter_gain() so they
> actually do the setting.

Alright, let's do that then.

>
>> +}
>
>> +
>> +static int d3323aa_probe(struct platform_device *pdev)
>> +{
>> +	DECLARE_BITMAP(default_regmap, D3323AA_REG_NR_BITS);
>> +	struct d3323aa_data *data;
>> +	struct iio_dev *indio_dev;
>> +	int ret;
>> +
>> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
>> +	if (!indio_dev)
>> +		return dev_err_probe(&pdev->dev, -ENOMEM,
>> +				     "Could not allocate iio device\n");
>> +
>> +	data = iio_priv(indio_dev);
>> +	data->dev = &pdev->dev;
>> +	platform_set_drvdata(pdev, indio_dev);
>
> Should be unnecessary with the suggested change below.

Yup, will be removed.

>
>> +
>> +	init_completion(&data->reset_completion);
>> +
>> +	ret = devm_mutex_init(data->dev, &data->regmap_lock);
>> +	if (ret)
>> +		return dev_err_probe(data->dev, ret,
>> +				     "Could not initialize mutex (%d)\n", ret);
>
> As already pointed out in other review - look at what dev_err_probe()
> does.

Yeah, will fix!

>
>> +
>> +	/* Request GPIOs. */
>
> This sort of code structure comment adds nothing useful. We can see
> you are requesting gpios from the code.  As such, all it can do is
> become wrong in future if code is reorganised.  Hence drop the comment.

Sure, let's drop it.

>> +	data->gpiod_vdd = devm_gpiod_get(data->dev, "vdd", GPIOD_OUT_LOW);
>
> This seems to be a supply, not a gpio.  Use the regulator framework.
> Make sure to request exclusive use though so you can turn it on and off.
> _get_exclusive() etc should ensure no one else is using it.

Alright, will do!

>> +	if (IS_ERR(data->gpiod_vdd))
>> +		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_vdd),
>> +				     "Could not get GPIO vdd (%ld)\n",
>> +				     PTR_ERR(data->gpiod_vdd));
>> +

[...]

>> +static void d3323aa_remove(struct platform_device *pdev)
>> +{
>> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> +	struct d3323aa_data *data = iio_priv(indio_dev);
>> +
>> +	if (data->detecting)
>> +		free_irq(data->irq, indio_dev);
>
> So this is an ordering mess.  You should never mix devm handling and
> non devm handling - once something happens in probe that is non devm
> you stop using devm_* from that point on.
>
> The irq handling in here is complex, but not on fast paths. As such I'd
> do devm_irq_request() and if you need to free it do so with devm_free_irq()
> in the places where you immediately re request it.  However see above.
> That should allow you to to drop this remove callabck.

Yes, we'll merge the IRQ handlers into one and thus we'll drop this
altogether.

>> +}
>> +
>> +static const struct of_device_id d3323aa_of_match[] = {
>> +	{
>> +		.compatible = "nicera,d3323aa",
>> +	},
>> +	{}
> 	{ }
>
> Is now standard format for these in IIO.
>
>> +};
>> +MODULE_DEVICE_TABLE(of, d3323aa_of_match);

Oh ok. I'll change it to:

static const struct of_device_id d3323aa_of_match[] = {
	{ .compatible = "nicera,d3323aa", },
	{ }
};

If that's more according to the style (I was relying on `clang-format`
here).

[...]


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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-16 17:16     ` Waqar Hameed
@ 2025-05-18 17:38       ` Jonathan Cameron
  2025-05-20 11:27         ` Waqar Hameed
  2025-06-14 22:05       ` Waqar Hameed
  1 sibling, 1 reply; 21+ messages in thread
From: Jonathan Cameron @ 2025-05-18 17:38 UTC (permalink / raw)
  To: Waqar Hameed; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio


> >> +#define D3323AA_DRV_NAME "d3323aa"  
> >
> > Put that inline where used.  A define like this both implies that various values
> > must be the same when they need not be and means that we have to go find the
> > define to fine out what they are set to.  Just setting the strings directly
> > tends to end up more readable.  
> 
> Sure, we can do that. (There are a bunch of IIO-drivers doing this, so I
> just thought that was the "convention".)

I'm sometimes in less fussy mood.  One day I might just clean those up
so there is nothing to copy into new drivers!

>
> >> +	/* Input clock or output detection signal (Vout). */  
> >
> > I'd rename. Vout kind of suggests a variable voltage. This seems to just
> > be a level signal.  
> 
> >> +	struct gpio_desc *gpiod_clk_vout;  
> 
> Yeah, it's a weird pin with multiple use-cases... I just named it
> according to what the datasheet calls it. What about
> `gpiod_clk_detection`?

That sounds like it's detecting a clock.  Hmm.  
gpiod_clkin_detectout maybe?

> >> +static int d3323aa_write_settings(struct iio_dev *indio_dev,
> >> +				  const unsigned long *regmap)  
> >
> > Rename regmap. regmap means some specific stuff in the kernel register_bm or something
> > like that avoids that potential confusion.
> >
> > However, it would be much easier to read this driver if only this
> > function and the read one knew about the bitmap stuff.  Inside the reset
> > of the driver just store and pass around a structure with the various fields.
> > Then in here use that to build up the bitmap locally and write to the device.
> > The opposite in read which decodes the bitmap into those fields.
> >
> > That will make for a more standard and easier to review driver.  
> 
> Hm, maybe it would be easier to understand it if we remove the bitmap
> from `struct d3323aa_data` and instead only store the relevant state
> variables. We still would need to have the `regmap_lock` but to instead
> protect all of the variables.
> 
> Let's go for that then! I actually don't have any strong arguments (or
> opinions) against it.

Just don't use the term regmap and that all sounds fine as it won't
cause confusion against the stuff in drivers/base/regmap/


> >> +static irqreturn_t d3323aa_irq_detection(int irq, void *dev_id)
> >> +{
> >> +	struct iio_dev *indio_dev = dev_id;
> >> +	struct d3323aa_data *data = iio_priv(indio_dev);
> >> +	enum iio_event_direction dir;
> >> +	int val;
> >> +
> >> +	val = gpiod_get_value(data->gpiod_clk_vout);  
> >
> > Ideally I'd like a setup where we can wire the interrupt side of this
> > to one pin and the gpio needed for writing to another.  In practice
> > they may well be the same pin but that does introduced a bunch of races
> > and dependency on what the interrupt controller does when an irq
> > is disabled.
> >
> > Using one pin as an irq and as a data line is prone to some nasty
> > corner cases though it works on some SoCs.  
> 
> Hm, that's an interesting idea. However, as you say there might then be
> other issues to take care of.
> 
> On the other hand, the scope of the clock bit bang is quite small (only in
> `*read/write_settings()`). So I guess we just leave it then?

I'd give a very high chance that if the driver sees much use you will get
bug reports as we've seen for ADCs that do similar multiple uses of the
same line (one of which is as an interrupt).

Here we will probably just see spurious interrupts which will be mostly
harmless.  So fine as it stands.




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

* Re: [PATCH 2/3] dt-bindings: iio: proximity: Add Nicera D3-323-AA PIR sensor
  2025-05-16 17:15     ` Waqar Hameed
@ 2025-05-20  6:36       ` Krzysztof Kozlowski
  2025-05-20 12:00         ` Waqar Hameed
  0 siblings, 1 reply; 21+ messages in thread
From: Krzysztof Kozlowski @ 2025-05-20  6:36 UTC (permalink / raw)
  To: Waqar Hameed
  Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, kernel, linux-iio, devicetree,
	linux-kernel

On 16/05/2025 19:15, Waqar Hameed wrote:
> On Fri, May 09, 2025 at 17:06 +0200 Krzysztof Kozlowski <krzk@kernel.org> wrote:
> 
>> On 09/05/2025 17:03, Waqar Hameed wrote:
>>> Nicera D3-323-AA is a PIR sensor for human detection. It has support for
>>> raw data measurements and detection notification. The communication
>>> protocol is custom made and therefore needs to be GPIO bit banged.
>>>
>>> Add devicetree bindings requiring the compatible string and the various
>>> GPIOs needed for operation. Some of the GPIOs have multiple use-cases
>>> depending on device state. Describe these thoroughly.
>>
>>
>> Drop redundant parts of description. Describe the hardware. 
> 
> I'll reformulate and incorporate some information of the pins and how it
> is used in the hardware.
> 
>> Entire last paragraph is pretty pointless.
> 
> I'll remove it then. (Some sub-system maintainers want a description of
> what the patch does, in imperative form. But I can see why it might not
> add any value when it comes to dt-bindings.)
> 
>>
>>> +
>>> +properties:
>>> +  compatible:
>>> +    const: nicera,d3323aa
>>> +
>>> +  vdd-gpio:
>>> +    maxItems: 1
>>> +    description:
>>> +      GPIO for supply voltage (1.8 to 5.5 V).
>>
>> This is not how pins are represented in the kernel. Either you have here
>> regulator (supply) or reset gpios. 
> 
> I'll change it to `vdd-supply`.
> 
>> Plus 'gpio' suffix is not valid, btw.
> 
> I actually `grep`ed before writing this to see if there were other
> dt-bindings with this suffix. Because the GPIO framework supports both
> `gpio` and `gpios` as suffixes (see `gpio_suffixes[]` in `gpiolib.c`).
> However, since `-gpios` are clearly in majority, we should go for that.


One is deprecated. It is always, always gpios.

> 
> [...]
> 
>>> +      This GPIO will be driven low by the driver in order to cut the supply and
>>> +      reset the device (driving it then back to high to power it on).
>>> +
>>> +  clk-vout-gpio:
>>
>> No, for the similar reasons. Which pin is this?
> 
> This pin is a little weird actually. As described below, right after
> power on, it is used as an interrupt to signal "ready for
> configuration". Then, it used as a bit banged clock signal for
> configuration. Finally, it is back as interrupt pin for threshold PIR
> sensor detections.
> 
> So, I'm not really sure what to call this (just opted for what it's
> called in the data sheet: "Vout (CLK)"). Just `clk-gpios` wouldn't be
> correct either, right? Should we prefix it with the vendor `nicera,`? Or
> any other suggestions?

Call it by the name of pin, so vout-clk-gpios. I guess from SoC/CPU side
this cannot be anything else than GPIO.

> 
>>
>>> +    maxItems: 1
>>> +    description:
>>> +      GPIO for clock and detection.
>>> +      After reset, the device signals with two falling edges on this pin that it
>>> +      is ready for configuration (within 1.2 s), which the driver listens for as
>>> +      interrupts.
>>> +      During configuration, it is used as clock for data reading and writing (on
>>> +      data-gpio). The driver drives this pin with the frequency of 1 kHz (bit
>>> +      banging).
>>> +      After all this, the device is now in operational mode and signals on this
>>> +      pin for any detections. The driver listens for this as interrupts.
>>> +
>>> +  data-gpio:
>>
>> There is no such pin.
> 
> You mean to change it to `data-gpios`? (There are some using that, e.g.
> `sensirion,sht15.yaml`).

No, I meant I opened datasheet and found no such pin. Unless you meant
DO, but then mention in description the actual name of the pin, if you
are using some more descriptive property name for it.

> 
>>
>>> +    maxItems: 1
>>> +    description:
>>> +      GPIO for data reading and writing.
>>> +      During configuration, configuration data will be written and read back
>>> +      with bit banging (together with clk-vout-gpio as clock).
>>> +      After this, during operational mode, the device will output serial data on
>>> +      this GPIO. However, the driver currently doesn't read this.

BTW, drop all references to driver here and in other places. If driver
change, you will change binding? If my driver behaves differently, why
binding would be claiming something else?


Best regards,
Krzysztof

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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-18 17:38       ` Jonathan Cameron
@ 2025-05-20 11:27         ` Waqar Hameed
  2025-05-25  9:30           ` Jonathan Cameron
  0 siblings, 1 reply; 21+ messages in thread
From: Waqar Hameed @ 2025-05-20 11:27 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio

On Sun, May 18, 2025 at 18:38 +0100 Jonathan Cameron <jic23@kernel.org> wrote:

>> >> +#define D3323AA_DRV_NAME "d3323aa"  
>> >
>> > Put that inline where used.  A define like this both implies that various values
>> > must be the same when they need not be and means that we have to go find the
>> > define to fine out what they are set to.  Just setting the strings directly
>> > tends to end up more readable.  
>> 
>> Sure, we can do that. (There are a bunch of IIO-drivers doing this, so I
>> just thought that was the "convention".)
>
> I'm sometimes in less fussy mood.  One day I might just clean those up
> so there is nothing to copy into new drivers!

A quick search tells that there are (at least) 105 of those:

  rgrep -A 30 "\.driver" drivers/iio/ | grep "\.name" | grep -v '"'
  
I was just about to write a small Python script to fix those, but just
wanted to confirm with you before spending more time on this. So if you
don't want to do this yourself, I can help your here :)

>>
>> >> +	/* Input clock or output detection signal (Vout). */  
>> >
>> > I'd rename. Vout kind of suggests a variable voltage. This seems to just
>> > be a level signal.  
>> 
>> >> +	struct gpio_desc *gpiod_clk_vout;  
>> 
>> Yeah, it's a weird pin with multiple use-cases... I just named it
>> according to what the datasheet calls it. What about
>> `gpiod_clk_detection`?
>
> That sounds like it's detecting a clock.  Hmm.  
> gpiod_clkin_detectout maybe?

No objections.

[...]


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

* Re: [PATCH 2/3] dt-bindings: iio: proximity: Add Nicera D3-323-AA PIR sensor
  2025-05-20  6:36       ` Krzysztof Kozlowski
@ 2025-05-20 12:00         ` Waqar Hameed
  0 siblings, 0 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-05-20 12:00 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, kernel, linux-iio, devicetree,
	linux-kernel

On Tue, May 20, 2025 at 08:36 +0200 Krzysztof Kozlowski <krzk@kernel.org> wrote:

> On 16/05/2025 19:15, Waqar Hameed wrote:
>> On Fri, May 09, 2025 at 17:06 +0200 Krzysztof Kozlowski <krzk@kernel.org> wrote:
>> 
>>> On 09/05/2025 17:03, Waqar Hameed wrote:
>>>> Nicera D3-323-AA is a PIR sensor for human detection. It has support for
>>>> raw data measurements and detection notification. The communication
>>>> protocol is custom made and therefore needs to be GPIO bit banged.
>>>>
>>>> Add devicetree bindings requiring the compatible string and the various
>>>> GPIOs needed for operation. Some of the GPIOs have multiple use-cases
>>>> depending on device state. Describe these thoroughly.
>>>
>>>
>>> Drop redundant parts of description. Describe the hardware. 
>> 
>> I'll reformulate and incorporate some information of the pins and how it
>> is used in the hardware.
>> 
>>> Entire last paragraph is pretty pointless.
>> 
>> I'll remove it then. (Some sub-system maintainers want a description of
>> what the patch does, in imperative form. But I can see why it might not
>> add any value when it comes to dt-bindings.)
>> 
>>>
>>>> +
>>>> +properties:
>>>> +  compatible:
>>>> +    const: nicera,d3323aa
>>>> +
>>>> +  vdd-gpio:
>>>> +    maxItems: 1
>>>> +    description:
>>>> +      GPIO for supply voltage (1.8 to 5.5 V).
>>>
>>> This is not how pins are represented in the kernel. Either you have here
>>> regulator (supply) or reset gpios. 
>> 
>> I'll change it to `vdd-supply`.
>> 
>>> Plus 'gpio' suffix is not valid, btw.
>> 
>> I actually `grep`ed before writing this to see if there were other
>> dt-bindings with this suffix. Because the GPIO framework supports both
>> `gpio` and `gpios` as suffixes (see `gpio_suffixes[]` in `gpiolib.c`).
>> However, since `-gpios` are clearly in majority, we should go for that.
>
>
> One is deprecated. It is always, always gpios.

Ah, ok! Good to know.

[...]

>>>> +    maxItems: 1
>>>> +    description:
>>>> +      GPIO for clock and detection.
>>>> +      After reset, the device signals with two falling edges on this pin that it
>>>> +      is ready for configuration (within 1.2 s), which the driver listens for as
>>>> +      interrupts.
>>>> +      During configuration, it is used as clock for data reading and writing (on
>>>> +      data-gpio). The driver drives this pin with the frequency of 1 kHz (bit
>>>> +      banging).
>>>> +      After all this, the device is now in operational mode and signals on this
>>>> +      pin for any detections. The driver listens for this as interrupts.
>>>> +
>>>> +  data-gpio:
>>>
>>> There is no such pin.
>> 
>> You mean to change it to `data-gpios`? (There are some using that, e.g.
>> `sensirion,sht15.yaml`).
>
> No, I meant I opened datasheet and found no such pin. Unless you meant
> DO, but then mention in description the actual name of the pin, if you
> are using some more descriptive property name for it.

Got it! Let's do that.

>>>> +    maxItems: 1
>>>> +    description:
>>>> +      GPIO for data reading and writing.
>>>> +      During configuration, configuration data will be written and read back
>>>> +      with bit banging (together with clk-vout-gpio as clock).
>>>> +      After this, during operational mode, the device will output serial data on
>>>> +      this GPIO. However, the driver currently doesn't read this.
>
> BTW, drop all references to driver here and in other places. If driver
> change, you will change binding? If my driver behaves differently, why
> binding would be claiming something else?

Understood, will do that!

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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-20 11:27         ` Waqar Hameed
@ 2025-05-25  9:30           ` Jonathan Cameron
  2025-05-27 14:48             ` Waqar Hameed
  0 siblings, 1 reply; 21+ messages in thread
From: Jonathan Cameron @ 2025-05-25  9:30 UTC (permalink / raw)
  To: Waqar Hameed; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio

On Tue, 20 May 2025 13:27:54 +0200
Waqar Hameed <waqar.hameed@axis.com> wrote:

> On Sun, May 18, 2025 at 18:38 +0100 Jonathan Cameron <jic23@kernel.org> wrote:
> 
> >> >> +#define D3323AA_DRV_NAME "d3323aa"    
> >> >
> >> > Put that inline where used.  A define like this both implies that various values
> >> > must be the same when they need not be and means that we have to go find the
> >> > define to fine out what they are set to.  Just setting the strings directly
> >> > tends to end up more readable.    
> >> 
> >> Sure, we can do that. (There are a bunch of IIO-drivers doing this, so I
> >> just thought that was the "convention".)  
> >
> > I'm sometimes in less fussy mood.  One day I might just clean those up
> > so there is nothing to copy into new drivers!  
> 
> A quick search tells that there are (at least) 105 of those:
> 
>   rgrep -A 30 "\.driver" drivers/iio/ | grep "\.name" | grep -v '"'
>   
> I was just about to write a small Python script to fix those, but just
> wanted to confirm with you before spending more time on this. So if you
> don't want to do this yourself, I can help your here :)

It's probably not worth the churn on the ones that have the string repeated
multiple times.  However, perhaps any that are only using it for .name would
be good to tidy up?  Those are less a case of it being 'taste' vs it being silly
to have a define!

> 
> >>  
> >> >> +	/* Input clock or output detection signal (Vout). */    
> >> >
> >> > I'd rename. Vout kind of suggests a variable voltage. This seems to just
> >> > be a level signal.    
> >>   
> >> >> +	struct gpio_desc *gpiod_clk_vout;    
> >> 
> >> Yeah, it's a weird pin with multiple use-cases... I just named it
> >> according to what the datasheet calls it. What about
> >> `gpiod_clk_detection`?  
> >
> > That sounds like it's detecting a clock.  Hmm.  
> > gpiod_clkin_detectout maybe?  
> 
> No objections.
> 
> [...]
> 


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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-25  9:30           ` Jonathan Cameron
@ 2025-05-27 14:48             ` Waqar Hameed
  2025-05-31 15:10               ` Jonathan Cameron
  0 siblings, 1 reply; 21+ messages in thread
From: Waqar Hameed @ 2025-05-27 14:48 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio

On Sun, May 25, 2025 at 10:30 +0100 Jonathan Cameron <jic23@kernel.org> wrote:

> On Tue, 20 May 2025 13:27:54 +0200
> Waqar Hameed <waqar.hameed@axis.com> wrote:
>
>> On Sun, May 18, 2025 at 18:38 +0100 Jonathan Cameron <jic23@kernel.org> wrote:
>> 
>> >> >> +#define D3323AA_DRV_NAME "d3323aa"    
>> >> >
>> >> > Put that inline where used.  A define like this both implies that various values
>> >> > must be the same when they need not be and means that we have to go find the
>> >> > define to fine out what they are set to.  Just setting the strings directly
>> >> > tends to end up more readable.    
>> >> 
>> >> Sure, we can do that. (There are a bunch of IIO-drivers doing this, so I
>> >> just thought that was the "convention".)  
>> >
>> > I'm sometimes in less fussy mood.  One day I might just clean those up
>> > so there is nothing to copy into new drivers!  
>> 
>> A quick search tells that there are (at least) 105 of those:
>> 
>>   rgrep -A 30 "\.driver" drivers/iio/ | grep "\.name" | grep -v '"'
>>   
>> I was just about to write a small Python script to fix those, but just
>> wanted to confirm with you before spending more time on this. So if you
>> don't want to do this yourself, I can help your here :)
>
> It's probably not worth the churn on the ones that have the string repeated
> multiple times.  However, perhaps any that are only using it for .name would
> be good to tidy up?  Those are less a case of it being 'taste' vs it being silly
> to have a define!

I think if you use it in multiple places, it should definitively be a
macro definition. I just sent some patches for those that only used it
once (I didn't include those with `KBUILD_MODNAME`. We can discuss if we
should also address those in that thread).

However, there are a bunch of drivers that _only_ use a macro definition
in `.name` and `indio_dev->name`, including this one. That _is_ more
than one place, so we should actually leave it? Or do you still think we
should have the same string literal in both places, as you originally
commented above?

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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-27 14:48             ` Waqar Hameed
@ 2025-05-31 15:10               ` Jonathan Cameron
  2025-06-02 15:09                 ` Waqar Hameed
  0 siblings, 1 reply; 21+ messages in thread
From: Jonathan Cameron @ 2025-05-31 15:10 UTC (permalink / raw)
  To: Waqar Hameed; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio

On Tue, 27 May 2025 16:48:30 +0200
Waqar Hameed <waqar.hameed@axis.com> wrote:

> On Sun, May 25, 2025 at 10:30 +0100 Jonathan Cameron <jic23@kernel.org> wrote:
> 
> > On Tue, 20 May 2025 13:27:54 +0200
> > Waqar Hameed <waqar.hameed@axis.com> wrote:
> >  
> >> On Sun, May 18, 2025 at 18:38 +0100 Jonathan Cameron <jic23@kernel.org> wrote:
> >>   
> >> >> >> +#define D3323AA_DRV_NAME "d3323aa"      
> >> >> >
> >> >> > Put that inline where used.  A define like this both implies that various values
> >> >> > must be the same when they need not be and means that we have to go find the
> >> >> > define to fine out what they are set to.  Just setting the strings directly
> >> >> > tends to end up more readable.      
> >> >> 
> >> >> Sure, we can do that. (There are a bunch of IIO-drivers doing this, so I
> >> >> just thought that was the "convention".)    
> >> >
> >> > I'm sometimes in less fussy mood.  One day I might just clean those up
> >> > so there is nothing to copy into new drivers!    
> >> 
> >> A quick search tells that there are (at least) 105 of those:
> >> 
> >>   rgrep -A 30 "\.driver" drivers/iio/ | grep "\.name" | grep -v '"'
> >>   
> >> I was just about to write a small Python script to fix those, but just
> >> wanted to confirm with you before spending more time on this. So if you
> >> don't want to do this yourself, I can help your here :)  
> >
> > It's probably not worth the churn on the ones that have the string repeated
> > multiple times.  However, perhaps any that are only using it for .name would
> > be good to tidy up?  Those are less a case of it being 'taste' vs it being silly
> > to have a define!  
> 
> I think if you use it in multiple places, it should definitively be a
> macro definition. I just sent some patches for those that only used it
> once (I didn't include those with `KBUILD_MODNAME`. We can discuss if we
> should also address those in that thread).

I would disagree slightly.  If it is used in multiple places because there
is some inherent reason they should have same string then I absolutely agree.
If it's just because it's a convenient string that is used twice in places
that could have had different string then not so much.

> 
> However, there are a bunch of drivers that _only_ use a macro definition
> in `.name` and `indio_dev->name`, including this one. That _is_ more
> than one place, so we should actually leave it? Or do you still think we
> should have the same string literal in both places, as you originally
> commented above?

I'd prefer that for new code, but it is a less clear cut case than the ones
you have tidied up, so not worth the churn of tidying up unless people
are otherwise working on the relevant drivers.

Jonathan

> 


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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-31 15:10               ` Jonathan Cameron
@ 2025-06-02 15:09                 ` Waqar Hameed
  0 siblings, 0 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-06-02 15:09 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio

On Sat, May 31, 2025 at 16:10 +0100 Jonathan Cameron <jic23@kernel.org> wrote:

[...]

>> I think if you use it in multiple places, it should definitively be a
>> macro definition. I just sent some patches for those that only used it
>> once (I didn't include those with `KBUILD_MODNAME`. We can discuss if we
>> should also address those in that thread).
>
> I would disagree slightly.  If it is used in multiple places because there
> is some inherent reason they should have same string then I absolutely agree.
> If it's just because it's a convenient string that is used twice in places
> that could have had different string then not so much.

Absolutely! If two places can end up with different strings, then they
shouldn't share the same variable in the first place. I was not as clear
as you with my one-sentence :)

>> However, there are a bunch of drivers that _only_ use a macro definition
>> in `.name` and `indio_dev->name`, including this one. That _is_ more
>> than one place, so we should actually leave it? Or do you still think we
>> should have the same string literal in both places, as you originally
>> commented above?
>
> I'd prefer that for new code, but it is a less clear cut case than the ones
> you have tidied up, so not worth the churn of tidying up unless people
> are otherwise working on the relevant drivers.

Sure, let's use string literals in this driver then.

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

* [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-06-14 21:56 Waqar Hameed
@ 2025-06-14 21:56 ` Waqar Hameed
  0 siblings, 0 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-06-14 21:56 UTC (permalink / raw)
  To: Jonathan Cameron, Lars-Peter Clausen; +Cc: kernel, linux-kernel, linux-iio

Nicera D3-323-AA is a PIR sensor for human detection. It has support for
raw data measurements and detection notification. The communication
protocol is custom made and therefore needs to be GPIO bit banged.

The device has two main settings that can be configured: a threshold
value for detection and a band-pass filter. The configurable parameters
for the band-pass filter are the high-pass and low-pass cutoff
frequencies and its peak gain. Map these settings to the corresponding
parameters in the `iio` framework.

Raw data measurements can be obtained from the device. However, since we
rely on bit banging, it will be rather cumbersome with buffer support.
The main reason being that the data protocol has strict timing
requirements (it's serial like UART), and it's mainly used during
debugging since in real-world applications only the event notification
is of importance. Therefore, only add support for events (for now).

Signed-off-by: Waqar Hameed <waqar.hameed@axis.com>
---
 drivers/iio/proximity/Kconfig   |   9 +
 drivers/iio/proximity/Makefile  |   1 +
 drivers/iio/proximity/d3323aa.c | 808 ++++++++++++++++++++++++++++++++
 3 files changed, 818 insertions(+)
 create mode 100644 drivers/iio/proximity/d3323aa.c

diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig
index a562a78b7d0d..6070974c2c85 100644
--- a/drivers/iio/proximity/Kconfig
+++ b/drivers/iio/proximity/Kconfig
@@ -32,6 +32,15 @@ config CROS_EC_MKBP_PROXIMITY
 	  To compile this driver as a module, choose M here: the
 	  module will be called cros_ec_mkbp_proximity.
 
+config D3323AA
+	tristate "Nicera (Nippon Ceramic Co.) D3-323-AA PIR sensor"
+	depends on GPIOLIB
+	help
+	  Say Y here to build a driver for the Nicera D3-323-AA PIR sensor.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called d3323aa.
+
 config HX9023S
 	tristate "TYHX HX9023S SAR sensor"
 	select IIO_BUFFER
diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile
index c5e76995764a..152034d38c49 100644
--- a/drivers/iio/proximity/Makefile
+++ b/drivers/iio/proximity/Makefile
@@ -6,6 +6,7 @@
 # When adding new entries keep the list in alphabetical order
 obj-$(CONFIG_AS3935)		+= as3935.o
 obj-$(CONFIG_CROS_EC_MKBP_PROXIMITY) += cros_ec_mkbp_proximity.o
+obj-$(CONFIG_D3323AA)		+= d3323aa.o
 obj-$(CONFIG_HX9023S)		+= hx9023s.o
 obj-$(CONFIG_IRSD200)		+= irsd200.o
 obj-$(CONFIG_ISL29501)		+= isl29501.o
diff --git a/drivers/iio/proximity/d3323aa.c b/drivers/iio/proximity/d3323aa.c
new file mode 100644
index 000000000000..71bccca75abd
--- /dev/null
+++ b/drivers/iio/proximity/d3323aa.c
@@ -0,0 +1,808 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Nicera D3-323-AA PIR sensor.
+ *
+ * Copyright (C) 2025 Axis Communications AB
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitmap.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+
+/*
+ * Register bitmap.
+ * For some reason the first bit is denoted as F37 in the datasheet, the second
+ * as F38 and so on. Note the gap between F60 and F64.
+ */
+#define D3323AA_REG_BIT_SLAVEA1		0	/* F37. */
+#define D3323AA_REG_BIT_SLAVEA2		1	/* F38. */
+#define D3323AA_REG_BIT_SLAVEA3		2	/* F39. */
+#define D3323AA_REG_BIT_SLAVEA4		3	/* F40. */
+#define D3323AA_REG_BIT_SLAVEA5		4	/* F41. */
+#define D3323AA_REG_BIT_SLAVEA6		5	/* F42. */
+#define D3323AA_REG_BIT_SLAVEA7		6	/* F43. */
+#define D3323AA_REG_BIT_SLAVEA8		7	/* F44. */
+#define D3323AA_REG_BIT_SLAVEA9		8	/* F45. */
+#define D3323AA_REG_BIT_SLAVEA10	9	/* F46. */
+#define D3323AA_REG_BIT_DETLVLABS0	10	/* F47. */
+#define D3323AA_REG_BIT_DETLVLABS1	11	/* F48. */
+#define D3323AA_REG_BIT_DETLVLABS2	12	/* F49. */
+#define D3323AA_REG_BIT_DETLVLABS3	13	/* F50. */
+#define D3323AA_REG_BIT_DETLVLABS4	14	/* F51. */
+#define D3323AA_REG_BIT_DETLVLABS5	15	/* F52. */
+#define D3323AA_REG_BIT_DETLVLABS6	16	/* F53. */
+#define D3323AA_REG_BIT_DETLVLABS7	17	/* F54. */
+#define D3323AA_REG_BIT_DSLP		18	/* F55. */
+#define D3323AA_REG_BIT_FSTEP0		19	/* F56. */
+#define D3323AA_REG_BIT_FSTEP1		20	/* F57. */
+#define D3323AA_REG_BIT_FILSEL0		21	/* F58. */
+#define D3323AA_REG_BIT_FILSEL1		22	/* F59. */
+#define D3323AA_REG_BIT_FILSEL2		23	/* F60. */
+#define D3323AA_REG_BIT_FDSET		24	/* F64. */
+#define D3323AA_REG_BIT_F65		25
+#define D3323AA_REG_BIT_F87		(D3323AA_REG_BIT_F65 + (87 - 65))
+
+#define D3323AA_REG_NR_BITS (D3323AA_REG_BIT_F87 - D3323AA_REG_BIT_SLAVEA1 + 1)
+#define D3323AA_THRESH_REG_NR_BITS                                             \
+	(D3323AA_REG_BIT_DETLVLABS7 - D3323AA_REG_BIT_DETLVLABS0 + 1)
+#define D3323AA_FILTER_TYPE_NR_BITS                                            \
+	(D3323AA_REG_BIT_FILSEL2 - D3323AA_REG_BIT_FILSEL0 + 1)
+#define D3323AA_FILTER_GAIN_REG_NR_BITS                                        \
+	(D3323AA_REG_BIT_FSTEP1 - D3323AA_REG_BIT_FSTEP0 + 1)
+
+#define D3323AA_THRESH_DEFAULT_VAL 56
+#define D3323AA_FILTER_GAIN_DEFAULT_IDX 1
+#define D3323AA_LP_FILTER_FREQ_DEFAULT_IDX 1
+
+/*
+ * The pattern is 0b01101, but store it reversed (0b10110) due to writing from
+ * LSB on the wire (c.f. d3323aa_write_settings()).
+ */
+#define D3323AA_SETTING_END_PATTERN 0x16
+#define D3323AA_SETTING_END_PATTERN_NR_BITS 5
+
+/*
+ * Device should be ready for configuration after this many milliseconds.
+ * Datasheet mentions "approx. 1.2 s". Measurements show around 1.23 s,
+ * therefore add 100 ms of slack.
+ */
+#define D3323AA_RESET_TIMEOUT (1200 + 100)
+
+/*
+ * The configuration of the device (write and read) should be done within this
+ * many milliseconds.
+ */
+#define D3323AA_CONFIG_TIMEOUT 1400
+
+/* Number of IRQs needed for configuration stage after reset. */
+#define D3323AA_IRQ_RESET_COUNT 2
+
+/*
+ * High-pass filter cutoff frequency for the band-pass filter. There is a
+ * corresponding low-pass cutoff frequency for each of the filter types
+ * (denoted A, B, C and D in the datasheet). The index in this array matches
+ * that corresponding value in d3323aa_lp_filter_freq.
+ * Note that this represents a fractional value (e.g. the first value
+ * corresponds to 40 / 100 = 0.4 Hz).
+ */
+static const int d3323aa_hp_filter_freq[][2] = {
+	{ 40, 100 },
+	{ 30, 100 },
+	{ 30, 100 },
+	{ 1, 100 },
+};
+
+/*
+ * Low-pass filter cutoff frequency for the band-pass filter. There is a
+ * corresponding high-pass cutoff frequency for each of the filter types
+ * (denoted A, B, C and D in the datasheet). The index in this array matches
+ * that corresponding value in d3323aa_hp_filter_freq.
+ * Note that this represents a fractional value (e.g. the first value
+ * corresponds to 27 / 10 = 2.7 Hz).
+ */
+static const int d3323aa_lp_filter_freq[][2] = {
+	{ 27, 10 },
+	{ 15, 10 },
+	{ 5, 1 },
+	{ 100, 1 },
+};
+
+/*
+ * Register bitmap values for filter types (denoted A, B, C and D in the
+ * datasheet). The index in this array matches the corresponding value in
+ * d3323aa_lp_filter_freq (which in turn matches d3323aa_hp_filter_freq). For
+ * example, the first value 7 corresponds to 2.7 Hz low-pass and 0.4 Hz
+ * high-pass cutoff frequency.
+ */
+static const int d3323aa_lp_filter_regval[] = {
+	7,
+	0,
+	1,
+	2,
+};
+
+/*
+ * This is denoted as "step" in datasheet and corresponds to the gain at peak
+ * for the band-pass filter. The index in this array is the corresponding index
+ * in d3323aa_filter_gain_regval for the register bitmap value.
+ */
+static const int d3323aa_filter_gain[] = { 1, 2, 3 };
+
+/*
+ * Register bitmap values for the filter gain. The index in this array is the
+ * corresponding index in d3323aa_filter_gain for the gain value.
+ */
+static const u8 d3323aa_filter_gain_regval[] = { 1, 3, 0 };
+
+struct d3323aa_data {
+	struct completion reset_completion;
+	/*
+	 *  Since the setup process always requires a complete write of _all_
+	 *  the state variables, we need to synchronize them with a lock.
+	 */
+	struct mutex statevar_lock;
+	atomic_t irq_reset_count;
+
+	struct device *dev;
+
+	/* Supply voltage. */
+	struct regulator *regulator_vdd;
+	/* Input clock or output detection signal (Vout). */
+	struct gpio_desc *gpiod_clkin_detectout;
+	/* Input (setting) or output data. */
+	struct gpio_desc *gpiod_data;
+
+	/*
+	 * We only need the low-pass cutoff frequency to unambiguously choose
+	 * the type of band-pass filter. For example, both filter type B and C
+	 * have 0.3 Hz as high-pass cutoff frequency (see
+	 * d3323aa_hp_filter_freq).
+	 */
+	size_t lp_filter_freq_idx;
+	size_t filter_gain_idx;
+	u8 detect_thresh;
+
+	/* Indicator for operational mode (configuring or detecting). */
+	bool detecting;
+};
+
+static int d3323aa_read_settings(struct iio_dev *indio_dev,
+				 unsigned long *regbitmap)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	size_t i;
+	int ret;
+
+	/* Bit bang the clock and data pins. */
+	ret = gpiod_direction_output(data->gpiod_clkin_detectout, 0);
+	if (ret)
+		return ret;
+
+	ret = gpiod_direction_input(data->gpiod_data);
+	if (ret)
+		return ret;
+
+	dev_dbg(data->dev, "Reading settings...\n");
+
+	for (i = 0; i < D3323AA_REG_NR_BITS; ++i) {
+		/* Clock frequency needs to be 1 kHz. */
+		gpiod_set_value(data->gpiod_clkin_detectout, 1);
+		udelay(500);
+
+		/* The data seems to change when clock signal is high. */
+		if (gpiod_get_value(data->gpiod_data))
+			set_bit(i, regbitmap);
+
+		gpiod_set_value(data->gpiod_clkin_detectout, 0);
+		udelay(500);
+	}
+
+	/* The first bit (F37) is just dummy data. Discard it. */
+	clear_bit(0, regbitmap);
+
+	/* Datasheet says to wait 30 ms after reading the settings. */
+	msleep(30);
+
+	return 0;
+}
+
+static int d3323aa_write_settings(struct iio_dev *indio_dev,
+				  unsigned long *written_regbitmap)
+{
+#define REGBITMAP_LEN \
+	(D3323AA_REG_NR_BITS + D3323AA_SETTING_END_PATTERN_NR_BITS)
+	DECLARE_BITMAP(regbitmap, REGBITMAP_LEN);
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	size_t i;
+	int ret;
+
+	/* Build the register bitmap. */
+	bitmap_zero(regbitmap, REGBITMAP_LEN);
+	bitmap_write(regbitmap, data->detect_thresh, D3323AA_REG_BIT_DETLVLABS0,
+		     D3323AA_REG_BIT_DETLVLABS7 - D3323AA_REG_BIT_DETLVLABS0 +
+			     1);
+	bitmap_write(regbitmap,
+		     d3323aa_filter_gain_regval[data->filter_gain_idx],
+		     D3323AA_REG_BIT_FSTEP0,
+		     D3323AA_REG_BIT_FSTEP1 - D3323AA_REG_BIT_FSTEP0 + 1);
+	bitmap_write(regbitmap,
+		     d3323aa_lp_filter_regval[data->lp_filter_freq_idx],
+		     D3323AA_REG_BIT_FILSEL0,
+		     D3323AA_REG_BIT_FILSEL2 - D3323AA_REG_BIT_FILSEL0 + 1);
+	/* Compulsory end pattern. */
+	bitmap_write(regbitmap, D3323AA_SETTING_END_PATTERN,
+		     D3323AA_REG_NR_BITS, D3323AA_SETTING_END_PATTERN_NR_BITS);
+
+	/* Bit bang the clock and data pins. */
+	ret = gpiod_direction_output(data->gpiod_clkin_detectout, 0);
+	if (ret)
+		return ret;
+
+	ret = gpiod_direction_output(data->gpiod_data, 0);
+	if (ret)
+		return ret;
+
+	dev_dbg(data->dev, "Writing settings...\n");
+
+	/* First bit (F37) is not used when writing the register bitmap. */
+	for (i = 1; i < REGBITMAP_LEN; ++i) {
+		gpiod_set_value(data->gpiod_data, test_bit(i, regbitmap));
+
+		/* Clock frequency needs to be 1 kHz. */
+		gpiod_set_value(data->gpiod_clkin_detectout, 1);
+		udelay(500);
+		gpiod_set_value(data->gpiod_clkin_detectout, 0);
+		udelay(500);
+	}
+
+	/* Datasheet says to wait 30 ms after writing the settings. */
+	msleep(30);
+
+	bitmap_copy(written_regbitmap, regbitmap, D3323AA_REG_NR_BITS);
+
+	return 0;
+}
+
+static irqreturn_t d3323aa_irq_handler(int irq, void *dev_id)
+{
+	struct iio_dev *indio_dev = dev_id;
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	enum iio_event_direction dir;
+	int val;
+
+	val = gpiod_get_value(data->gpiod_clkin_detectout);
+	if (val < 0) {
+		dev_err_ratelimited(data->dev,
+				    "Could not read from GPIO vout-clk (%d)\n",
+				    val);
+		return IRQ_HANDLED;
+	}
+
+	if (!data->detecting) {
+		/* Reset interrupt counting falling edges. */
+		if (!val && atomic_inc_return(&data->irq_reset_count) ==
+				    D3323AA_IRQ_RESET_COUNT)
+			complete(&data->reset_completion);
+
+		return IRQ_HANDLED;
+	}
+
+	/* Detection interrupt. */
+	dir = val ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
+	iio_push_event(indio_dev,
+		       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
+					    IIO_EV_TYPE_THRESH, dir),
+		       iio_get_time_ns(indio_dev));
+
+	return IRQ_HANDLED;
+}
+
+static int d3323aa_reset(struct iio_dev *indio_dev)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	long time;
+	int ret;
+
+	if (regulator_is_enabled(data->regulator_vdd)) {
+		ret = regulator_disable(data->regulator_vdd);
+		if (ret)
+			return ret;
+	}
+
+	/*
+	 * Datasheet says VDD needs to be low at least for 30 ms. Let's add a
+	 * couple more to allow VDD to completely discharge as well.
+	 */
+	msleep(30 + 5);
+
+	/*
+	 * After setting VDD to high, the device signals with
+	 * D3323AA_IRQ_RESET_COUNT falling edges on Vout/CLK that it is now
+	 * ready for configuration. Datasheet says that this should happen
+	 * within D3323AA_RESET_TIMEOUT ms. Count these two edges within that
+	 * timeout.
+	 */
+	atomic_set(&data->irq_reset_count, 0);
+	reinit_completion(&data->reset_completion);
+	data->detecting = false;
+
+	ret = gpiod_direction_input(data->gpiod_clkin_detectout);
+	if (ret)
+		return ret;
+
+	dev_dbg(data->dev, "Resetting...\n");
+
+	ret = regulator_enable(data->regulator_vdd);
+	if (ret)
+		return ret;
+
+	/*
+	 * Wait for VDD to completely charge up. Measurements have shown that
+	 * Vout/CLK signal slowly ramps up during this period. Thus, the digital
+	 * signal will have bogus values. It is therefore necessary to wait
+	 * before we can count the "real" falling edges.
+	 */
+	usleep_range(2000, 5000);
+
+	time = wait_for_completion_killable_timeout(
+		&data->reset_completion,
+		msecs_to_jiffies(D3323AA_RESET_TIMEOUT));
+	if (time == 0) {
+		return -ETIMEDOUT;
+	} else if (time < 0) {
+		/* Got interrupted. */
+		return time;
+	}
+
+	dev_dbg(data->dev, "Reset completed\n");
+
+	return 0;
+}
+
+static int d3323aa_setup(struct iio_dev *indio_dev, size_t lp_filter_freq_idx,
+			 size_t filter_gain_idx, u8 detect_thresh)
+{
+	DECLARE_BITMAP(write_regbitmap, D3323AA_REG_NR_BITS);
+	DECLARE_BITMAP(read_regbitmap, D3323AA_REG_NR_BITS);
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	unsigned long start_time;
+	int ret;
+
+	ret = d3323aa_reset(indio_dev);
+	if (ret) {
+		if (ret != -ERESTARTSYS)
+			dev_err(data->dev, "Could not reset device (%d)\n",
+				ret);
+
+		return ret;
+	}
+
+	/*
+	 * Datasheet says to wait 10 us before setting the configuration.
+	 * Moreover, the total configuration should be done within
+	 * D3323AA_CONFIG_TIMEOUT ms. Clock it.
+	 */
+	usleep_range(10, 20);
+	start_time = jiffies;
+
+	ret = d3323aa_write_settings(indio_dev, write_regbitmap);
+	if (ret) {
+		dev_err(data->dev, "Could not write settings (%d)\n", ret);
+		return ret;
+	}
+
+	ret = d3323aa_read_settings(indio_dev, read_regbitmap);
+	if (ret) {
+		dev_err(data->dev, "Could not read settings (%d)\n", ret);
+		return ret;
+	}
+
+	if (time_is_before_jiffies(start_time +
+				   msecs_to_jiffies(D3323AA_CONFIG_TIMEOUT))) {
+		dev_err(data->dev, "Could not set up configuration in time\n");
+		return -EAGAIN;
+	}
+
+	/* Check if settings were set successfully. */
+	if (!bitmap_equal(write_regbitmap, read_regbitmap,
+			  D3323AA_REG_NR_BITS)) {
+		dev_err(data->dev, "Settings data mismatch\n");
+		return -EIO;
+	}
+
+	/* Now in operational mode. */
+	ret = gpiod_direction_input(data->gpiod_clkin_detectout);
+	if (ret) {
+		dev_err(data->dev,
+			"Could not set GPIO vout-clk as input (%d)\n", ret);
+		return ret;
+	}
+
+	ret = gpiod_direction_input(data->gpiod_data);
+	if (ret) {
+		dev_err(data->dev, "Could not set GPIO data as input (%d)\n",
+			ret);
+		return ret;
+	}
+
+	data->lp_filter_freq_idx = lp_filter_freq_idx;
+	data->filter_gain_idx = filter_gain_idx;
+	data->detect_thresh = detect_thresh;
+	data->detecting = true;
+
+	dev_dbg(data->dev, "Setup done\n");
+
+	return 0;
+}
+
+static int d3323aa_set_lp_filter_freq(struct iio_dev *indio_dev, const int val,
+				      int val2)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	size_t idx;
+
+	/* Truncate fractional part to one digit. */
+	val2 /= 100000;
+
+	for (idx = 0; idx < ARRAY_SIZE(d3323aa_lp_filter_freq); ++idx) {
+		int integer = d3323aa_lp_filter_freq[idx][0] /
+			      d3323aa_lp_filter_freq[idx][1];
+		int fract = d3323aa_lp_filter_freq[idx][0] %
+			    d3323aa_lp_filter_freq[idx][1];
+
+		if (val == integer && val2 == fract)
+			break;
+	}
+
+	if (idx == ARRAY_SIZE(d3323aa_lp_filter_freq))
+		return -ERANGE;
+
+	return d3323aa_setup(indio_dev, idx, data->filter_gain_idx,
+			     data->detect_thresh);
+}
+
+static int d3323aa_set_hp_filter_freq(struct iio_dev *indio_dev, const int val,
+				      int val2)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	size_t idx;
+
+	/* Truncate fractional part to two digits. */
+	val2 /= 10000;
+
+	for (idx = 0; idx < ARRAY_SIZE(d3323aa_hp_filter_freq); ++idx) {
+		int integer = d3323aa_hp_filter_freq[idx][0] /
+			      d3323aa_hp_filter_freq[idx][1];
+		int fract = d3323aa_hp_filter_freq[idx][0] %
+			    d3323aa_hp_filter_freq[idx][1];
+
+		if (val == integer && val2 == fract)
+			break;
+	}
+
+	if (idx == ARRAY_SIZE(d3323aa_hp_filter_freq))
+		return -ERANGE;
+
+	if (idx == data->lp_filter_freq_idx) {
+		/* Corresponding filter frequency already set. */
+		return 0;
+	}
+
+	if (idx == 1 && data->lp_filter_freq_idx == 2) {
+		/*
+		 * The low-pass cutoff frequency is the only way to
+		 * unambiguously choose the type of band-pass filter. For
+		 * example, both filter type B (index 1) and C (index 2) have
+		 * 0.3 Hz as high-pass cutoff frequency (see
+		 * d3323aa_hp_filter_freq). Therefore, if one of these are
+		 * requested _and_ the corresponding low-pass filter frequency
+		 * is already set, we can't know which filter type is the wanted
+		 * one. The low-pass filter frequency is the decider (i.e. in
+		 * this case index 2).
+		 */
+		return 0;
+	}
+
+	return d3323aa_setup(indio_dev, idx, data->filter_gain_idx,
+			     data->detect_thresh);
+}
+
+static int d3323aa_set_filter_gain(struct iio_dev *indio_dev, const int val)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+	size_t idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(d3323aa_filter_gain); ++idx) {
+		if (d3323aa_filter_gain[idx] == val)
+			break;
+	}
+
+	if (idx == ARRAY_SIZE(d3323aa_filter_gain))
+		return -ERANGE;
+
+	return d3323aa_setup(indio_dev, data->lp_filter_freq_idx, idx,
+			     data->detect_thresh);
+}
+
+static int d3323aa_set_threshold(struct iio_dev *indio_dev, const int val)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+
+	if (val > ((1 << D3323AA_THRESH_REG_NR_BITS) - 1))
+		return -ERANGE;
+
+	return d3323aa_setup(indio_dev, data->lp_filter_freq_idx,
+			     data->filter_gain_idx, val);
+}
+
+static int d3323aa_read_avail(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      const int **vals, int *type, int *length,
+			      long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+		*vals = (int *)d3323aa_hp_filter_freq;
+		*type = IIO_VAL_FRACTIONAL;
+		*length = 2 * ARRAY_SIZE(d3323aa_hp_filter_freq);
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+		*vals = (int *)d3323aa_lp_filter_freq;
+		*type = IIO_VAL_FRACTIONAL;
+		*length = 2 * ARRAY_SIZE(d3323aa_lp_filter_freq);
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		*vals = (int *)d3323aa_filter_gain;
+		*type = IIO_VAL_INT;
+		*length = ARRAY_SIZE(d3323aa_filter_gain);
+		return IIO_AVAIL_LIST;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int d3323aa_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+
+	guard(mutex)(&data->statevar_lock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+		*val = d3323aa_hp_filter_freq[data->lp_filter_freq_idx][0];
+		*val2 = d3323aa_hp_filter_freq[data->lp_filter_freq_idx][1];
+		return IIO_VAL_FRACTIONAL;
+	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+		*val = d3323aa_lp_filter_freq[data->lp_filter_freq_idx][0];
+		*val2 = d3323aa_lp_filter_freq[data->lp_filter_freq_idx][1];
+		return IIO_VAL_FRACTIONAL;
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		*val = d3323aa_filter_gain[data->filter_gain_idx];
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int d3323aa_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{
+	struct d3323aa_data *data = iio_priv(indio_dev);
+
+	guard(mutex)(&data->statevar_lock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+		return d3323aa_set_hp_filter_freq(indio_dev, val, val2);
+	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+		return d3323aa_set_lp_filter_freq(indio_dev, val, val2);
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		return d3323aa_set_filter_gain(indio_dev, val);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int d3323aa_read_event(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 d3323aa_data *data = iio_priv(indio_dev);
+
+	guard(mutex)(&data->statevar_lock);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		*val = data->detect_thresh;
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int d3323aa_write_event(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 d3323aa_data *data = iio_priv(indio_dev);
+
+	guard(mutex)(&data->statevar_lock);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		return d3323aa_set_threshold(indio_dev, val);
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info d3323aa_info = {
+	.read_avail = d3323aa_read_avail,
+	.read_raw = d3323aa_read_raw,
+	.write_raw = d3323aa_write_raw,
+	.read_event_value = d3323aa_read_event,
+	.write_event_value = d3323aa_write_event,
+};
+
+static const struct iio_event_spec d3323aa_event_spec[] = {
+	{
+		.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 d3323aa_channels[] = {
+	{
+		.type = IIO_PROXIMITY,
+		.info_mask_separate =
+			BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) |
+			BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
+			BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+		.info_mask_separate_available =
+			BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) |
+			BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
+			BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+		.event_spec = d3323aa_event_spec,
+		.num_event_specs = ARRAY_SIZE(d3323aa_event_spec),
+	},
+};
+
+static void d3323aa_disable_regulator(void *indata)
+{
+	struct d3323aa_data *data = indata;
+	int ret;
+
+	if (!regulator_is_enabled(data->regulator_vdd))
+		return;
+
+	ret = regulator_disable(data->regulator_vdd);
+	if (ret)
+		dev_err(data->dev, "Could not disable regulator (%d)\n", ret);
+}
+
+static int d3323aa_probe(struct platform_device *pdev)
+{
+	struct d3323aa_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
+	if (!indio_dev)
+		return dev_err_probe(&pdev->dev, -ENOMEM,
+				     "Could not allocate iio device\n");
+
+	data = iio_priv(indio_dev);
+	data->dev = &pdev->dev;
+
+	init_completion(&data->reset_completion);
+
+	ret = devm_mutex_init(data->dev, &data->statevar_lock);
+	if (ret)
+		return dev_err_probe(data->dev, ret,
+				     "Could not initialize mutex\n");
+
+	data->regulator_vdd = devm_regulator_get_exclusive(data->dev, "vdd");
+	if (IS_ERR(data->regulator_vdd))
+		return dev_err_probe(data->dev, PTR_ERR(data->regulator_vdd),
+				     "Could not get regulator\n");
+
+	ret = devm_add_action_or_reset(data->dev, d3323aa_disable_regulator,
+				       data);
+	if (ret)
+		return dev_err_probe(
+			data->dev, ret,
+			"Could not add disable regulator action\n");
+
+	data->gpiod_clkin_detectout =
+		devm_gpiod_get(data->dev, "vout-clk", GPIOD_OUT_LOW);
+	if (IS_ERR(data->gpiod_clkin_detectout))
+		return dev_err_probe(data->dev,
+				     PTR_ERR(data->gpiod_clkin_detectout),
+				     "Could not get GPIO vout-clk\n");
+
+	data->gpiod_data = devm_gpiod_get(data->dev, "data", GPIOD_OUT_LOW);
+	if (IS_ERR(data->gpiod_data))
+		return dev_err_probe(data->dev, PTR_ERR(data->gpiod_data),
+				     "Could not get GPIO data\n");
+
+	ret = gpiod_to_irq(data->gpiod_clkin_detectout);
+	if (ret < 0)
+		return dev_err_probe(data->dev, ret, "Could not get IRQ\n");
+
+	/*
+	 * Device signals with a rising or falling detection signal when the
+	 * proximity data is above or below the threshold, respectively.
+	 */
+	ret = devm_request_irq(data->dev, ret, d3323aa_irq_handler,
+			       IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			       dev_name(data->dev), indio_dev);
+	if (ret)
+		return dev_err_probe(data->dev, ret, "Could not request IRQ\n");
+
+	ret = d3323aa_setup(indio_dev, D3323AA_LP_FILTER_FREQ_DEFAULT_IDX,
+			    D3323AA_FILTER_GAIN_DEFAULT_IDX,
+			    D3323AA_THRESH_DEFAULT_VAL);
+	if (ret)
+		return ret;
+
+	indio_dev->info = &d3323aa_info;
+	indio_dev->name = "d3323aa";
+	indio_dev->channels = d3323aa_channels;
+	indio_dev->num_channels = ARRAY_SIZE(d3323aa_channels);
+
+	ret = devm_iio_device_register(data->dev, indio_dev);
+	if (ret)
+		return dev_err_probe(data->dev, ret,
+				     "Could not register iio device\n");
+
+	return 0;
+}
+
+static const struct of_device_id d3323aa_of_match[] = {
+	{
+		.compatible = "nicera,d3323aa",
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, d3323aa_of_match);
+
+static struct platform_driver d3323aa_driver = {
+	.probe = d3323aa_probe,
+	.driver = {
+		.name = "d3323aa",
+		.of_match_table = d3323aa_of_match,
+	},
+};
+module_platform_driver(d3323aa_driver);
+
+MODULE_AUTHOR("Waqar Hameed <waqar.hameed@axis.com>");
+MODULE_DESCRIPTION("Nicera D3-323-AA PIR sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.39.5


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

* Re: [PATCH 3/3] iio: Add driver for Nicera D3-323-AA PIR sensor
  2025-05-16 17:16     ` Waqar Hameed
  2025-05-18 17:38       ` Jonathan Cameron
@ 2025-06-14 22:05       ` Waqar Hameed
  1 sibling, 0 replies; 21+ messages in thread
From: Waqar Hameed @ 2025-06-14 22:05 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Lars-Peter Clausen, kernel, linux-kernel, linux-iio

On Fri, May 16, 2025 at 19:16 +0200 Waqar Hameed <waqar.hameed@axis.com> wrote:

> On Sun, May 11, 2025 at 13:14 +0100 Jonathan Cameron <jic23@kernel.org> wrote:

[...]

>>> +
>>> +/*
>>> + * Register bitmap.
>>> + * For some reason the first bit is denoted as F37 in the datasheet, the second
>>> + * as F38 and so on. Note the gap between F60 and F64.
>>> + */
>>> +#define D3323AA_REG_BIT_SLAVEA1		0	/* F37. */
>>> +#define D3323AA_REG_BIT_SLAVEA2		1	/* F38. */
>>> +#define D3323AA_REG_BIT_SLAVEA3		2	/* F39. */
>>> +#define D3323AA_REG_BIT_SLAVEA4		3	/* F40. */
>>> +#define D3323AA_REG_BIT_SLAVEA5		4	/* F41. */
>>> +#define D3323AA_REG_BIT_SLAVEA6		5	/* F42. */
>>> +#define D3323AA_REG_BIT_SLAVEA7		6	/* F43. */
>>> +#define D3323AA_REG_BIT_SLAVEA8		7	/* F44. */
>>> +#define D3323AA_REG_BIT_SLAVEA9		8	/* F45. */
>> Perhaps these can be represented as masks using GENMASK() rather than
>> bits.  A lot of this will be hidden away if you follow suggesting to
>> only expose that you are using a bitmap to bitbang in the read/write
>> functions.
>
> Yes, that would be the natural thing to do when moving the bitmap stuff
> to the read/write functions (as answered below).

Since `bitmap_write()` needs an offset (and size), I didn't use
`GENMASK()` in v2 and thought it would be more clear this way. I'm still
open for suggestions though.

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

end of thread, other threads:[~2025-06-14 22:05 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-09 15:03 [PATCH 0/3] Add driver for Nicera D3-323-AA PIR sensor Waqar Hameed
2025-05-09 15:03 ` [PATCH 2/3] dt-bindings: iio: proximity: Add " Waqar Hameed
2025-05-09 15:06   ` Krzysztof Kozlowski
2025-05-16 17:15     ` Waqar Hameed
2025-05-20  6:36       ` Krzysztof Kozlowski
2025-05-20 12:00         ` Waqar Hameed
2025-05-09 15:03 ` [PATCH 3/3] iio: Add driver for " Waqar Hameed
2025-05-11  7:57   ` Christophe JAILLET
2025-05-16 17:16     ` Waqar Hameed
2025-05-11 12:14   ` Jonathan Cameron
2025-05-16 17:16     ` Waqar Hameed
2025-05-18 17:38       ` Jonathan Cameron
2025-05-20 11:27         ` Waqar Hameed
2025-05-25  9:30           ` Jonathan Cameron
2025-05-27 14:48             ` Waqar Hameed
2025-05-31 15:10               ` Jonathan Cameron
2025-06-02 15:09                 ` Waqar Hameed
2025-06-14 22:05       ` Waqar Hameed
2025-05-09 15:09 ` [PATCH 0/3] " Krzysztof Kozlowski
2025-05-16 17:14   ` Waqar Hameed
  -- strict thread matches above, loose matches on Subject: below --
2025-06-14 21:56 Waqar Hameed
2025-06-14 21:56 ` [PATCH 3/3] iio: " Waqar Hameed

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