Skip to contentSkip to conversation

Support for Texas Instruments OPT4060 RGBW Color sensor.

PATCH Superseded Per-Daniel Olsson 5 participants 12 messages Oct 5, 2024 – Oct 15, 2024

Subsystems (2)

v2 of 10 Superseded Latest is v10 →

lore
Share & tools
lore
#0 lore raw

v2 cover Support for Texas Instruments OPT4060 RGBW Color sensor.

This patch series adds support for Texas Instruments OPT4060 RGBW Color sensor using the i2c interface.

The driver exposes both raw adc values and calculated lux values though sysfs. Integration time can be configured though sysfs as well. The OPT4060 sensor supports both rising and falling threshold interrupts. These interrupts are exposed as IIO events. The driver also implements an IIO triggered buffer with two triggers, one trigger for conversion ready interrupts and one trigger for threshold interrupts. The typical use case for this is to define a threshold and listen for the events, and at the same time enable the triggered buffer with the threshold trigger. Once the application gets the threshold event, the values from the time of the event will be available in the triggered buffer. This limits the number of interrupts between sensor and host and also the the usage of sysfs for reading values after events.

Changes in v2:
- dt-bindings: Removed incorrect allOf.
- dt-bindings: Changed to generic node name.
- Correction in opt4060_trigger_one_shot(...) for continuous mode.
- Correction in opt4060_power_down(...), wrong register was read.
- Corrected usage of active_scan_mask in opt4060_trigger_handler(...).
- Clean-up of various comments.
- Link to V1: https://lore.kernel.org/lkml/[email protected]/

Per-Daniel Olsson (2):
dt-bindings: iio: light: Document TI OPT4060 RGBW sensor
iio: light: Add support for TI OPT4060 color sensor

 .../bindings/iio/light/ti,opt4060.yaml        |   51 +
 drivers/iio/light/Kconfig                     |   13 +
 drivers/iio/light/Makefile                    |    1 +
 drivers/iio/light/opt4060.c                   | 1216 +++++++++++++++++
 4 files changed, 1281 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml
 create mode 100644 drivers/iio/light/opt4060.c

base-commit: 0c559323bbaabee7346c12e74b497e283aaafef5

Signature · 1 line
2.39.5
#1 lore raw

PATCH v2 1/2 dt-bindings: iio: light: Document TI OPT4060 RGBW sensor

Add devicetree bindings for the OPT4060 RGBW color sensor.

---
 .../bindings/iio/light/ti,opt4060.yaml        | 51 +++++++++++++++++++
 1 file changed, 51 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml
Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml new file +51 -0
new file mode 100644
index 000000000000..0577e27db1ef
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/light/ti,opt4060.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments OPT4060 RGBW Color Sensor
+
+maintainers:
+  - Per-Daniel Olsson <[email protected]>
+
+description:
+  Texas Instrument RGBW high resolution color sensor over I2C.
+  https://www.ti.com/lit/gpn/opt4060
+
+properties:
+  compatible:
+    enum:
+      - ti,opt4060
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  vdd-supply:
+    description: Regulator that provides power to the sensor.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        light-sensor@44 {
+            compatible = "ti,opt4060";
+            reg = <0x44>;
+            vdd-supply = <&vdd_reg>;
+            interrupt-parent = <&gpio5>;
+            interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
+        };
+    };
+...
Signature · 1 line
2.39.5
Reviewed-by on v2 1/2
#2 lore raw

Re: [PATCH v2 1/2] dt-bindings: iio: light: Document TI OPT4060 RGBW sensor

Quoted 7 lines · Per-Daniel Olsson

Add devicetree bindings for the OPT4060 RGBW color sensor.

---
 .../bindings/iio/light/ti,opt4060.yaml        | 51 +++++++++++++++++++
 1 file changed, 51 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml

---

<form letter>
This is an automated instruction, just in case, because many review tags are being ignored. If you know the process, you can skip it (please do not feel offended by me posting it here - no bad intentions intended). If you do not know the process, here is a short explanation:

Please add Acked-by/Reviewed-by/Tested-by tags when posting new versions, under or above your Signed-off-by tag. Tag is "received", when provided in a message replied to you on the mailing list. Tools like b4 can help here. However, there's no need to repost patches *only* to add the tags. The upstream maintainer will do that for tags received on the version they apply.

https://elixir.bootlin.com/linux/v6.5-rc3/source/Documentation/process/submitting-patches.rst#L577
</form letter>

Best regards,
Krzysztof

#3 lore raw

PATCH v2 2/2 iio: light: Add support for TI OPT4060 color sensor

In reply to Per-Daniel Olsson

Add support for Texas Instruments OPT4060 RGBW Color sensor.

---
 drivers/iio/light/Kconfig   |   13 +
 drivers/iio/light/Makefile  |    1 +
 drivers/iio/light/opt4060.c | 1216 +++++++++++++++++++++++++++++++++++
 3 files changed, 1230 insertions(+)
 create mode 100644 drivers/iio/light/opt4060.c
drivers/iio/light/Kconfig +13 -0
index 515ff46b5b82..0e2059512157 100644
@@ -490,6 +490,19 @@ config OPT4001
 	  If built as a dynamically linked module, it will be called
 	  opt4001.
 
+config OPT4060
+	tristate "Texas Instruments OPT4060 RGBW Color Sensor"
+	depends on I2C
+	select REGMAP_I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  If you say Y or M here, you get support for Texas Instruments
+	  OPT4060 RGBW Color Sensor.
+
+	  If built as a dynamically linked module, it will be called
+	  opt4060.
+
 config PA12203001
 	tristate "TXC PA12203001 light and proximity sensor"
 	depends on I2C
drivers/iio/light/Makefile +1 -0
index 321010fc0b93..55902b21ec0c 100644
@@ -42,6 +42,7 @@ obj-$(CONFIG_MAX44009)		+= max44009.o
 obj-$(CONFIG_NOA1305)		+= noa1305.o
 obj-$(CONFIG_OPT3001)		+= opt3001.o
 obj-$(CONFIG_OPT4001)		+= opt4001.o
+obj-$(CONFIG_OPT4060)		+= opt4060.o
 obj-$(CONFIG_PA12203001)	+= pa12203001.o
 obj-$(CONFIG_ROHM_BU27008)	+= rohm-bu27008.o
 obj-$(CONFIG_ROHM_BU27034)	+= rohm-bu27034.o
drivers/iio/light/opt4060.c new file +1216 -0

1,216 lines not on this page · raw message

Signature · 1 line
2.39.5
maintainer
#4 lore raw

Re: [PATCH v2 2/2] iio: light: Add support for TI OPT4060 color sensor

Quoted 1 line · On Sat, 5 Oct 2024 18:51:19 +0200 Per-Daniel Olsson…

Add support for Texas Instruments OPT4060 RGBW Color sensor.

Hi Per-Daniel.

Thanks for your driver. Various comments inline.
The two bigger things I'd like to understand are:
1) Why we have a threshold trigger? That is unusual so I'd like to fully understand the use case. I see an explanation in your cover letter so I'll comment on that here but I would like one here as well.

2) Why do we need processed and raw? That may make sense but it is also unusual so I'd like a comment in this patch description on your reasoning.

I've only raised some style issues in a few places. Please check to see if those apply more broadly.

Thanks,

Jonathan

Quoted 66 lines
---
 drivers/iio/light/Kconfig   |   13 +
 drivers/iio/light/Makefile  |    1 +
 drivers/iio/light/opt4060.c | 1216 +++++++++++++++++++++++++++++++++++
 3 files changed, 1230 insertions(+)
 create mode 100644 drivers/iio/light/opt4060.c
drivers/iio/light/Kconfig +13 -0
index 515ff46b5b82..0e2059512157 100644
@@ -490,6 +490,19 @@ config OPT4001
 	  If built as a dynamically linked module, it will be called
 	  opt4001.
 
+config OPT4060
+	tristate "Texas Instruments OPT4060 RGBW Color Sensor"
+	depends on I2C
+	select REGMAP_I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  If you say Y or M here, you get support for Texas Instruments
+	  OPT4060 RGBW Color Sensor.
+
+	  If built as a dynamically linked module, it will be called
+	  opt4060.
+
 config PA12203001
 	tristate "TXC PA12203001 light and proximity sensor"
 	depends on I2C
drivers/iio/light/Makefile +1 -0
index 321010fc0b93..55902b21ec0c 100644
@@ -42,6 +42,7 @@ obj-$(CONFIG_MAX44009)		+= max44009.o
 obj-$(CONFIG_NOA1305)		+= noa1305.o
 obj-$(CONFIG_OPT3001)		+= opt3001.o
 obj-$(CONFIG_OPT4001)		+= opt4001.o
+obj-$(CONFIG_OPT4060)		+= opt4060.o
 obj-$(CONFIG_PA12203001)	+= pa12203001.o
 obj-$(CONFIG_ROHM_BU27008)	+= rohm-bu27008.o
 obj-$(CONFIG_ROHM_BU27034)	+= rohm-bu27034.o
drivers/iio/light/opt4060.c new file +15 -0
new file mode 100644
index 000000000000..a917b59cd500
@@ -0,0 +1,1216 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Axis Communications AB
+ *
+ * Datasheet: https://www.ti.com/lit/gpn/opt4060
+ *
+ * Device driver for the Texas Instruments OPT4060 RGBW Color Sensor.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/math64.h>
+#include <linux/units.h>
+#include <linux/limits.h>

drivers/iio/light/opt4060.c line 25

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define OPT_4060_DRV_NAME	"opt4060"

I'm generally against these DRV_NAME type defines as they tend to imply there is a reason various strings in the driver match when it is an arbitrary choice. I'd rather see the string inline.

drivers/iio/light/opt4060.c line 148

Quoted 4 lines
drivers/iio/light/opt4060.c new file +4 -0
+
+struct opt4060_buffer {
+	u32 chan[OPT4060_NUM_CHANS];
+	s64 ts;

aligned_s64
Which is needed to cover the delights of 32 bit x86 where an s64 is 32bit aligned and this whole structure can end up aligned differently from the obvious.

Quoted 1 line
drivers/iio/light/opt4060.c new file +1 -0
+};

...

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+
+static u8 opt4060_calculate_crc(u8 exp, u32 mantissa, u8 count)

Good to include the formula here. I'm assuming this doesn't correspond to any of the standard crc codes for which we have implementations already.

Quoted 15 lines
drivers/iio/light/opt4060.c new file +13 -0
+{
+	u8 crc;
+
+	crc = (hweight32(mantissa) + hweight32(exp) + hweight32(count)) % 2;
+	crc |= ((hweight32(mantissa & 0xAAAAA) + hweight32(exp & 0xA)
+		 + hweight32(count & 0xA)) % 2) << 1;
+	crc |= ((hweight32(mantissa & 0x88888) + hweight32(exp & 0x8)
+		 + hweight32(count & 0x8)) % 2) << 2;
+	crc |= (hweight32(mantissa & 0x80808) % 2) << 3;
+
+	return crc;
+}
 
 
+

drivers/iio/light/opt4060.c

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+static int opt4060_read_raw_value(struct opt4060_chip *chip,
+				  unsigned long address, u32 *raw)
+{
+	int ret;
+	u32 result;
+	u16 msb;
+	u16 lsb;
+	u8 exp;
+	u8 count;
+	u8 crc;

Combine entrees of same type to save a few lines here

drivers/iio/light/opt4060.c line 312

Quoted 6 lines
drivers/iio/light/opt4060.c new file +6 -0
+	u8 calc_crc;
+	u32 mantissa_raw;
+
+	ret = regmap_bulk_read(chip->regmap, address, &result, 2);
+	if (ret)
+		dev_err(chip->dev, "Reading channel data failed\n");

return ret; and drop the else.

Also kernel coding style requires {} for all legs if any need it.

Quoted 25 lines
drivers/iio/light/opt4060.c new file +25 -0
+	else {
+		count = FIELD_GET(OPT4060_COUNTER_MASK, result >> 16);
+		crc = FIELD_GET(OPT4060_CRC_MASK, result >> 16);
+		lsb = FIELD_GET(OPT4060_LSB_MASK, result >> 16);
+		exp = FIELD_GET(OPT4060_EXPONENT_MASK, result);
+		msb = FIELD_GET(OPT4060_MSB_MASK, result);
+		mantissa_raw = (msb << 8) + lsb;
+		calc_crc = opt4060_calculate_crc(exp, mantissa_raw, count);
+		if (calc_crc != crc)
+			return -EIO;
+
+		*raw = mantissa_raw << exp;
+	}
+	return ret;
+}
+
+static int opt4060_read_chan_value(struct iio_dev *indio_dev,
+				   struct iio_chan_spec const *chan,
+				   int *val, int *val2, bool processed)
+{
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+	u32 adc_raw;
+	int ret;
+	/* Set timeout to two times the integration time multiplied by channel count. */
+	unsigned int timeout_us = 2 * OPT4060_NUM_CHANS *

drivers/iio/light/opt4060.c line 347

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				  opt4060_int_time_reg[chip->int_time][0];
+
+	if (chip->irq) {
+		reinit_completion(&chip->completion);
+		ret = opt4060_trigger_one_shot(chip);
+		if (ret)
+			return ret;
+		if (wait_for_completion_timeout(&chip->completion,
+						usecs_to_jiffies(timeout_us)) == 0)
+			dev_info(chip->dev, "Completion timed out.\n");

It's an error dev_err() appropriate.

drivers/iio/light/opt4060.c line 355

Quoted 8 lines
drivers/iio/light/opt4060.c new file +8 -0
+	}
+
+	ret = opt4060_read_raw_value(chip, chan->address, &adc_raw);
+	if (ret) {
+		dev_err(chip->dev, "Reading raw channel data failed\n");
+		return ret;
+	}
+	if (processed) {

Having both processed and raw for a channel needs an explanation. It's not helped by processed being meaningless for color channels as there are not really an well defined units.

drivers/iio/light/opt4060.c line 367

Quoted 12 lines
drivers/iio/light/opt4060.c new file +12 -0
+		u32 lux_raw;
+		u32 rem;
+		u32 mul = opt4060_channel_factors[chan->scan_index].mul;
+		u32 div = opt4060_channel_factors[chan->scan_index].div;
+
+		lux_raw = adc_raw * mul;
+		*val = div_u64_rem(lux_raw, div, &rem);
+		*val2 = rem * div_u64(div, 10);
+		return IIO_VAL_INT_PLUS_NANO;
+	}
+	*val = adc_raw;
+	*val2 = 0;

No need to set val2 if IIO_VAL_INT
The core code guarantees never to use it.

Quoted 6 lines
drivers/iio/light/opt4060.c new file +5 -0
+	return IIO_VAL_INT;
+}
 
+static int opt4060_power_down(struct opt4060_chip *chip)
+{
+	int ret;

drivers/iio/light/opt4060.c line 406

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	unsigned int reg;
+
+	ret = regmap_read(chip->regmap, OPT4060_CTRL, &reg);
+	if (ret) {
+		dev_err(chip->dev, "Failed to read configuration\n");
+		return ret;
+	}
+
+	/* MODE_OFF is 0x0 so just set bits to 0 */
+	reg &= ~OPT4060_CTRL_OPER_MODE_MASK;

regmap_clear_bits() maybe?

drivers/iio/light/opt4060.c line 417

Quoted 11 lines
drivers/iio/light/opt4060.c new file +11 -0
+
+	ret = regmap_write(chip->regmap, OPT4060_CTRL, reg);
+	if (ret)
+		dev_err(chip->dev, "Failed to power down\n");
+
+	return ret;
+}
+
+static void opt4060_chip_off_action(void *data)
+{
+	struct opt4060_chip *chip = data;

No need for the local variable. Just rename data to chip is enough.

Quoted 12 lines
drivers/iio/light/opt4060.c new file +12 -0
+
+	opt4060_power_down(chip);
+}
+
+#define OPT4060_CHAN(color)							\
+{										\
+	.type = IIO_LIGHT,							\
+	.modified = 1,								\
+	.channel2 = IIO_MOD_LIGHT_##color,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |			\
+			      BIT(IIO_CHAN_INFO_RAW),				\
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),			\

drivers/iio/light/opt4060.c line 439

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),	\
+	.address = OPT4060_##color##_MSB,					\
+	.scan_index = OPT4060_##color,						\
+	.scan_type = {								\
+		.sign = 'u',							\
+		.realbits = 32,							\
+		.storagebits = 32,						\
+		.endianness = IIO_LE,						\
+	},									\
+	.event_spec = opt4060_event_spec,					\

where you have an optional irq like here, have two sets of chan_spec and pick between them dependent on the availability of that irq.

If there is no irq, there should be no event attributes.

Quoted 34 lines
drivers/iio/light/opt4060.c new file +33 -0
+	.num_event_specs = ARRAY_SIZE(opt4060_event_spec),			\
+}
+
+static const struct iio_event_spec opt4060_event_spec[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				 BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				 BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_PERIOD),
+	},
+};
 
+
+static bool opt4060_event_active(struct opt4060_chip *chip)
+{
+	return chip->thresh_event_lo_active || chip->thresh_event_hi_active;
+}
+
+static int opt4060_read_available(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_INT_TIME:

drivers/iio/light/opt4060.c line 578

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+		*length = ARRAY_SIZE(opt4060_int_time_available) * 2;
+		*vals = (const int *)opt4060_int_time_available;
+		*type = IIO_VAL_INT_PLUS_MICRO;
+		return IIO_AVAIL_LIST;
+
+	default:
+		return -EINVAL;
+	}
+}
+

One blank line is enough. Check for other instances.

drivers/iio/light/opt4060.c line 585

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+
+static int opt4060_event_set_state(struct opt4060_chip *chip, bool state)
+{
+	int ret = 0;
+
+	if (state)
+		ret = opt4060_set_continous_mode(chip, true);

return opt...

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+	else if (!chip->data_trigger_active && chip->irq)
+		ret = opt4060_set_continous_mode(chip, false);

return opt...

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+
+	return ret;

Not an error?

Quoted 6 lines
drivers/iio/light/opt4060.c new file +6 -0
+}
+
+static int opt4060_trigger_set_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct opt4060_chip *chip = iio_priv(indio_dev);

drivers/iio/light/opt4060.c line 605

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	int ret = 0;
+
+	if (indio_dev->trig) {
+		iio_trigger_put(indio_dev->trig);
+		indio_dev->trig = NULL;
+	}
+
+	if (state) {
+		indio_dev->trig = iio_trigger_get(trig);
+		ret = opt4060_set_continous_mode(chip, true);

return opt...

drivers/iio/light/opt4060.c line 607

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+	} else if (!opt4060_event_active(chip) && chip->irq)
+		ret = opt4060_set_continous_mode(chip, false);

return opt

Quoted 11 lines
drivers/iio/light/opt4060.c new file +11 -0
+
+	if (trig == chip->data_trig) {
+		chip->data_trigger_active = state;
+		chip->threshold_trigger_active = !state;
+	} else if (trig == chip->threshold_trig) {
+		chip->data_trigger_active = !state;
+		chip->threshold_trigger_active = state;
+	}
+
+	return ret;
+}

drivers/iio/light/opt4060.c line 685

Quoted 10 lines
drivers/iio/light/opt4060.c new file +9 -0
 
+
+static int opt4060_get_channel_sel(struct opt4060_chip *chip, int *ch_sel)
+{
+	int ret;
+	u32 regval;
+
+	ret = regmap_read(chip->regmap, OPT4060_INT_CTRL, &regval);
+	if (ret)
+		dev_err(chip->dev, "Failed to get channel selection.\n");
		return ret;

drivers/iio/light/opt4060.c

Quoted 1 line
drivers/iio/light/opt4060.c new file +1 -0
+	else

no else needed.

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+		*ch_sel = FIELD_GET(OPT4060_INT_CTRL_THRESH_SEL, regval);
+	return ret;

return 0;
Check for other cases that look like this. Early returns on error almost always make for more readable code than keeping the return for the end (After doing nothing useful extra)

Quoted 25 lines
drivers/iio/light/opt4060.c new file +25 -0
+}
+
+static int opt4060_set_channel_sel(struct opt4060_chip *chip, int ch_sel)
+{
+	int ret;
+	u32 regval;
+
+	regval = FIELD_PREP(OPT4060_INT_CTRL_THRESH_SEL, ch_sel);
+	ret = regmap_update_bits(chip->regmap, OPT4060_INT_CTRL,
+				 OPT4060_INT_CTRL_THRESH_SEL, regval);
+	if (ret)
+		dev_err(chip->dev, "Failed to set channel selection.\n");
+	return ret;
+}
+
+static int opt4060_get_thresholds(struct opt4060_chip *chip, u32 *th_lo, u32 *th_hi)
+{
+	int ret;
+	u32 regval;
+
+	ret = regmap_read(chip->regmap, OPT4060_THRESHOLD_LOW, &regval);
+	if (ret) {
+		dev_err(chip->dev, "Failed to read THRESHOLD_LOW.\n");
+		return ret;
+	}

drivers/iio/light/opt4060.c line 723

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	*th_lo = opt4060_calc_val_from_th_reg(regval);
+
+	ret = regmap_read(chip->regmap, OPT4060_THRESHOLD_HIGH, &regval);
+	if (ret) {
+		dev_err(chip->dev, "Failed to read THRESHOLD_LOW.\n");
+		return ret;
+	}
+	*th_hi = opt4060_calc_val_from_th_reg(regval);
+
+	return ret;
	return 0;
Quoted 35 lines
drivers/iio/light/opt4060.c new file +35 -0
+}
+
+static int opt4060_set_thresholds(struct opt4060_chip *chip, u32 th_lo, u32 th_hi)
+{
+	int ret;
+
+	ret = regmap_write(chip->regmap, OPT4060_THRESHOLD_LOW, opt4060_calc_th_reg(th_lo));
+	if (ret) {
+		dev_err(chip->dev, "Failed to write THRESHOLD_LOW.\n");
+		return ret;
+	}
+
+	ret = regmap_write(chip->regmap, OPT4060_THRESHOLD_HIGH, opt4060_calc_th_reg(th_hi));
+	if (ret)
+		dev_err(chip->dev, "Failed to write THRESHOLD_HIGH.\n");
+
+	return ret;
+}
+
+static int opt4060_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)
+{
+	int ret = -EINVAL;
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		if (chan->type == IIO_LIGHT && type == IIO_EV_TYPE_THRESH) {
+			u32 th_lo, th_hi;
+
+			if (opt4060_get_thresholds(chip, &th_lo, &th_hi))

drivers/iio/light/opt4060.c line 768

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				return -EFAULT;
+			if (dir == IIO_EV_DIR_FALLING) {
+				*val = th_lo;
+				ret = IIO_VAL_INT;
+			} else if (dir == IIO_EV_DIR_RISING) {
+				*val = th_hi;
+				ret = IIO_VAL_INT;
+			}
+		}
+		break;

Similar refactor to do early returns as suggested for the next function (see below)

Quoted 22 lines
drivers/iio/light/opt4060.c new file +22 -0
+	case IIO_EV_INFO_PERIOD:
+		return opt4060_read_ev_period(chip, val, val2);
+	default:
+		return -EINVAL;
+	}
+	return ret;
+}
+
+static int opt4060_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)
+{
+	int ret = -EINVAL;
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		if (chan->type == IIO_LIGHT && type == IIO_EV_TYPE_THRESH) {
+			u32 th_lo, th_hi;

drivers/iio/light/opt4060.c line 800

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+
+			if (opt4060_get_thresholds(chip, &th_lo, &th_hi))
+				return -EFAULT;
+			if (dir == IIO_EV_DIR_FALLING)
+				th_lo = val;
+			else if (dir == IIO_EV_DIR_RISING)
+				th_hi = val;
+			if (opt4060_set_thresholds(chip, th_lo, th_hi))
+				return -EFAULT;
+			ret = 0;
			return 0;

drivers/iio/light/opt4060.c

Quoted 1 line
drivers/iio/light/opt4060.c new file +1 -0
+		}
		return -EINVAL;

drivers/iio/light/opt4060.c line 808

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+		break;
+	case IIO_EV_INFO_PERIOD:
+		return opt4060_write_ev_period(chip, val, val2);
+	default:
+		return -EINVAL;
+	}
+	return ret;

Drop this as with above always returned already.

drivers/iio/light/opt4060.c line 819

Quoted 11 lines
drivers/iio/light/opt4060.c new file +11 -0
+}
+
+static int opt4060_read_event_config(struct iio_dev *indio_dev,
+				     const struct iio_chan_spec *chan,
+				     enum iio_event_type type,
+				     enum iio_event_direction dir)
+{
+	int ret = -EINVAL;
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+
+	if (chan->type == IIO_LIGHT && type == IIO_EV_TYPE_THRESH) {

As below. Consider flipping the condition for an early return Check all similar cases for whether it improves readability.

Quoted 14 lines
drivers/iio/light/opt4060.c new file +14 -0
+		int ch_idx = chan->scan_index;
+		int ch_sel;
+
+		if (opt4060_get_channel_sel(chip, &ch_sel))
+			return -EFAULT;
+
+		if (((dir == IIO_EV_DIR_FALLING) && chip->thresh_event_lo_active) ||
+		    ((dir == IIO_EV_DIR_RISING) && chip->thresh_event_hi_active))
+			ret = (ch_sel == ch_idx);
+		else
+			ret = FALSE;
+	}
+	return ret;
+}

drivers/iio/light/opt4060.c line 843

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+
+static int opt4060_write_event_config(struct iio_dev *indio_dev,
+				      const struct iio_chan_spec *chan,
+				      enum iio_event_type type,
+				      enum iio_event_direction dir, int state)
+{
+	int ret = -EINVAL;
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+
+	if (chan->type == IIO_LIGHT && type == IIO_EV_TYPE_THRESH) {

Maybe flip the condition to reduce effective indent.

	if (chan->type != IIO_LIGHT)
		return -EINVAL;
	if (type != IIO_EV_TYPE_THRESH)
		reuturn -EINVAL;

etc

Quoted 12 lines
drivers/iio/light/opt4060.c new file +12 -0
+		int ch_idx = chan->scan_index;
+		int ch_sel;
+
+		if (opt4060_get_channel_sel(chip, &ch_sel))
+			return -EFAULT;
+
+		if (state) {
+			/* Only one channel can be active at the same time */
+			if ((chip->thresh_event_lo_active ||
+			     chip->thresh_event_hi_active) && (ch_idx != ch_sel))
+				return -EBUSY;
+			if (dir == IIO_EV_DIR_FALLING)

drivers/iio/light/opt4060.c line 865

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				chip->thresh_event_lo_active = TRUE;
+			else if (dir == IIO_EV_DIR_RISING)
+				chip->thresh_event_hi_active = TRUE;
+			if (opt4060_set_channel_sel(chip, ch_idx))
+				return -EFAULT;
+			ret = 0;
+		} else {
+			if (ch_idx == ch_sel) {
+				if (dir == IIO_EV_DIR_FALLING)
+					chip->thresh_event_lo_active = FALSE;

false etc

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				else if (dir == IIO_EV_DIR_RISING)
+					chip->thresh_event_hi_active = FALSE;
+			}
+			ret = 0;
+		}
+
+		if (opt4060_event_set_state(chip, chip->thresh_event_hi_active |
+					    chip->thresh_event_lo_active))
+			return -EFAULT;
+	}

drivers/iio/light/opt4060.c line 909

Quoted 10 lines
drivers/iio/light/opt4060.c new file +9 -0
+	return ret;
+}
 
+
+static bool opt4060_volatile_reg(struct device *dev, unsigned int reg)
+{
+	if ((reg >= OPT4060_RED_MSB && reg <= OPT4060_CLEAR_LSB) ||
+	    reg == OPT4060_RES_CTRL)
+		return true;
+	return false;

As below, just return the condition.

drivers/iio/light/opt4060.c line 916

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+}
+
+static bool opt4060_writable_reg(struct device *dev, unsigned int reg)
+{
+	if (reg >= OPT4060_THRESHOLD_LOW || reg >= OPT4060_INT_CTRL)
+		return true;
+	return false;

return reg >=...

drivers/iio/light/opt4060.c line 923

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+}
+
+static bool opt4060_readonly_reg(struct device *dev, unsigned int reg)
+{
+	if (reg == OPT4060_DEVICE_ID)
+		return true;
+	return false;
	return reg == ..

drivers/iio/light/opt4060.c line 931

Quoted 8 lines
drivers/iio/light/opt4060.c new file +8 -0
+}
+
+static bool opt4060_readable_reg(struct device *dev, unsigned int reg)
+{
+	/* Volatile, writable and read-only registers are readable. */
+	if (opt4060_volatile_reg(dev, reg) || opt4060_writable_reg(dev, reg) ||
+	    opt4060_readonly_reg(dev, reg))
+		return TRUE;

true

drivers/iio/light/opt4060.c

Quoted 1 line
drivers/iio/light/opt4060.c new file +1 -0
+	return false;

return opt....

Quoted 6 lines
drivers/iio/light/opt4060.c new file +5 -0
+}
+
 
+
+static irqreturn_t opt4060_trigger_handler(int irq, void *p)
+{

drivers/iio/light/opt4060.c line 963

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	struct iio_poll_func *pf = p;
+	struct iio_dev *idev = pf->indio_dev;
+	struct opt4060_chip *chip = iio_priv(idev);
+	struct opt4060_buffer raw;
+	int ret, chan;
+	int i = 0;
+
+	memset(&raw, 0, sizeof(raw));
+
+	for_each_set_bit(chan, idev->active_scan_mask, idev->masklength) {

Try compiling this on latest togreg branch. We have removed direct access to masklength

There is a iio_for_each_active_channel() macro that handles this usage more cleanly.

Quoted 15 lines
drivers/iio/light/opt4060.c new file +15 -0
+		ret = opt4060_read_raw_value(chip,
+					     opt4060_channels[chan].address,
+					     &raw.chan[i++]);
+		if (ret) {
+			dev_err(chip->dev, "Reading raw channel data failed\n");
+			goto err_read;
+		}
+	}
+
+	iio_push_to_buffers_with_timestamp(idev, &raw, pf->timestamp);
+err_read:
+	iio_trigger_notify_done(idev->trig);
+	return IRQ_HANDLED;
+}
+

...

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+static int opt4060_setup_triggers(struct opt4060_chip *chip, struct iio_dev *idev)
+{
+	struct iio_trigger *data_trigger;
+	struct iio_trigger *threshold_trigger;
+	char *name;
+	int ret;
+

drivers/iio/light/opt4060.c line 1074

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	ret = devm_iio_triggered_buffer_setup(chip->dev, idev,
+					      &iio_pollfunc_store_time,
+					      opt4060_trigger_handler,
+					      &opt4060_buffer_ops);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+			     "iio_triggered_buffer_setup_ext FAIL\n");
+
+	data_trigger = devm_iio_trigger_alloc(chip->dev, "%s-data-ready-dev%d",
+				       idev->name, iio_device_id(idev));

Where it doesn't result in very long lines, align to just after (

That may mean you have more line breaks. If so that is fine.

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+	if (!data_trigger)
+		return -ENOMEM;

Slight preference for a blank line after error checks like this.

drivers/iio/light/opt4060.c line 1086

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	chip->data_trig = data_trigger;
+	data_trigger->ops = &opt4060_trigger_ops;
+	iio_trigger_set_drvdata(data_trigger, idev);
+	ret = devm_iio_trigger_register(chip->dev, data_trigger);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+				     "Data ready trigger registration failed\n");
+
+	threshold_trigger = devm_iio_trigger_alloc(chip->dev, "%s-threshold-dev%d",
+				       idev->name, iio_device_id(idev));

This needs some explanation. Why a threshold trigger rather than just using events? Superficially looks like you should just use the event interfaces.

drivers/iio/light/opt4060.c line 1096

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	if (!threshold_trigger)
+		return -ENOMEM;
+	chip->threshold_trig = threshold_trigger;
+	threshold_trigger->ops = &opt4060_trigger_ops;
+	iio_trigger_set_drvdata(threshold_trigger, idev);
+	ret = devm_iio_trigger_register(chip->dev, threshold_trigger);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+				     "Threshold trigger registration failed\n");
+

One blank line is plenty.

drivers/iio/light/opt4060.c line 1099

Quoted 3 lines
drivers/iio/light/opt4060.c new file +3 -0
+
+	name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-opt4060",
+			      dev_name(chip->dev));

Check for failure to allocate.

Quoted 14 lines
drivers/iio/light/opt4060.c new file +14 -0
+
+	ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread,
+					IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
+					IRQF_ONESHOT, name, idev);
+	if (ret)
+		return dev_err_probe(chip->dev, ret, "Could not request IRQ\n");
+
+	ret = regmap_write_bits(chip->regmap, OPT4060_INT_CTRL,
+				OPT4060_INT_CTRL_OUTPUT,
+				OPT4060_INT_CTRL_OUTPUT);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+				     "Failed to set interrupt as output\n");
+

drivers/iio/light/opt4060.c line 1123

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	return 0;
+}
+
+static int opt4060_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct opt4060_chip *chip;
+	struct iio_dev *indio_dev;
+	int ret;
+	uint dev_id;

unsigned int to match the regmap parameter type.

drivers/iio/light/opt4060.c line 1131

Quoted 8 lines
drivers/iio/light/opt4060.c new file +8 -0
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+
+	/* Request regulator */

very obvious. Drop the comment.

Quoted 18 lines
drivers/iio/light/opt4060.c new file +18 -0
+	ret = devm_regulator_get_enable(dev, "vdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable vdd supply\n");
+
+	chip->regmap = devm_regmap_init_i2c(client, &opt4060_regmap_config);
+	if (IS_ERR(chip->regmap))
+		return dev_err_probe(dev, PTR_ERR(chip->regmap),
+				     "regmap initialization failed\n");
+
+	chip->dev = dev;
+	chip->irq = client->irq;
+	init_completion(&chip->completion);
+
+	indio_dev->info = &opt4060_info;
+
+	ret = regmap_reinit_cache(chip->regmap, &opt4060_regmap_config);
+	if (ret)
+		return dev_err_probe(dev, ret,

drivers/iio/light/opt4060.c line 1159

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				     "failed to reinit regmap cache\n");
+
+	ret = regmap_read(chip->regmap, OPT4060_DEVICE_ID, &dev_id);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+			"Failed to read the device ID register\n");
+
+	dev_id = FIELD_GET(OPT4060_DEVICE_ID_MASK, dev_id);
+	if (dev_id != OPT4060_DEVICE_ID_VAL)
+		dev_warn(dev, "Device ID: %#04x unknown\n", dev_id);

Prefer dev_info() for this as it may well be a valid fallback compatible.

drivers/iio/light/opt4060.c line 1166

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+
+	indio_dev->channels = opt4060_channels;
+	indio_dev->num_channels = ARRAY_SIZE(opt4060_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->name = OPT_4060_DRV_NAME;
+
+	/* Initialize device with default settings */

As below. Comment is (rightly) obvious from the code, so don't have a comment.

drivers/iio/light/opt4060.c line 1172

Quoted 6 lines
drivers/iio/light/opt4060.c new file +6 -0
+	ret = opt4060_load_defaults(chip);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to set sensor defaults\n");
+
+	/* If irq is enabled, initialize triggers */

The comment doesn't add anything not obvious from the code. I'd drop it and any similar ones.

drivers/iio/light/opt4060.c line 1178

Quoted 6 lines
drivers/iio/light/opt4060.c new file +6 -0
+	if (chip->irq) {
+		ret = opt4060_setup_triggers(chip, indio_dev);
+		if (ret)
+			return ret;
+	} else {
+		dev_info(chip->dev, "No IRQ, events and triggers disabled\n");

Too noisy. At most dev_dbg. Probably not useful at all as it is fairly easy to see no irq was registered.

drivers/iio/light/opt4060.c line 1181

Quoted 3 lines
drivers/iio/light/opt4060.c new file +3 -0
+	}
+
+	ret = devm_add_action_or_reset(dev, opt4060_chip_off_action, chip);

This needs to be alongside whatever turned it on. Or if that is a side effect of later accesses (i.e. it is powered up only channel read) then add a comment to that affect.

drivers/iio/light/opt4060.c line 1191

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to setup power off action\n");
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+/*
+ * The compatible string determines which constants to use depending on
+ * opt4060 packaging

There is only one. So this comment is currently meaningless.

drivers/iio/light/opt4060.c line 1194

Quoted 3 lines

+ */

drivers/iio/light/opt4060.c new file +2 -0
+static const struct i2c_device_id opt4060_id[] = {
+	{ OPT_4060_DRV_NAME, 0 },

Put the string directly in here. There is no reason why this should match the DRV_NAME. Also drop the 0 as it is currently pointless and when you do introduce some data it should be a pointer anyway.

drivers/iio/light/opt4060.c line 1201

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, opt4060_id);
+
+static const struct of_device_id opt4060_of_match[] = {
+	{ .compatible = "ti,opt4060" },
+	{}

Trivial but I'm trying to get consistency in IIO (slowly) on { } for these terminators (so with the space).
I'd prefer to not introduce more instances of {}

Thanks,

Jonathan

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+};
+MODULE_DEVICE_TABLE(of, opt4060_of_match);
#5 lore raw

Re: [PATCH v2 2/2] iio: light: Add support for TI OPT4060 color sensor

Quoted 7 lines · Jonathan Cameron
Quoted 1 line · On Sat, 5 Oct 2024 18:51:19 +0200 Per-Daniel Olsson…

Add support for Texas Instruments OPT4060 RGBW Color sensor.

Hi Per-Daniel.

Thanks for your driver. Various comments inline.

Hi Jonathan.

Thank you for your comments. I will address and correct the inline comments in the next version and I will try to answer your two bigger questions here. I replied to your comments in the cover letter earlier but I forgot to answer them here also, as you requested.

Quoted 4 lines

The two bigger things I'd like to understand are:
1) Why we have a threshold trigger? That is unusual so I'd like to fully understand the use case. I see an explanation in your cover letter so I'll comment on that here but I would like one here as well.

Our userspace application needs the values after getting the threshold event. Before I implemented the threshold trigger and the triggered buffer, the application would read the values from sysfs right after the event. In that case the values will not be the ones that actually triggered the event. When the light condition is close to the threshold, the value from sysfs might even be on the wrong side of the threshold which can be confusing for the state machine in userspace. I would say that this feature is fairly important to us, this is the way our userspace is using the sensor.

Quoted 4 lines

2) Why do we need processed and raw? That may make sense but it is also unusual so I'd like a comment in this patch description on your reasoning.

The raw values are the actual ADC values from the sensor. Those values are the ones that are needed when setting thresholds, so the raw values are definitely needed. The processed values are a conversion to something that should be close to lux. This is not something I know much about, I just read the data sheet (https://www.ti.com/lit/gpn/opt4060).
According to chapter 8.4.5.2 (page 17), lux can be calculated this way:

lux = GREEN_ADC_RAW * 2.15e-3

It is also stated that R=G=B for a D65 standard white light source if red is multiplied by 2.4 and blue is multiplied with 1.3. We currently don't use the processed values in our use of the sensor, but I assume that other users will expect this functionality.

Quoted 2 lines

I've only raised some style issues in a few places. Please check to see if those apply more broadly.

I'll try to address the style issues broadly in the next version.

Best regards / P-D

Quoted 876 lines

Thanks,

Jonathan

Quoted 66 lines
---
 drivers/iio/light/Kconfig   |   13 +
 drivers/iio/light/Makefile  |    1 +
 drivers/iio/light/opt4060.c | 1216 +++++++++++++++++++++++++++++++++++
 3 files changed, 1230 insertions(+)
 create mode 100644 drivers/iio/light/opt4060.c
drivers/iio/light/Kconfig +13 -0
index 515ff46b5b82..0e2059512157 100644
@@ -490,6 +490,19 @@ config OPT4001
 	  If built as a dynamically linked module, it will be called
 	  opt4001.
 
+config OPT4060
+	tristate "Texas Instruments OPT4060 RGBW Color Sensor"
+	depends on I2C
+	select REGMAP_I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  If you say Y or M here, you get support for Texas Instruments
+	  OPT4060 RGBW Color Sensor.
+
+	  If built as a dynamically linked module, it will be called
+	  opt4060.
+
 config PA12203001
 	tristate "TXC PA12203001 light and proximity sensor"
 	depends on I2C
drivers/iio/light/Makefile +1 -0
index 321010fc0b93..55902b21ec0c 100644
@@ -42,6 +42,7 @@ obj-$(CONFIG_MAX44009)		+= max44009.o
 obj-$(CONFIG_NOA1305)		+= noa1305.o
 obj-$(CONFIG_OPT3001)		+= opt3001.o
 obj-$(CONFIG_OPT4001)		+= opt4001.o
+obj-$(CONFIG_OPT4060)		+= opt4060.o
 obj-$(CONFIG_PA12203001)	+= pa12203001.o
 obj-$(CONFIG_ROHM_BU27008)	+= rohm-bu27008.o
 obj-$(CONFIG_ROHM_BU27034)	+= rohm-bu27034.o
drivers/iio/light/opt4060.c new file +15 -0
new file mode 100644
index 000000000000..a917b59cd500
@@ -0,0 +1,1216 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Axis Communications AB
+ *
+ * Datasheet: https://www.ti.com/lit/gpn/opt4060
+ *
+ * Device driver for the Texas Instruments OPT4060 RGBW Color Sensor.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/math64.h>
+#include <linux/units.h>
+#include <linux/limits.h>

drivers/iio/light/opt4060.c line 25

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define OPT_4060_DRV_NAME	"opt4060"

I'm generally against these DRV_NAME type defines as they tend to imply there is a reason various strings in the driver match when it is an arbitrary choice. I'd rather see the string inline.

drivers/iio/light/opt4060.c line 148

Quoted 4 lines
drivers/iio/light/opt4060.c new file +4 -0
+
+struct opt4060_buffer {
+	u32 chan[OPT4060_NUM_CHANS];
+	s64 ts;

aligned_s64
Which is needed to cover the delights of 32 bit x86 where an s64 is 32bit aligned and this whole structure can end up aligned differently from the obvious.

Quoted 1 line
drivers/iio/light/opt4060.c new file +1 -0
+};

...

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+
+static u8 opt4060_calculate_crc(u8 exp, u32 mantissa, u8 count)

Good to include the formula here. I'm assuming this doesn't correspond to any of the standard crc codes for which we have implementations already.

Quoted 15 lines
drivers/iio/light/opt4060.c new file +13 -0
+{
+	u8 crc;
+
+	crc = (hweight32(mantissa) + hweight32(exp) + hweight32(count)) % 2;
+	crc |= ((hweight32(mantissa & 0xAAAAA) + hweight32(exp & 0xA)
+		 + hweight32(count & 0xA)) % 2) << 1;
+	crc |= ((hweight32(mantissa & 0x88888) + hweight32(exp & 0x8)
+		 + hweight32(count & 0x8)) % 2) << 2;
+	crc |= (hweight32(mantissa & 0x80808) % 2) << 3;
+
+	return crc;
+}
 
 
+

drivers/iio/light/opt4060.c

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+static int opt4060_read_raw_value(struct opt4060_chip *chip,
+				  unsigned long address, u32 *raw)
+{
+	int ret;
+	u32 result;
+	u16 msb;
+	u16 lsb;
+	u8 exp;
+	u8 count;
+	u8 crc;

Combine entrees of same type to save a few lines here

drivers/iio/light/opt4060.c line 312

Quoted 6 lines
drivers/iio/light/opt4060.c new file +6 -0
+	u8 calc_crc;
+	u32 mantissa_raw;
+
+	ret = regmap_bulk_read(chip->regmap, address, &result, 2);
+	if (ret)
+		dev_err(chip->dev, "Reading channel data failed\n");

return ret; and drop the else.

Also kernel coding style requires {} for all legs if any need it.

Quoted 25 lines
drivers/iio/light/opt4060.c new file +25 -0
+	else {
+		count = FIELD_GET(OPT4060_COUNTER_MASK, result >> 16);
+		crc = FIELD_GET(OPT4060_CRC_MASK, result >> 16);
+		lsb = FIELD_GET(OPT4060_LSB_MASK, result >> 16);
+		exp = FIELD_GET(OPT4060_EXPONENT_MASK, result);
+		msb = FIELD_GET(OPT4060_MSB_MASK, result);
+		mantissa_raw = (msb << 8) + lsb;
+		calc_crc = opt4060_calculate_crc(exp, mantissa_raw, count);
+		if (calc_crc != crc)
+			return -EIO;
+
+		*raw = mantissa_raw << exp;
+	}
+	return ret;
+}
+
+static int opt4060_read_chan_value(struct iio_dev *indio_dev,
+				   struct iio_chan_spec const *chan,
+				   int *val, int *val2, bool processed)
+{
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+	u32 adc_raw;
+	int ret;
+	/* Set timeout to two times the integration time multiplied by channel count. */
+	unsigned int timeout_us = 2 * OPT4060_NUM_CHANS *

drivers/iio/light/opt4060.c line 347

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				  opt4060_int_time_reg[chip->int_time][0];
+
+	if (chip->irq) {
+		reinit_completion(&chip->completion);
+		ret = opt4060_trigger_one_shot(chip);
+		if (ret)
+			return ret;
+		if (wait_for_completion_timeout(&chip->completion,
+						usecs_to_jiffies(timeout_us)) == 0)
+			dev_info(chip->dev, "Completion timed out.\n");

It's an error dev_err() appropriate.

drivers/iio/light/opt4060.c line 355

Quoted 8 lines
drivers/iio/light/opt4060.c new file +8 -0
+	}
+
+	ret = opt4060_read_raw_value(chip, chan->address, &adc_raw);
+	if (ret) {
+		dev_err(chip->dev, "Reading raw channel data failed\n");
+		return ret;
+	}
+	if (processed) {

Having both processed and raw for a channel needs an explanation. It's not helped by processed being meaningless for color channels as there are not really an well defined units.

drivers/iio/light/opt4060.c line 367

Quoted 12 lines
drivers/iio/light/opt4060.c new file +12 -0
+		u32 lux_raw;
+		u32 rem;
+		u32 mul = opt4060_channel_factors[chan->scan_index].mul;
+		u32 div = opt4060_channel_factors[chan->scan_index].div;
+
+		lux_raw = adc_raw * mul;
+		*val = div_u64_rem(lux_raw, div, &rem);
+		*val2 = rem * div_u64(div, 10);
+		return IIO_VAL_INT_PLUS_NANO;
+	}
+	*val = adc_raw;
+	*val2 = 0;

No need to set val2 if IIO_VAL_INT
The core code guarantees never to use it.

Quoted 6 lines
drivers/iio/light/opt4060.c new file +5 -0
+	return IIO_VAL_INT;
+}
 
+static int opt4060_power_down(struct opt4060_chip *chip)
+{
+	int ret;

drivers/iio/light/opt4060.c line 406

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	unsigned int reg;
+
+	ret = regmap_read(chip->regmap, OPT4060_CTRL, &reg);
+	if (ret) {
+		dev_err(chip->dev, "Failed to read configuration\n");
+		return ret;
+	}
+
+	/* MODE_OFF is 0x0 so just set bits to 0 */
+	reg &= ~OPT4060_CTRL_OPER_MODE_MASK;

regmap_clear_bits() maybe?

drivers/iio/light/opt4060.c line 417

Quoted 11 lines
drivers/iio/light/opt4060.c new file +11 -0
+
+	ret = regmap_write(chip->regmap, OPT4060_CTRL, reg);
+	if (ret)
+		dev_err(chip->dev, "Failed to power down\n");
+
+	return ret;
+}
+
+static void opt4060_chip_off_action(void *data)
+{
+	struct opt4060_chip *chip = data;

No need for the local variable. Just rename data to chip is enough.

Quoted 12 lines
drivers/iio/light/opt4060.c new file +12 -0
+
+	opt4060_power_down(chip);
+}
+
+#define OPT4060_CHAN(color)							\
+{										\
+	.type = IIO_LIGHT,							\
+	.modified = 1,								\
+	.channel2 = IIO_MOD_LIGHT_##color,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |			\
+			      BIT(IIO_CHAN_INFO_RAW),				\
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),			\

drivers/iio/light/opt4060.c line 439

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),	\
+	.address = OPT4060_##color##_MSB,					\
+	.scan_index = OPT4060_##color,						\
+	.scan_type = {								\
+		.sign = 'u',							\
+		.realbits = 32,							\
+		.storagebits = 32,						\
+		.endianness = IIO_LE,						\
+	},									\
+	.event_spec = opt4060_event_spec,					\

where you have an optional irq like here, have two sets of chan_spec and pick between them dependent on the availability of that irq.

If there is no irq, there should be no event attributes.

Quoted 34 lines
drivers/iio/light/opt4060.c new file +33 -0
+	.num_event_specs = ARRAY_SIZE(opt4060_event_spec),			\
+}
+
+static const struct iio_event_spec opt4060_event_spec[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				 BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				 BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_PERIOD),
+	},
+};
 
+
+static bool opt4060_event_active(struct opt4060_chip *chip)
+{
+	return chip->thresh_event_lo_active || chip->thresh_event_hi_active;
+}
+
+static int opt4060_read_available(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_INT_TIME:

drivers/iio/light/opt4060.c line 578

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+		*length = ARRAY_SIZE(opt4060_int_time_available) * 2;
+		*vals = (const int *)opt4060_int_time_available;
+		*type = IIO_VAL_INT_PLUS_MICRO;
+		return IIO_AVAIL_LIST;
+
+	default:
+		return -EINVAL;
+	}
+}
+

One blank line is enough. Check for other instances.

drivers/iio/light/opt4060.c line 585

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+
+static int opt4060_event_set_state(struct opt4060_chip *chip, bool state)
+{
+	int ret = 0;
+
+	if (state)
+		ret = opt4060_set_continous_mode(chip, true);

return opt...

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+	else if (!chip->data_trigger_active && chip->irq)
+		ret = opt4060_set_continous_mode(chip, false);

return opt...

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+
+	return ret;

Not an error?

Quoted 6 lines
drivers/iio/light/opt4060.c new file +6 -0
+}
+
+static int opt4060_trigger_set_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct opt4060_chip *chip = iio_priv(indio_dev);

drivers/iio/light/opt4060.c line 605

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	int ret = 0;
+
+	if (indio_dev->trig) {
+		iio_trigger_put(indio_dev->trig);
+		indio_dev->trig = NULL;
+	}
+
+	if (state) {
+		indio_dev->trig = iio_trigger_get(trig);
+		ret = opt4060_set_continous_mode(chip, true);

return opt...

drivers/iio/light/opt4060.c line 607

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+	} else if (!opt4060_event_active(chip) && chip->irq)
+		ret = opt4060_set_continous_mode(chip, false);

return opt

Quoted 11 lines
drivers/iio/light/opt4060.c new file +11 -0
+
+	if (trig == chip->data_trig) {
+		chip->data_trigger_active = state;
+		chip->threshold_trigger_active = !state;
+	} else if (trig == chip->threshold_trig) {
+		chip->data_trigger_active = !state;
+		chip->threshold_trigger_active = state;
+	}
+
+	return ret;
+}

drivers/iio/light/opt4060.c line 685

Quoted 10 lines
drivers/iio/light/opt4060.c new file +9 -0
 
+
+static int opt4060_get_channel_sel(struct opt4060_chip *chip, int *ch_sel)
+{
+	int ret;
+	u32 regval;
+
+	ret = regmap_read(chip->regmap, OPT4060_INT_CTRL, &regval);
+	if (ret)
+		dev_err(chip->dev, "Failed to get channel selection.\n");
		return ret;

drivers/iio/light/opt4060.c

Quoted 1 line
drivers/iio/light/opt4060.c new file +1 -0
+	else

no else needed.

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+		*ch_sel = FIELD_GET(OPT4060_INT_CTRL_THRESH_SEL, regval);
+	return ret;

return 0;
Check for other cases that look like this. Early returns on error almost always make for more readable code than keeping the return for the end (After doing nothing useful extra)

Quoted 25 lines
drivers/iio/light/opt4060.c new file +25 -0
+}
+
+static int opt4060_set_channel_sel(struct opt4060_chip *chip, int ch_sel)
+{
+	int ret;
+	u32 regval;
+
+	regval = FIELD_PREP(OPT4060_INT_CTRL_THRESH_SEL, ch_sel);
+	ret = regmap_update_bits(chip->regmap, OPT4060_INT_CTRL,
+				 OPT4060_INT_CTRL_THRESH_SEL, regval);
+	if (ret)
+		dev_err(chip->dev, "Failed to set channel selection.\n");
+	return ret;
+}
+
+static int opt4060_get_thresholds(struct opt4060_chip *chip, u32 *th_lo, u32 *th_hi)
+{
+	int ret;
+	u32 regval;
+
+	ret = regmap_read(chip->regmap, OPT4060_THRESHOLD_LOW, &regval);
+	if (ret) {
+		dev_err(chip->dev, "Failed to read THRESHOLD_LOW.\n");
+		return ret;
+	}

drivers/iio/light/opt4060.c line 723

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	*th_lo = opt4060_calc_val_from_th_reg(regval);
+
+	ret = regmap_read(chip->regmap, OPT4060_THRESHOLD_HIGH, &regval);
+	if (ret) {
+		dev_err(chip->dev, "Failed to read THRESHOLD_LOW.\n");
+		return ret;
+	}
+	*th_hi = opt4060_calc_val_from_th_reg(regval);
+
+	return ret;
	return 0;
Quoted 35 lines
drivers/iio/light/opt4060.c new file +35 -0
+}
+
+static int opt4060_set_thresholds(struct opt4060_chip *chip, u32 th_lo, u32 th_hi)
+{
+	int ret;
+
+	ret = regmap_write(chip->regmap, OPT4060_THRESHOLD_LOW, opt4060_calc_th_reg(th_lo));
+	if (ret) {
+		dev_err(chip->dev, "Failed to write THRESHOLD_LOW.\n");
+		return ret;
+	}
+
+	ret = regmap_write(chip->regmap, OPT4060_THRESHOLD_HIGH, opt4060_calc_th_reg(th_hi));
+	if (ret)
+		dev_err(chip->dev, "Failed to write THRESHOLD_HIGH.\n");
+
+	return ret;
+}
+
+static int opt4060_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)
+{
+	int ret = -EINVAL;
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		if (chan->type == IIO_LIGHT && type == IIO_EV_TYPE_THRESH) {
+			u32 th_lo, th_hi;
+
+			if (opt4060_get_thresholds(chip, &th_lo, &th_hi))

drivers/iio/light/opt4060.c line 768

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				return -EFAULT;
+			if (dir == IIO_EV_DIR_FALLING) {
+				*val = th_lo;
+				ret = IIO_VAL_INT;
+			} else if (dir == IIO_EV_DIR_RISING) {
+				*val = th_hi;
+				ret = IIO_VAL_INT;
+			}
+		}
+		break;

Similar refactor to do early returns as suggested for the next function (see below)

Quoted 22 lines
drivers/iio/light/opt4060.c new file +22 -0
+	case IIO_EV_INFO_PERIOD:
+		return opt4060_read_ev_period(chip, val, val2);
+	default:
+		return -EINVAL;
+	}
+	return ret;
+}
+
+static int opt4060_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)
+{
+	int ret = -EINVAL;
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		if (chan->type == IIO_LIGHT && type == IIO_EV_TYPE_THRESH) {
+			u32 th_lo, th_hi;

drivers/iio/light/opt4060.c line 800

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+
+			if (opt4060_get_thresholds(chip, &th_lo, &th_hi))
+				return -EFAULT;
+			if (dir == IIO_EV_DIR_FALLING)
+				th_lo = val;
+			else if (dir == IIO_EV_DIR_RISING)
+				th_hi = val;
+			if (opt4060_set_thresholds(chip, th_lo, th_hi))
+				return -EFAULT;
+			ret = 0;
			return 0;

drivers/iio/light/opt4060.c

Quoted 1 line
drivers/iio/light/opt4060.c new file +1 -0
+		}
		return -EINVAL;

drivers/iio/light/opt4060.c line 808

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+		break;
+	case IIO_EV_INFO_PERIOD:
+		return opt4060_write_ev_period(chip, val, val2);
+	default:
+		return -EINVAL;
+	}
+	return ret;

Drop this as with above always returned already.

drivers/iio/light/opt4060.c line 819

Quoted 11 lines
drivers/iio/light/opt4060.c new file +11 -0
+}
+
+static int opt4060_read_event_config(struct iio_dev *indio_dev,
+				     const struct iio_chan_spec *chan,
+				     enum iio_event_type type,
+				     enum iio_event_direction dir)
+{
+	int ret = -EINVAL;
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+
+	if (chan->type == IIO_LIGHT && type == IIO_EV_TYPE_THRESH) {

As below. Consider flipping the condition for an early return Check all similar cases for whether it improves readability.

Quoted 14 lines
drivers/iio/light/opt4060.c new file +14 -0
+		int ch_idx = chan->scan_index;
+		int ch_sel;
+
+		if (opt4060_get_channel_sel(chip, &ch_sel))
+			return -EFAULT;
+
+		if (((dir == IIO_EV_DIR_FALLING) && chip->thresh_event_lo_active) ||
+		    ((dir == IIO_EV_DIR_RISING) && chip->thresh_event_hi_active))
+			ret = (ch_sel == ch_idx);
+		else
+			ret = FALSE;
+	}
+	return ret;
+}

drivers/iio/light/opt4060.c line 843

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+
+static int opt4060_write_event_config(struct iio_dev *indio_dev,
+				      const struct iio_chan_spec *chan,
+				      enum iio_event_type type,
+				      enum iio_event_direction dir, int state)
+{
+	int ret = -EINVAL;
+	struct opt4060_chip *chip = iio_priv(indio_dev);
+
+	if (chan->type == IIO_LIGHT && type == IIO_EV_TYPE_THRESH) {

Maybe flip the condition to reduce effective indent.

	if (chan->type != IIO_LIGHT)
		return -EINVAL;
	if (type != IIO_EV_TYPE_THRESH)
		reuturn -EINVAL;

etc

Quoted 12 lines
drivers/iio/light/opt4060.c new file +12 -0
+		int ch_idx = chan->scan_index;
+		int ch_sel;
+
+		if (opt4060_get_channel_sel(chip, &ch_sel))
+			return -EFAULT;
+
+		if (state) {
+			/* Only one channel can be active at the same time */
+			if ((chip->thresh_event_lo_active ||
+			     chip->thresh_event_hi_active) && (ch_idx != ch_sel))
+				return -EBUSY;
+			if (dir == IIO_EV_DIR_FALLING)

drivers/iio/light/opt4060.c line 865

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				chip->thresh_event_lo_active = TRUE;
+			else if (dir == IIO_EV_DIR_RISING)
+				chip->thresh_event_hi_active = TRUE;
+			if (opt4060_set_channel_sel(chip, ch_idx))
+				return -EFAULT;
+			ret = 0;
+		} else {
+			if (ch_idx == ch_sel) {
+				if (dir == IIO_EV_DIR_FALLING)
+					chip->thresh_event_lo_active = FALSE;

false etc

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				else if (dir == IIO_EV_DIR_RISING)
+					chip->thresh_event_hi_active = FALSE;
+			}
+			ret = 0;
+		}
+
+		if (opt4060_event_set_state(chip, chip->thresh_event_hi_active |
+					    chip->thresh_event_lo_active))
+			return -EFAULT;
+	}

drivers/iio/light/opt4060.c line 909

Quoted 10 lines
drivers/iio/light/opt4060.c new file +9 -0
+	return ret;
+}
 
+
+static bool opt4060_volatile_reg(struct device *dev, unsigned int reg)
+{
+	if ((reg >= OPT4060_RED_MSB && reg <= OPT4060_CLEAR_LSB) ||
+	    reg == OPT4060_RES_CTRL)
+		return true;
+	return false;

As below, just return the condition.

drivers/iio/light/opt4060.c line 916

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+}
+
+static bool opt4060_writable_reg(struct device *dev, unsigned int reg)
+{
+	if (reg >= OPT4060_THRESHOLD_LOW || reg >= OPT4060_INT_CTRL)
+		return true;
+	return false;

return reg >=...

drivers/iio/light/opt4060.c line 923

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+}
+
+static bool opt4060_readonly_reg(struct device *dev, unsigned int reg)
+{
+	if (reg == OPT4060_DEVICE_ID)
+		return true;
+	return false;
	return reg == ..

drivers/iio/light/opt4060.c line 931

Quoted 8 lines
drivers/iio/light/opt4060.c new file +8 -0
+}
+
+static bool opt4060_readable_reg(struct device *dev, unsigned int reg)
+{
+	/* Volatile, writable and read-only registers are readable. */
+	if (opt4060_volatile_reg(dev, reg) || opt4060_writable_reg(dev, reg) ||
+	    opt4060_readonly_reg(dev, reg))
+		return TRUE;

true

drivers/iio/light/opt4060.c

Quoted 1 line
drivers/iio/light/opt4060.c new file +1 -0
+	return false;

return opt....

Quoted 6 lines
drivers/iio/light/opt4060.c new file +5 -0
+}
+
 
+
+static irqreturn_t opt4060_trigger_handler(int irq, void *p)
+{

drivers/iio/light/opt4060.c line 963

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	struct iio_poll_func *pf = p;
+	struct iio_dev *idev = pf->indio_dev;
+	struct opt4060_chip *chip = iio_priv(idev);
+	struct opt4060_buffer raw;
+	int ret, chan;
+	int i = 0;
+
+	memset(&raw, 0, sizeof(raw));
+
+	for_each_set_bit(chan, idev->active_scan_mask, idev->masklength) {

Try compiling this on latest togreg branch. We have removed direct access to masklength

There is a iio_for_each_active_channel() macro that handles this usage more cleanly.

Quoted 15 lines
drivers/iio/light/opt4060.c new file +15 -0
+		ret = opt4060_read_raw_value(chip,
+					     opt4060_channels[chan].address,
+					     &raw.chan[i++]);
+		if (ret) {
+			dev_err(chip->dev, "Reading raw channel data failed\n");
+			goto err_read;
+		}
+	}
+
+	iio_push_to_buffers_with_timestamp(idev, &raw, pf->timestamp);
+err_read:
+	iio_trigger_notify_done(idev->trig);
+	return IRQ_HANDLED;
+}
+

...

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+static int opt4060_setup_triggers(struct opt4060_chip *chip, struct iio_dev *idev)
+{
+	struct iio_trigger *data_trigger;
+	struct iio_trigger *threshold_trigger;
+	char *name;
+	int ret;
+

drivers/iio/light/opt4060.c line 1074

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	ret = devm_iio_triggered_buffer_setup(chip->dev, idev,
+					      &iio_pollfunc_store_time,
+					      opt4060_trigger_handler,
+					      &opt4060_buffer_ops);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+			     "iio_triggered_buffer_setup_ext FAIL\n");
+
+	data_trigger = devm_iio_trigger_alloc(chip->dev, "%s-data-ready-dev%d",
+				       idev->name, iio_device_id(idev));

Where it doesn't result in very long lines, align to just after (

That may mean you have more line breaks. If so that is fine.

drivers/iio/light/opt4060.c

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+	if (!data_trigger)
+		return -ENOMEM;

Slight preference for a blank line after error checks like this.

drivers/iio/light/opt4060.c line 1086

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	chip->data_trig = data_trigger;
+	data_trigger->ops = &opt4060_trigger_ops;
+	iio_trigger_set_drvdata(data_trigger, idev);
+	ret = devm_iio_trigger_register(chip->dev, data_trigger);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+				     "Data ready trigger registration failed\n");
+
+	threshold_trigger = devm_iio_trigger_alloc(chip->dev, "%s-threshold-dev%d",
+				       idev->name, iio_device_id(idev));

This needs some explanation. Why a threshold trigger rather than just using events? Superficially looks like you should just use the event interfaces.

drivers/iio/light/opt4060.c line 1096

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	if (!threshold_trigger)
+		return -ENOMEM;
+	chip->threshold_trig = threshold_trigger;
+	threshold_trigger->ops = &opt4060_trigger_ops;
+	iio_trigger_set_drvdata(threshold_trigger, idev);
+	ret = devm_iio_trigger_register(chip->dev, threshold_trigger);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+				     "Threshold trigger registration failed\n");
+

One blank line is plenty.

drivers/iio/light/opt4060.c line 1099

Quoted 3 lines
drivers/iio/light/opt4060.c new file +3 -0
+
+	name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-opt4060",
+			      dev_name(chip->dev));

Check for failure to allocate.

Quoted 14 lines
drivers/iio/light/opt4060.c new file +14 -0
+
+	ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread,
+					IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
+					IRQF_ONESHOT, name, idev);
+	if (ret)
+		return dev_err_probe(chip->dev, ret, "Could not request IRQ\n");
+
+	ret = regmap_write_bits(chip->regmap, OPT4060_INT_CTRL,
+				OPT4060_INT_CTRL_OUTPUT,
+				OPT4060_INT_CTRL_OUTPUT);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+				     "Failed to set interrupt as output\n");
+

drivers/iio/light/opt4060.c line 1123

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	return 0;
+}
+
+static int opt4060_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct opt4060_chip *chip;
+	struct iio_dev *indio_dev;
+	int ret;
+	uint dev_id;

unsigned int to match the regmap parameter type.

drivers/iio/light/opt4060.c line 1131

Quoted 8 lines
drivers/iio/light/opt4060.c new file +8 -0
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+
+	/* Request regulator */

very obvious. Drop the comment.

Quoted 18 lines
drivers/iio/light/opt4060.c new file +18 -0
+	ret = devm_regulator_get_enable(dev, "vdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable vdd supply\n");
+
+	chip->regmap = devm_regmap_init_i2c(client, &opt4060_regmap_config);
+	if (IS_ERR(chip->regmap))
+		return dev_err_probe(dev, PTR_ERR(chip->regmap),
+				     "regmap initialization failed\n");
+
+	chip->dev = dev;
+	chip->irq = client->irq;
+	init_completion(&chip->completion);
+
+	indio_dev->info = &opt4060_info;
+
+	ret = regmap_reinit_cache(chip->regmap, &opt4060_regmap_config);
+	if (ret)
+		return dev_err_probe(dev, ret,

drivers/iio/light/opt4060.c line 1159

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+				     "failed to reinit regmap cache\n");
+
+	ret = regmap_read(chip->regmap, OPT4060_DEVICE_ID, &dev_id);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+			"Failed to read the device ID register\n");
+
+	dev_id = FIELD_GET(OPT4060_DEVICE_ID_MASK, dev_id);
+	if (dev_id != OPT4060_DEVICE_ID_VAL)
+		dev_warn(dev, "Device ID: %#04x unknown\n", dev_id);

Prefer dev_info() for this as it may well be a valid fallback compatible.

drivers/iio/light/opt4060.c line 1166

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+
+	indio_dev->channels = opt4060_channels;
+	indio_dev->num_channels = ARRAY_SIZE(opt4060_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->name = OPT_4060_DRV_NAME;
+
+	/* Initialize device with default settings */

As below. Comment is (rightly) obvious from the code, so don't have a comment.

drivers/iio/light/opt4060.c line 1172

Quoted 6 lines
drivers/iio/light/opt4060.c new file +6 -0
+	ret = opt4060_load_defaults(chip);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to set sensor defaults\n");
+
+	/* If irq is enabled, initialize triggers */

The comment doesn't add anything not obvious from the code. I'd drop it and any similar ones.

drivers/iio/light/opt4060.c line 1178

Quoted 6 lines
drivers/iio/light/opt4060.c new file +6 -0
+	if (chip->irq) {
+		ret = opt4060_setup_triggers(chip, indio_dev);
+		if (ret)
+			return ret;
+	} else {
+		dev_info(chip->dev, "No IRQ, events and triggers disabled\n");

Too noisy. At most dev_dbg. Probably not useful at all as it is fairly easy to see no irq was registered.

drivers/iio/light/opt4060.c line 1181

Quoted 3 lines
drivers/iio/light/opt4060.c new file +3 -0
+	}
+
+	ret = devm_add_action_or_reset(dev, opt4060_chip_off_action, chip);

This needs to be alongside whatever turned it on. Or if that is a side effect of later accesses (i.e. it is powered up only channel read) then add a comment to that affect.

drivers/iio/light/opt4060.c line 1191

Quoted 10 lines
drivers/iio/light/opt4060.c new file +10 -0
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to setup power off action\n");
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+/*
+ * The compatible string determines which constants to use depending on
+ * opt4060 packaging

There is only one. So this comment is currently meaningless.

drivers/iio/light/opt4060.c line 1194

Quoted 3 lines

+ */

drivers/iio/light/opt4060.c new file +2 -0
+static const struct i2c_device_id opt4060_id[] = {
+	{ OPT_4060_DRV_NAME, 0 },

Put the string directly in here. There is no reason why this should match the DRV_NAME. Also drop the 0 as it is currently pointless and when you do introduce some data it should be a pointer anyway.

drivers/iio/light/opt4060.c line 1201

Quoted 7 lines
drivers/iio/light/opt4060.c new file +7 -0
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, opt4060_id);
+
+static const struct of_device_id opt4060_of_match[] = {
+	{ .compatible = "ti,opt4060" },
+	{}

Trivial but I'm trying to get consistency in IIO (slowly) on { } for these terminators (so with the space).
I'd prefer to not introduce more instances of {}

Thanks,

Jonathan

Quoted 2 lines
drivers/iio/light/opt4060.c new file +2 -0
+};
+MODULE_DEVICE_TABLE(of, opt4060_of_match);
#6 lore raw

Re: [PATCH v2 2/2] iio: light: Add support for TI OPT4060 color sensor

In reply to Per-Daniel Olsson
kernel test robot noticed the following build warnings:

Hi Per-Daniel,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 0c559323bbaabee7346c12e74b497e283aaafef5]

url:    https://github.com/intel-lab-lkp/linux/commits/Per-Daniel-Olsson/dt-bindings-iio-light-Document-TI-OPT4060-RGBW-sensor/20241006-005244
base:   0c559323bbaabee7346c12e74b497e283aaafef5
patch link:    https://lore.kernel.org/r/20241005165119.3549472-3-perdaniel.olsson%40axis.com
patch subject: [PATCH v2 2/2] iio: light: Add support for TI OPT4060 color sensor
config: um-allmodconfig (https://download.01.org/0day-ci/archive/20241007/[email protected]/config)
compiler: clang version 20.0.0git (https://github.com/llvm/llvm-project fef3566a25ff0e34fb87339ba5e13eca17cec00f)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241007/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

In file included from drivers/iio/light/opt4060.c:11:
In file included from include/linux/i2c.h:19:
In file included from include/linux/regulator/consumer.h:35:
In file included from include/linux/suspend.h:5:
In file included from include/linux/swap.h:9:
In file included from include/linux/memcontrol.h:13:
In file included from include/linux/cgroup.h:26:
In file included from include/linux/kernel_stat.h:8:
In file included from include/linux/interrupt.h:11:
In file included from include/linux/hardirq.h:11:
In file included from arch/um/include/asm/hardirq.h:5:
In file included from include/asm-generic/hardirq.h:17:
In file included from include/linux/irq.h:20:
In file included from include/linux/io.h:14:
In file included from arch/um/include/asm/io.h:24:
include/asm-generic/io.h:548:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]

     548 |         val = __raw_readb(PCI_IOBASE + addr);
         |                           ~~~~~~~~~~ ^
   include/asm-generic/io.h:561:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     561 |         val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
         |                                                         ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/little_endian.h:37:51: note: expanded from macro '__le16_to_cpu'
      37 | #define __le16_to_cpu(x) ((__force __u16)(__le16)(x))
         |                                                   ^
   In file included from drivers/iio/light/opt4060.c:11:
   In file included from include/linux/i2c.h:19:
   In file included from include/linux/regulator/consumer.h:35:
   In file included from include/linux/suspend.h:5:
   In file included from include/linux/swap.h:9:
   In file included from include/linux/memcontrol.h:13:
   In file included from include/linux/cgroup.h:26:
   In file included from include/linux/kernel_stat.h:8:
   In file included from include/linux/interrupt.h:11:
   In file included from include/linux/hardirq.h:11:
   In file included from arch/um/include/asm/hardirq.h:5:
   In file included from include/asm-generic/hardirq.h:17:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:14:
   In file included from arch/um/include/asm/io.h:24:
   include/asm-generic/io.h:574:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     574 |         val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
         |                                                         ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/little_endian.h:35:51: note: expanded from macro '__le32_to_cpu'
      35 | #define __le32_to_cpu(x) ((__force __u32)(__le32)(x))
         |                                                   ^
   In file included from drivers/iio/light/opt4060.c:11:
   In file included from include/linux/i2c.h:19:
   In file included from include/linux/regulator/consumer.h:35:
   In file included from include/linux/suspend.h:5:
   In file included from include/linux/swap.h:9:
   In file included from include/linux/memcontrol.h:13:
   In file included from include/linux/cgroup.h:26:
   In file included from include/linux/kernel_stat.h:8:
   In file included from include/linux/interrupt.h:11:
   In file included from include/linux/hardirq.h:11:
   In file included from arch/um/include/asm/hardirq.h:5:
   In file included from include/asm-generic/hardirq.h:17:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:14:
   In file included from arch/um/include/asm/io.h:24:
   include/asm-generic/io.h:585:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     585 |         __raw_writeb(value, PCI_IOBASE + addr);
         |                             ~~~~~~~~~~ ^
   include/asm-generic/io.h:595:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     595 |         __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
         |                                                       ~~~~~~~~~~ ^
   include/asm-generic/io.h:605:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     605 |         __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
         |                                                       ~~~~~~~~~~ ^
   include/asm-generic/io.h:693:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     693 |         readsb(PCI_IOBASE + addr, buffer, count);
         |                ~~~~~~~~~~ ^
   include/asm-generic/io.h:701:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     701 |         readsw(PCI_IOBASE + addr, buffer, count);
         |                ~~~~~~~~~~ ^
   include/asm-generic/io.h:709:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     709 |         readsl(PCI_IOBASE + addr, buffer, count);
         |                ~~~~~~~~~~ ^
   include/asm-generic/io.h:718:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     718 |         writesb(PCI_IOBASE + addr, buffer, count);
         |                 ~~~~~~~~~~ ^
   include/asm-generic/io.h:727:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     727 |         writesw(PCI_IOBASE + addr, buffer, count);
         |                 ~~~~~~~~~~ ^
   include/asm-generic/io.h:736:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     736 |         writesl(PCI_IOBASE + addr, buffer, count);
         |                 ~~~~~~~~~~ ^
   In file included from drivers/iio/light/opt4060.c:11:
   In file included from include/linux/i2c.h:19:
   In file included from include/linux/regulator/consumer.h:35:
   In file included from include/linux/suspend.h:5:
   In file included from include/linux/swap.h:9:
   In file included from include/linux/memcontrol.h:21:
   In file included from include/linux/mm.h:2213:
   include/linux/vmstat.h:518:36: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion]
     518 |         return node_stat_name(NR_LRU_BASE + lru) + 3; // skip "nr_"
         |                               ~~~~~~~~~~~ ^ ~~~
>> drivers/iio/light/opt4060.c:1026:8: warning: variable 'chan' is uninitialized when used here [-Wuninitialized]
    1026 |                                                         chan,
         |                                                         ^~~~
   include/linux/iio/events.h:54:51: note: expanded from macro 'IIO_UNMOD_EVENT_CODE'
      54 |         IIO_EVENT_CODE(chan_type, 0, 0, direction, type, number, 0, 0)
         |                                                          ^~~~~~
   include/linux/iio/events.h:29:9: note: expanded from macro 'IIO_EVENT_CODE'
      29 |          ((u16)chan))
         |                ^~~~
   drivers/iio/light/opt4060.c:994:15: note: initialize the variable 'chan' to silence this warning
     994 |         int ret, chan, dummy;
         |                      ^
         |                       = 0

14 warnings generated.

Kconfig warnings: (for reference only)
WARNING: unmet direct dependencies detected for MODVERSIONS
Depends on [n]: MODULES [=y] && !COMPILE_TEST [=y]
Selected by [y]:
- RANDSTRUCT_FULL [=y] && (CC_HAS_RANDSTRUCT [=y] || GCC_PLUGINS [=n]) && MODULES [=y]
WARNING: unmet direct dependencies detected for GET_FREE_REGION
Depends on [n]: SPARSEMEM [=n]
Selected by [m]:
- RESOURCE_KUNIT_TEST [=m] && RUNTIME_TESTING_MENU [=y] && KUNIT [=m]

vim +/chan +1026 drivers/iio/light/opt4060.c

989
990 static irqreturn_t opt4060_irq_thread(int irq, void *private)
991 {
992 struct iio_dev *idev = private;
993 struct opt4060_chip *chip = iio_priv(idev);
994 int ret, chan, dummy;
995 unsigned int int_res;
996
997 ret = regmap_read(chip->regmap, OPT4060_RES_CTRL, &int_res);
998 if (ret < 0) {
999 dev_err(chip->dev, "Failed to read interrupt reasons.\n");
1000 return IRQ_NONE;
1001 }
1002
1003 if (!chip->data_trigger_active) {
1004 /* Only trigger interrupts on thresholds */
1005 ret = opt4060_set_int_state(chip, OPT4060_INT_CTRL_THRESHOLD);
1006 if (ret) {
1007 dev_err(chip->dev, "Failed to set interrupt state\n");
1008 return IRQ_NONE;
1009 }
1010 }
1011
1012 /* Read OPT4060_CTRL to clear interrupt */
1013 ret = regmap_read(chip->regmap, OPT4060_CTRL, &dummy);
1014 if (ret < 0) {
1015 dev_err(chip->dev, "Failed to clear interrupt\n");
1016 return IRQ_NONE;
1017 }
1018
1019 /* Handle events */
1020 if (int_res & (OPT4060_RES_CTRL_FLAG_H | OPT4060_RES_CTRL_FLAG_L)) {
1021 u64 code;
1022
1023 /* Check if the interrupt is from the lower threshold */
1024 if (int_res & OPT4060_RES_CTRL_FLAG_L) {
1025 code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT,

> 1026								chan,

1027 IIO_EV_TYPE_THRESH,
1028 IIO_EV_DIR_FALLING);
1029 iio_push_event(idev, code,
1030 iio_get_time_ns(idev));
1031 }
1032 /* Check if the interrupt is from the upper threshold */
1033 if (int_res & OPT4060_RES_CTRL_FLAG_H) {
1034 code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
1035 chan,
1036 IIO_EV_TYPE_THRESH,
1037 IIO_EV_DIR_RISING);
1038 iio_push_event(idev, code,
1039 iio_get_time_ns(idev));
1040 }
1041 /* Handle threshold triggers */
1042 if (chip->threshold_trigger_active && iio_buffer_enabled(idev))
1043 iio_trigger_poll_nested(chip->threshold_trig);
1044 }
1045
1046 /* Handle conversion ready */
1047 if (int_res & OPT4060_RES_CTRL_CONV_READY) {
1048 /* Signal completion for potentially waiting reads */
1049 complete(&chip->completion);
1050
1051 /* Handle data ready triggers */
1052 if (chip->data_trigger_active && iio_buffer_enabled(idev))
1053 iio_trigger_poll_nested(chip->data_trig);
1054 }
1055 return IRQ_HANDLED;
1056 }
1057

Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
#7 lore raw

Re: [PATCH v2 2/2] iio: light: Add support for TI OPT4060 color sensor

In reply to Per-Daniel Olsson
kernel test robot noticed the following build warnings:

Hi Per-Daniel,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 0c559323bbaabee7346c12e74b497e283aaafef5]

url:    https://github.com/intel-lab-lkp/linux/commits/Per-Daniel-Olsson/dt-bindings-iio-light-Document-TI-OPT4060-RGBW-sensor/20241006-005244
base:   0c559323bbaabee7346c12e74b497e283aaafef5
patch link:    https://lore.kernel.org/r/20241005165119.3549472-3-perdaniel.olsson%40axis.com
patch subject: [PATCH v2 2/2] iio: light: Add support for TI OPT4060 color sensor
config: csky-randconfig-r122-20241007 (https://download.01.org/0day-ci/archive/20241007/[email protected]/config)
compiler: csky-linux-gcc (GCC) 14.1.0
reproduce: (https://download.01.org/0day-ci/archive/20241007/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

sparse warnings: (new ones prefixed by >>)

>> drivers/iio/light/opt4060.c:963:9: sparse: sparse: dereference of noderef expression
>> drivers/iio/light/opt4060.c:963:9: sparse: sparse: dereference of noderef expression
>> drivers/iio/light/opt4060.c:963:9: sparse: sparse: dereference of noderef expression

vim +963 drivers/iio/light/opt4060.c

951
952 static irqreturn_t opt4060_trigger_handler(int irq, void *p)
953 {
954 struct iio_poll_func *pf = p;
955 struct iio_dev *idev = pf->indio_dev;
956 struct opt4060_chip *chip = iio_priv(idev);
957 struct opt4060_buffer raw;
958 int ret, chan;
959 int i = 0;
960
961 memset(&raw, 0, sizeof(raw));
962

 > 963		for_each_set_bit(chan, idev->active_scan_mask, idev->masklength) {

964 ret = opt4060_read_raw_value(chip,
965 opt4060_channels[chan].address,
966 &raw.chan[i++]);
967 if (ret) {
968 dev_err(chip->dev, "Reading raw channel data failed\n");
969 goto err_read;
970 }
971 }
972
973 iio_push_to_buffers_with_timestamp(idev, &raw, pf->timestamp);
974 err_read:
975 iio_trigger_notify_done(idev->trig);
976 return IRQ_HANDLED;
977 }
978

Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
maintainer
#8 lore raw
In reply to Per-Daniel Olsson
Quoted 5 lines · On Sat, 5 Oct 2024 18:51:17 +0200 Per-Daniel Olsson…

This patch series adds support for Texas Instruments OPT4060 RGBW Color sensor using the i2c interface.

The driver exposes both raw adc values and calculated lux values though sysfs. Integration time can be configured though sysfs as well.

Lux is a unit with a particular light curve. It has no defined meaning for color channels. As a result we usually only have colors as intensity channels (unit free). If it is possible to compute an estimate of the illuminance then that can be a separate IIO_LIGHT channel.

Quoted 10 lines

The OPT4060 sensor
supports both rising and falling threshold interrupts. These interrupts are exposed as IIO events. The driver also implements an IIO triggered buffer with two triggers, one trigger for conversion ready interrupts and one trigger for threshold interrupts. The typical use case for this is to define a threshold and listen for the events, and at the same time enable the triggered buffer with the threshold trigger. Once the application gets the threshold event, the values from the time of the event will be available in the triggered buffer. This limits the number of interrupts between sensor and host and also the the usage of sysfs for reading values after events.

We have had various discussions of threshold triggers in the past, but I don't think we ever merged any (maybe there is one somewhere?) The reasons for that are:
1) They are hard for generic userspace to understand.
2) For light sensors the input tends to be slow moving - grabbing one reading on a threshold interrupt is rarely that informative (or are you grabbing them on dataready once the threshold is breached?)
3) If we want to do threshold triggers we should really add generic infrastructure for them based on adding an in kernel events consumer path and a way to instantiate a trigger based on any event. Doing it in a single driver creates an ABI we won't want to retain long term.

So how important is this feature to you and why? Maybe it is time to finally take a close look at option 3.

Jonathan

Quoted 24 lines

Changes in v2:
- dt-bindings: Removed incorrect allOf.
- dt-bindings: Changed to generic node name.
- Correction in opt4060_trigger_one_shot(...) for continuous mode.
- Correction in opt4060_power_down(...), wrong register was read.
- Corrected usage of active_scan_mask in opt4060_trigger_handler(...).
- Clean-up of various comments.
- Link to V1: https://lore.kernel.org/lkml/[email protected]/

Per-Daniel Olsson (2):
dt-bindings: iio: light: Document TI OPT4060 RGBW sensor
iio: light: Add support for TI OPT4060 color sensor

 .../bindings/iio/light/ti,opt4060.yaml        |   51 +
 drivers/iio/light/Kconfig                     |   13 +
 drivers/iio/light/Makefile                    |    1 +
 drivers/iio/light/opt4060.c                   | 1216 +++++++++++++++++
 4 files changed, 1281 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml
 create mode 100644 drivers/iio/light/opt4060.c

base-commit: 0c559323bbaabee7346c12e74b497e283aaafef5

#9 lore raw
Quoted 6 lines · Jonathan Cameron
Quoted 3 lines · On Sat, 5 Oct 2024 18:51:17 +0200 Per-Daniel Olsson…

This patch series adds support for Texas Instruments OPT4060 RGBW Color sensor using the i2c interface.

Quoted 7 lines
Quoted 2 lines

The driver exposes both raw adc values and calculated lux values though sysfs. Integration time can be configured though sysfs as well.

Lux is a unit with a particular light curve. It has no defined meaning for color channels. As a result we usually only have colors as intensity channels (unit free). If it is possible to compute an estimate of the illuminance then that can be a separate IIO_LIGHT channel.

The thing with lux is not actually something I know much about, I just read the data sheet (https://www.ti.com/lit/gpn/opt4060). According to chapter 8.4.5.2 (page 17), lux can be calculated this way:

lux = GREEN_ADC_RAW * 2.15e-3

It is also stated that R=G=B for a D65 standard white light source if red is multiplied by 2.4 and blue is multiplied with 1.3. I interpreted this as if IIO_LIGHT was the best fit and that's why I didn't use IIO_INTENSITY. Should I change to IIO_INTENSITY?

Quoted 12 lines
Quoted 11 lines

The OPT4060 sensor
supports both rising and falling threshold interrupts. These interrupts are exposed as IIO events. The driver also implements an IIO triggered buffer with two triggers, one trigger for conversion ready interrupts and one trigger for threshold interrupts. The typical use case for this is to define a threshold and listen for the events, and at the same time enable the triggered buffer with the threshold trigger. Once the application gets the threshold event, the values from the time of the event will be available in the triggered buffer. This limits the number of interrupts between sensor and host and also the the usage of sysfs for reading values after events.

Quoted 6 lines

We have had various discussions of threshold triggers in the past, but I don't think we ever merged any (maybe there is one somewhere?) The reasons for that are:
1) They are hard for generic userspace to understand.
2) For light sensors the input tends to be slow moving - grabbing one reading on a threshold interrupt is rarely that informative (or are you grabbing them on dataready once the threshold is breached?)

When the sensor triggers an interrupt for a threshold, it will also have the bit for dataready set. So the values available at that point in time are the values that triggered the threshold interrupt.

Quoted 7 lines

3) If we want to do threshold triggers we should really add generic infrastructure for them based on adding an in kernel events consumer path and a way to instantiate a trigger based on any event. Doing it in a single driver creates an ABI we won't want to retain long term.

So how important is this feature to you and why? Maybe it is time to finally take a close look at option 3.

Our userspace application needs the values after getting the threshold event. Before I implemented the threshold trigger and the triggered buffer, the application would read the values from sysfs right after the event. In that case the values will not be the ones that actually triggered the event. When the light condition is close to the threshold, the value from sysfs might even be on the wrong side of the threshold which can be confusing for the state machine in userspace. I would say that this feature is fairly important to us, this is the way our userspace is using the sensor.

If I understand you correctly, the problem you see with threshold triggers is that it creates an implicit dependency between events and triggers. For the trigger to function, userspace will first have to enable the event and set the threshold. I can understand this issue, I think. Your suggestion with a way to instantiate triggers from events sounds like a potential way forward. Do you have any more thoughts on how that could be implemented? How can we proceed? How can I help?

Thank you for you comments so far, looking forward to hearing your thoughts on a way forward.

/ P-D

Quoted 28 lines

Jonathan

Quoted 24 lines

Changes in v2:
- dt-bindings: Removed incorrect allOf.
- dt-bindings: Changed to generic node name.
- Correction in opt4060_trigger_one_shot(...) for continuous mode.
- Correction in opt4060_power_down(...), wrong register was read.
- Corrected usage of active_scan_mask in opt4060_trigger_handler(...).
- Clean-up of various comments.
- Link to V1: https://lore.kernel.org/lkml/[email protected]/

Per-Daniel Olsson (2):
dt-bindings: iio: light: Document TI OPT4060 RGBW sensor
iio: light: Add support for TI OPT4060 color sensor

 .../bindings/iio/light/ti,opt4060.yaml        |   51 +
 drivers/iio/light/Kconfig                     |   13 +
 drivers/iio/light/Makefile                    |    1 +
 drivers/iio/light/opt4060.c                   | 1216 +++++++++++++++++
 4 files changed, 1281 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml
 create mode 100644 drivers/iio/light/opt4060.c

base-commit: 0c559323bbaabee7346c12e74b497e283aaafef5

#10 lore raw
Quoted 10 lines · On Mon, 7 Oct 2024 15:37:07 +0200 Per-Daniel Olsson…
Quoted 9 lines · Jonathan Cameron
Quoted 5 lines · On Sat, 5 Oct 2024 18:51:17 +0200 Per-Daniel Olsson…

This patch series adds support for Texas Instruments OPT4060 RGBW Color sensor using the i2c interface.

The driver exposes both raw adc values and calculated lux values though sysfs. Integration time can be configured though sysfs as well.

Quoted 6 lines
Quoted 4 lines

Lux is a unit with a particular light curve. It has no defined meaning for color channels. As a result we usually only have colors as intensity channels (unit free). If it is possible to compute an estimate of the illuminance then that can be a separate IIO_LIGHT channel.

The thing with lux is not actually something I know much about,

https://en.wikipedia.org/wiki/Illuminance is a decent description (though I haven't reread it today!)

Key thing is a brightness measure adjusted to take into account an approximation of the human eye sensitivity to various wavelengths.

Quoted 5 lines

I just read the
data sheet (https://www.ti.com/lit/gpn/opt4060). According to chapter 8.4.5.2 (page 17), lux can be calculated this way:

lux = GREEN_ADC_RAW * 2.15e-3

ouch.

Quoted 5 lines

It is also stated that R=G=B for a D65 standard white light source if red is multiplied by 2.4 and blue is multiplied with 1.3. I interpreted this as if IIO_LIGHT was the best fit and that's why I didn't use IIO_INTENSITY. Should I change to IIO_INTENSITY?

Yes. Light isn't typically a d65 light unfortunately (reference lights are expensive!)

Mind you I guess if was, we'd live in a blank and white world as there would be no colors, just shades of gray...

Quoted 32 lines
Quoted 13 lines
Quoted 12 lines

The OPT4060 sensor
supports both rising and falling threshold interrupts. These interrupts are exposed as IIO events. The driver also implements an IIO triggered buffer with two triggers, one trigger for conversion ready interrupts and one trigger for threshold interrupts. The typical use case for this is to define a threshold and listen for the events, and at the same time enable the triggered buffer with the threshold trigger. Once the application gets the threshold event, the values from the time of the event will be available in the triggered buffer. This limits the number of interrupts between sensor and host and also the the usage of sysfs for reading values after events.

Quoted 6 lines

We have had various discussions of threshold triggers in the past, but I don't think we ever merged any (maybe there is one somewhere?) The reasons for that are:
1) They are hard for generic userspace to understand.
2) For light sensors the input tends to be slow moving - grabbing one reading on a threshold interrupt is rarely that informative (or are you grabbing them on dataready once the threshold is breached?)

When the sensor triggers an interrupt for a threshold, it will also have the bit for dataready set. So the values available at that point in time are the values that triggered the threshold interrupt.

Quoted 7 lines

3) If we want to do threshold triggers we should really add generic infrastructure for them based on adding an in kernel events consumer path and a way to instantiate a trigger based on any event. Doing it in a single driver creates an ABI we won't want to retain long term.

So how important is this feature to you and why? Maybe it is time to finally take a close look at option 3.

Quoted 7 lines

Our userspace application needs the values after getting the threshold event. Before I implemented the threshold trigger and the triggered buffer, the application would read the values from sysfs right after the event. In that case the values will not be the ones that actually triggered the event. When the light condition is close to the threshold, the value from sysfs might even be on the wrong side of the threshold which can be confusing for the state machine in userspace. I would say that this feature is fairly important to us, this is the way our userspace is using the sensor.

Brutal answer is fix your state machine to drop that assumption. I'd try to clamp the nearest to threshold to the threshold value in your userspace app. Any error that introduces should be lost in the noise.

Quoted 7 lines

If I understand you correctly, the problem you see with threshold triggers is that it creates an implicit dependency between events and triggers. For the trigger to function, userspace will first have to enable the event and set the threshold. I can understand this issue, I think. Your suggestion with a way to instantiate triggers from events sounds like a potential way forward. Do you have any more thoughts on how that could be implemented? How can we proceed? How can I help?

Step one would be to add a general in kernel interface to get hold of events. That would have to look a little like the in kernel access to buffers (see inkern.c) We might be able to get away with different consumers just having to accept they may get events they didn't ask for. So make the consumers filter them and the interface would just allow requesting 'more' events from a device. That device could say no if it doesn't support the requested events in addition to what it already has.

That interface has a bunch of other uses such as trip points for thermal etc.

After that was done we'd also need a way to instantiate a trigger on a particular devices' event stream + filter. Maybe we could do it for all devices, though that is going to be a little ugly as a lot of new triggers would turn up as in theory we should register one for every possible event each device can create. (imagine we want a trigger on a rising threshold and a different one to capture something else on the falling threshold).

Alternative would be to use configfs to provide a creation path for such triggers.

So not a small job, but not really breaking any new ground, just filling in a couple of long known to be missing features.

We might need some example tooling + a bunch of docs on how to use this as well.

Jonathan

Quoted 36 lines

Thank you for you comments so far, looking forward to hearing your thoughts on a way forward.

/ P-D

Quoted 28 lines

Jonathan

Quoted 24 lines

Changes in v2:
- dt-bindings: Removed incorrect allOf.
- dt-bindings: Changed to generic node name.
- Correction in opt4060_trigger_one_shot(...) for continuous mode.
- Correction in opt4060_power_down(...), wrong register was read.
- Corrected usage of active_scan_mask in opt4060_trigger_handler(...).
- Clean-up of various comments.
- Link to V1: https://lore.kernel.org/lkml/[email protected]/

Per-Daniel Olsson (2):
dt-bindings: iio: light: Document TI OPT4060 RGBW sensor
iio: light: Add support for TI OPT4060 color sensor

 .../bindings/iio/light/ti,opt4060.yaml        |   51 +
 drivers/iio/light/Kconfig                     |   13 +
 drivers/iio/light/Makefile                    |    1 +
 drivers/iio/light/opt4060.c                   | 1216 +++++++++++++++++
 4 files changed, 1281 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml
 create mode 100644 drivers/iio/light/opt4060.c

base-commit: 0c559323bbaabee7346c12e74b497e283aaafef5

#11 lore raw
Quoted 32 lines · Jonathan Cameron
Quoted 10 lines · On Mon, 7 Oct 2024 15:37:07 +0200 Per-Daniel Olsson…
Quoted 9 lines · Jonathan Cameron
Quoted 5 lines · On Sat, 5 Oct 2024 18:51:17 +0200 Per-Daniel Olsson…

This patch series adds support for Texas Instruments OPT4060 RGBW Color sensor using the i2c interface.

The driver exposes both raw adc values and calculated lux values though sysfs. Integration time can be configured though sysfs as well.

Quoted 6 lines
Quoted 4 lines

Lux is a unit with a particular light curve. It has no defined meaning for color channels. As a result we usually only have colors as intensity channels (unit free). If it is possible to compute an estimate of the illuminance then that can be a separate IIO_LIGHT channel.

The thing with lux is not actually something I know much about,

https://en.wikipedia.org/wiki/Illuminance is a decent description (though I haven't reread it today!)

Key thing is a brightness measure adjusted to take into account an approximation of the human eye sensitivity to various wavelengths.

Quoted 5 lines
Quoted 1 line

I just read the

data sheet (https://www.ti.com/lit/gpn/opt4060). According to chapter 8.4.5.2 (page 17), lux can be calculated this way:

lux = GREEN_ADC_RAW * 2.15e-3

ouch.

Quoted 1 line
Quoted 8 lines
Quoted 4 lines

It is also stated that R=G=B for a D65 standard white light source if red is multiplied by 2.4 and blue is multiplied with 1.3. I interpreted this as if IIO_LIGHT was the best fit and that's why I didn't use IIO_INTENSITY. Should I change to IIO_INTENSITY?

Yes. Light isn't typically a d65 light unfortunately (reference lights are expensive!)

I have switched to IIO_INTENSITY in patch v3.

Quoted 72 lines

Mind you I guess if was, we'd live in a blank and white world as there would be no colors, just shades of gray...>

Quoted 32 lines
Quoted 13 lines
Quoted 12 lines

The OPT4060 sensor
supports both rising and falling threshold interrupts. These interrupts are exposed as IIO events. The driver also implements an IIO triggered buffer with two triggers, one trigger for conversion ready interrupts and one trigger for threshold interrupts. The typical use case for this is to define a threshold and listen for the events, and at the same time enable the triggered buffer with the threshold trigger. Once the application gets the threshold event, the values from the time of the event will be available in the triggered buffer. This limits the number of interrupts between sensor and host and also the the usage of sysfs for reading values after events.

Quoted 6 lines

We have had various discussions of threshold triggers in the past, but I don't think we ever merged any (maybe there is one somewhere?) The reasons for that are:
1) They are hard for generic userspace to understand.
2) For light sensors the input tends to be slow moving - grabbing one reading on a threshold interrupt is rarely that informative (or are you grabbing them on dataready once the threshold is breached?)

When the sensor triggers an interrupt for a threshold, it will also have the bit for dataready set. So the values available at that point in time are the values that triggered the threshold interrupt.

Quoted 7 lines

3) If we want to do threshold triggers we should really add generic infrastructure for them based on adding an in kernel events consumer path and a way to instantiate a trigger based on any event. Doing it in a single driver creates an ABI we won't want to retain long term.

So how important is this feature to you and why? Maybe it is time to finally take a close look at option 3.

Quoted 7 lines

Our userspace application needs the values after getting the threshold event. Before I implemented the threshold trigger and the triggered buffer, the application would read the values from sysfs right after the event. In that case the values will not be the ones that actually triggered the event. When the light condition is close to the threshold, the value from sysfs might even be on the wrong side of the threshold which can be confusing for the state machine in userspace. I would say that this feature is fairly important to us, this is the way our userspace is using the sensor.

Brutal answer is fix your state machine to drop that assumption. I'd try to clamp the nearest to threshold to the threshold value in your userspace app. Any error that introduces should be lost in the noise.

Quoted 7 lines

If I understand you correctly, the problem you see with threshold triggers is that it creates an implicit dependency between events and triggers. For the trigger to function, userspace will first have to enable the event and set the threshold. I can understand this issue, I think. Your suggestion with a way to instantiate triggers from events sounds like a potential way forward. Do you have any more thoughts on how that could be implemented? How can we proceed? How can I help?

Step one would be to add a general in kernel interface to get hold of events. That would have to look a little like the in kernel access to buffers (see inkern.c) We might be able to get away with different consumers just having to accept they may get events they didn't ask for. So make the consumers filter them and the interface would just allow requesting 'more' events from a device. That device could say no if it doesn't support the requested events in addition to what it already has.

That interface has a bunch of other uses such as trip points for thermal etc.

After that was done we'd also need a way to instantiate a trigger on a particular devices' event stream + filter. Maybe we could do it for all devices, though that is going to be a little ugly as a lot of new triggers would turn up as in theory we should register one for every possible event each device can create. (imagine we want a trigger on a rising threshold and a different one to capture something else on the falling threshold).

Quoted 9 lines

Alternative would be to use configfs to provide a creation path for such triggers.

So not a small job, but not really breaking any new ground, just filling in a couple of long known to be missing features.

We might need some example tooling + a bunch of docs on how to use this as well.

Jonathan

Thank you for your thoughts and directions. I will try to find time to prototype what you're suggesting. It will probably take a while, both since it's "not a small job" and also because I'm not yet that familiar with the code.

/ P-D

Quoted 37 lines
Quoted 36 lines

Thank you for you comments so far, looking forward to hearing your thoughts on a way forward.

/ P-D

Quoted 28 lines

Jonathan

Quoted 24 lines

Changes in v2:
- dt-bindings: Removed incorrect allOf.
- dt-bindings: Changed to generic node name.
- Correction in opt4060_trigger_one_shot(...) for continuous mode.
- Correction in opt4060_power_down(...), wrong register was read.
- Corrected usage of active_scan_mask in opt4060_trigger_handler(...).
- Clean-up of various comments.
- Link to V1: https://lore.kernel.org/lkml/[email protected]/

Per-Daniel Olsson (2):
dt-bindings: iio: light: Document TI OPT4060 RGBW sensor
iio: light: Add support for TI OPT4060 color sensor

 .../bindings/iio/light/ti,opt4060.yaml        |   51 +
 drivers/iio/light/Kconfig                     |   13 +
 drivers/iio/light/Makefile                    |    1 +
 drivers/iio/light/opt4060.c                   | 1216 +++++++++++++++++
 4 files changed, 1281 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml
 create mode 100644 drivers/iio/light/opt4060.c

base-commit: 0c559323bbaabee7346c12e74b497e283aaafef5