linux-iio.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/6] Support ROHM BU27034 ALS sensor
@ 2023-03-02 10:57 Matti Vaittinen
  2023-03-02 10:57 ` [PATCH v2 1/6] dt-bindings: iio: light: Support ROHM BU27034 Matti Vaittinen
                   ` (5 more replies)
  0 siblings, 6 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-02 10:57 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Masahiro Yamada, Randy Dunlap, Matti Vaittinen, Shreeya Patel,
	Krzysztof Kozlowski, Jonathan Cameron, devicetree, Zhigang Shi,
	Lars-Peter Clausen, Paul Gazzillo, Rob Herring, Dmitry Osipenko,
	linux-iio, linux-kernel, Liam Beguin, Andy Shevchenko

[-- Attachment #1: Type: text/plain, Size: 6838 bytes --]

Support ROHM BU27034 ALS sensor

This series adds support for ROHM BU27034 Ambient Light Sensor.

The BU27034 has configurable gain and measurement (integration) time
settings. Both of these have inversely proportional relation to the
sensor's intensity channel scale.

Many users only set the scale, which means that many drivers attempt to
'guess' the best gain+time combination to meet the scale. Usually this
is the biggest integration time which allows setting the requested
scale. Typically, increasing the integration time has better accuracy
than increasing the gain, which often amplifies the noise as well as the
real signal.

However, there may be cases where more responsive sensors are needed.
So, in some cases the longest integration times may not be what the user
prefers. The driver has no way of knowing this.

Hence, the approach taken by this series is to allow user to set both
the scale and the integration time with following logic:

1. When scale is set, the existing integration time is tried to be
   maintained as a first priority.
   1a) If the requested scale can't be met by current time, then also
       other time + gain combinations are searched. If scale can be met
       by some other integration time, then the new time may be applied.
       If the time setting is common for all channels, then also other
       channels must be able to maintain their scale with this new time
       (by changing their gain). The new times are scanned in the order
       of preference (typically the longest times first).
   1b) If the requested scale can be met using current time, then only
       the gain for the channel is changed.

2. When the integration time change - scale is tried to be maintained.
   When integration time change is requested also gain for all impacted
   channels is adjusted so that the scale is not changed, or is chaned
   as little as possible. This is different from the RFCv1 where the
   request was rejected if suitable gain couldn't be found for some
   channel(s).

This logic is simple. When total gain (either caused by time or hw-gain)
is doubled, the scale gets halved. Also, the supported times are given a
'multiplier' value which tells how much they increase the total gain.

However, when I wrote this logic in bu27034 driver, I made quite a few
errors on the way - and driver got pretty big. As I am writing drivers
for two other sensors (RGB C/IR + flicker BU27010 and RGB C/IR BU27008)
with similar gain-time-scale logic I thought that adding common helpers
for these computations might be wise. I hope this way all the bugs will
be concentrated in one place and not in every individual driver ;)

Hence, this series also intriduces IIO gain-time-scale helpers
(abbreviated as gts-helpers) + a couple of KUnit tests for the most
hairy parts.

I can't help thinking that there should've been simpler way of computing
the gain-time-scale conversions. Also, pretty good speed improvements
might be available if some of the do_div()s could be replaced by >>.
This, however, is not a priority for my light-sensor use-case where
speed demands are not that big.

Finally, these added helpers do provide some value also for drivers
which only:
 a) allow gain change
  or
 b) allow changing both the time and gain but so that the time-change is
    not reflected in register values.

For a) we provide the gain - selector (register value) table format +
selector to gain look-ups, gain <-> scale conversions and the available
scales helpers.

For latter case we also provide the time-tables, and actually all the
APIs should be usable by setting the time multiplier to 1. (not testeted
thoroughly though).

Revision history:
RFCv1 => v2:
  dt-bindings:
	- Fix binding file name and id by using comma instead of a hyphen to
	  separate the vendor and part names.
  gts-helpers:
	- fix include guardian
	- Improve kernel doc for iio_init_iio_gts.
	- Add iio_gts_scale_to_total_gain
	- Add iio_gts_total_gain_to_scale
	- Fix review comments from Jonathan
	  - add documentation to few functions
	  - replace 0xffffffffffffffffLLU by U64_MAX
	  - some styling fixes
	  - drop unnecessary NULL checks
	  - order function arguments by  in / out purpose
	  - drop GAIN_SCALE_ITIME_MS()
	- Add helpers for available scales and times
	- Rename to iio-gts-helpers
  gts-tests:
	- add tests for available scales/times helpers
	- adapt to renamed iio-gts-helpers.h header
  bu27034-driver:
	- (really) protect read-only registers
	- fix get and set gain
	- buffered mode
	- Protect the whole sequences including meas_en/meas_dis to avoid messing
	  up the enable / disable order
	- typofixes / doc improvements
	- change dropped GAIN_SCALE_ITIME_MS() to GAIN_SCALE_ITIME_US()
	- use more accurate scale for lux channel (milli lux)
	- provide available scales / integration times (using helpers).
	- adapt to renamed iio-gts-helpers.h file
	- bu27034 - longer lines in Kconfig
	- Drop bu27034_meas_en and bu27034_meas_dis wrappers.
	- Change device-name from bu27034-als to bu27034
  MAINTAINERS:
	- Add iio-list

---

Matti Vaittinen (6):
  dt-bindings: iio: light: Support ROHM BU27034
  iio: light: Add gain-time-scale helpers
  iio: test: test gain-time-scale helpers
  MAINTAINERS: Add IIO gain-time-scale helpers
  iio: light: ROHM BU27034 Ambient Light Sensor
  MAINTAINERS: Add ROHM BU27034

 .../bindings/iio/light/rohm,bu27034.yaml      |   46 +
 MAINTAINERS                                   |   14 +
 drivers/iio/light/Kconfig                     |   15 +
 drivers/iio/light/Makefile                    |    2 +
 drivers/iio/light/iio-gts-helper.c            | 1152 +++++++++++++
 drivers/iio/light/iio-gts-helper.h            |  130 ++
 drivers/iio/light/rohm-bu27034.c              | 1491 +++++++++++++++++
 drivers/iio/test/Kconfig                      |   15 +
 drivers/iio/test/Makefile                     |    1 +
 drivers/iio/test/iio-test-gts.c               |  537 ++++++
 10 files changed, 3403 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/rohm,bu27034.yaml
 create mode 100644 drivers/iio/light/iio-gts-helper.c
 create mode 100644 drivers/iio/light/iio-gts-helper.h
 create mode 100644 drivers/iio/light/rohm-bu27034.c
 create mode 100644 drivers/iio/test/iio-test-gts.c


base-commit: c9c3395d5e3dcc6daee66c6908354d47bf98cb0c
-- 
2.39.2


-- 
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* [PATCH v2 1/6] dt-bindings: iio: light: Support ROHM BU27034
  2023-03-02 10:57 [PATCH v2 0/6] Support ROHM BU27034 ALS sensor Matti Vaittinen
@ 2023-03-02 10:57 ` Matti Vaittinen
  2023-03-02 10:57 ` [PATCH v2 2/6] iio: light: Add gain-time-scale helpers Matti Vaittinen
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-02 10:57 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring,
	Krzysztof Kozlowski, Matti Vaittinen, linux-iio, devicetree,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2432 bytes --]

ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes
capable of detecting a very wide range of illuminance. Typical application
is adjusting LCD and backlight power of TVs and mobile phones.

Add dt-bindings.

Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

---

Changes since RFCv1:
- Fix binding file name and id by using comma instead of a hyphen to
  separate the vendor and part names.
---
 .../bindings/iio/light/rohm,bu27034.yaml      | 46 +++++++++++++++++++
 1 file changed, 46 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/rohm,bu27034.yaml

diff --git a/Documentation/devicetree/bindings/iio/light/rohm,bu27034.yaml b/Documentation/devicetree/bindings/iio/light/rohm,bu27034.yaml
new file mode 100644
index 000000000000..30a109a1bf3b
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/light/rohm,bu27034.yaml
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/light/rohm,bu27034.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ROHM BU27034 ambient light sensor
+
+maintainers:
+  - Matti Vaittinen <mazziesaccount@gmail.com>
+
+description: |
+  ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes
+  capable of detecting a very wide range of illuminance. Typical application
+  is adjusting LCD and backlight power of TVs and mobile phones.
+  https://fscdn.rohm.com/en/products/databook/datasheet/ic/sensor/light/bu27034nuc-e.pdf
+
+properties:
+  compatible:
+    const: rohm,bu27034
+
+  reg:
+    maxItems: 1
+
+  vdd-supply: true
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      light-sensor@38 {
+        compatible = "rohm,bu27034";
+        reg = <0x38>;
+        vdd-supply = <&vdd>;
+      };
+    };
+
+...
-- 
2.39.2


-- 
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-02 10:57 [PATCH v2 0/6] Support ROHM BU27034 ALS sensor Matti Vaittinen
  2023-03-02 10:57 ` [PATCH v2 1/6] dt-bindings: iio: light: Support ROHM BU27034 Matti Vaittinen
@ 2023-03-02 10:57 ` Matti Vaittinen
  2023-03-02 15:05   ` Andy Shevchenko
  2023-03-04 18:35   ` Jonathan Cameron
  2023-03-02 10:58 ` [PATCH v2 3/6] iio: test: test " Matti Vaittinen
                   ` (3 subsequent siblings)
  5 siblings, 2 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-02 10:57 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Jonathan Cameron, Lars-Peter Clausen, Andy Shevchenko,
	Matti Vaittinen, Paul Gazzillo, Dmitry Osipenko, Shreeya Patel,
	Zhigang Shi, linux-kernel, linux-iio

[-- Attachment #1: Type: text/plain, Size: 43363 bytes --]

Some light sensors can adjust both the HW-gain and integration time.
There are cases where adjusting the integration time has similar impact
to the scale of the reported values as gain setting has.

IIO users do typically expect to handle scale by a single writable 'scale'
entry. Driver should then adjust the gain/time accordingly.

It however is difficult for a driver to know whether it should change
gain or integration time to meet the requested scale. Usually it is
preferred to have longer integration time which usually improves
accuracy, but there may be use-cases where long measurement times can be
an issue. Thus it can be preferable to allow also changing the
integration time - but mitigate the scale impact by also changing the gain
underneath. Eg, if integration time change doubles the measured values,
the driver can reduce the HW-gain to half.

The theory of the computations of gain-time-scale is simple. However,
some people (undersigned) got that implemented wrong for more than once.

Add some gain-time-scale helpers in order to not dublicate errors in all
drivers needing these computations.

Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>

---
Currently it is only BU27034 using these in this series. I am however working
with drivers for RGB sensors BU27008 and BU27010 which have similar
[gain - integration time - scale] - relation. I hope sending those
follows soon after the BU27034 is done.

Changes since RFCv1:
- fix include guardian
- Improve kernel doc for iio_init_iio_gts.
- Add iio_gts_scale_to_total_gain
- Add iio_gts_total_gain_to_scale
- Fix review comments from Jonathan
  - add documentation to few functions
  - replace 0xffffffffffffffffLLU by U64_MAX
  - some styling fixes
  - drop unnecessary NULL checks
  - order function arguments by  in / out purpose
  - drop GAIN_SCALE_ITIME_MS()
- Add helpers for available scales and times
- Rename to iio-gts-helpers
---
 drivers/iio/light/Kconfig          |    3 +
 drivers/iio/light/Makefile         |    1 +
 drivers/iio/light/iio-gts-helper.c | 1152 ++++++++++++++++++++++++++++
 drivers/iio/light/iio-gts-helper.h |  130 ++++
 4 files changed, 1286 insertions(+)
 create mode 100644 drivers/iio/light/iio-gts-helper.c
 create mode 100644 drivers/iio/light/iio-gts-helper.h

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 0d4447df7200..671d84f98c56 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -183,6 +183,9 @@ config IIO_CROS_EC_LIGHT_PROX
 	  To compile this driver as a module, choose M here:
 	  the module will be called cros_ec_light_prox.
 
+config IIO_GTS_HELPER
+	tristate
+
 config GP2AP002
 	tristate "Sharp GP2AP002 Proximity/ALS sensor"
 	depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 6f23817fae6f..82d14ebd3c11 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_CM3323)		+= cm3323.o
 obj-$(CONFIG_CM3605)		+= cm3605.o
 obj-$(CONFIG_CM36651)		+= cm36651.o
 obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o
+obj-$(CONFIG_IIO_GTS_HELPER)	+= iio-gts-helper.o
 obj-$(CONFIG_GP2AP002)		+= gp2ap002.o
 obj-$(CONFIG_GP2AP020A00F)	+= gp2ap020a00f.o
 obj-$(CONFIG_HID_SENSOR_ALS)	+= hid-sensor-als.o
diff --git a/drivers/iio/light/iio-gts-helper.c b/drivers/iio/light/iio-gts-helper.c
new file mode 100644
index 000000000000..df6c2fffe775
--- /dev/null
+++ b/drivers/iio/light/iio-gts-helper.c
@@ -0,0 +1,1152 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* gain-time-scale conversion helpers for IIO light sensors
+ *
+ * Copyright (c) 2023 Matti Vaittinen <mazziesaccount@gmail.com>
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/units.h>
+
+#include <linux/iio/types.h>
+
+#include "iio-gts-helper.h"
+
+/*
+ * iio_gts_get_gain - Convert scale to total gain
+ *
+ * Internal helper for converting scale to total gain.
+ *
+ * @max:	Maximum linearized scale. As an example, when scale is creted in
+ *		magnitude of NANOs and max scale is 64.1 - The linearized
+ *		scale is 64 100 000 000.
+ * @scale:	Linearized scale to compte the gain for.
+ *
+ * Return:	(floored) gain corresponding to the scales. -EINVAL if scale
+ *		is invalid.
+ */
+static int iio_gts_get_gain(const u64 max, const u64 scale)
+{
+	int tmp = 1;
+
+	if (scale > max || !scale)
+		return -EINVAL;
+
+	if (U64_MAX - max < scale) {
+		/* Risk of overflow */
+		if (max - scale < scale)
+			return 1;
+
+		while (max - scale > scale * (u64) tmp)
+			tmp++;
+
+		return tmp + 1;
+	}
+
+	while (max > scale * (u64) tmp)
+		tmp++;
+
+	return tmp;
+}
+/*
+ * gain_get_scale_fraction - get the gain or time based on scale and known one
+ *
+ * Internal helper for computing unknown fraction of total gain.
+ * Compute either gain or time based on scale and either the gain or time
+ * depending on which one is known.
+ *
+ * @max:	Maximum linearized scale. As an example, when scale is creted in
+ *		magnitude of NANOs and max scale is 64.1 - The linearized
+ *		scale is 64 100 000 000.
+ * @scale:	Linearized scale to compute the gain/time for.
+ * @known:	Either integration time or gain depending on which one is known
+ * @unknown:	Pointer to variable where the computed gain/time is stored
+ *
+ * Return:	0 on success
+ */
+static int gain_get_scale_fraction(const u64 max, u64 scale, int known,
+				   int *unknown)
+{
+	int tot_gain;
+
+	tot_gain = iio_gts_get_gain(max, scale);
+	if (tot_gain < 0)
+		return tot_gain;
+
+	*unknown = tot_gain / known;
+
+	/* We require total gain to be exact multiple of known * unknown */
+	if (!*unknown || *unknown * known != tot_gain)
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct iio_itime_sel_mul *
+			iio_gts_find_itime_by_time(struct iio_gts *gts, int time)
+{
+	int i;
+
+	if (!gts->num_itime)
+		return NULL;
+
+	for (i = 0; i < gts->num_itime; i++)
+		if (gts->itime_table[i].time_us == time)
+			return &gts->itime_table[i];
+
+	return NULL;
+}
+
+static const struct iio_itime_sel_mul *
+			iio_gts_find_itime_by_sel(struct iio_gts *gts, int sel)
+{
+	int i;
+
+	for (i = 0; i < gts->num_itime; i++)
+		if (gts->itime_table[i].sel == sel)
+			return &gts->itime_table[i];
+
+	return NULL;
+}
+
+static int iio_gts_delinearize(u64 lin_scale, unsigned long scaler,
+			       int *scale_whole, int *scale_nano)
+{
+	int frac;
+
+	if (scaler > NANO || !scaler)
+		return -EINVAL;
+
+	frac = do_div(lin_scale, scaler);
+
+	*scale_whole = lin_scale;
+	*scale_nano = frac * (NANO / scaler);
+
+	return 0;
+}
+
+static int iio_gts_linearize(int scale_whole, int scale_nano,
+			     unsigned long scaler, u64 *lin_scale)
+{
+	/*
+	 * Expect scale to be (mostly) NANO or MICRO. Divide divider instead of
+	 * multiplication followed by division to avoid overflow
+	 */
+	if (scaler > NANO || !scaler)
+		return -EINVAL;
+
+	*lin_scale = (u64) scale_whole * (u64)scaler + (u64)(scale_nano
+		     / (NANO / scaler));
+
+	return 0;
+}
+
+/**
+ * iio_gts_total_gain_to_scale - convert gain to scale
+ * @gts:	Gain time scale descriptor
+ * @scale_int:	Pointer to integral part of the scale (typically val1)
+ * @scale_nano:	Pointer to ractional part of the scale (nano or ppb)
+ * @gain_tot:	the gain to be converted
+ *
+ * Convert the total gain value to scale. NOTE: This does not separate gain
+ * generated by hwgain or integration time. It is up to caller to decide what
+ * part of the total gain is due to integration time and what due to hw-gain.
+ *
+ * Return: 0 on success. Negative errno on failure
+ */
+int iio_gts_total_gain_to_scale(struct iio_gts *gts, int total_gain,
+				int *scale_int, int *scale_nano)
+{
+	u64 tmp;
+
+	tmp = gts->max_scale;
+
+	do_div(tmp, total_gain);
+
+	return iio_gts_delinearize(tmp, NANO, scale_int, scale_nano);
+}
+EXPORT_SYMBOL_GPL(iio_gts_total_gain_to_scale);
+
+/**
+ * iio_gts_scale_to_total_gain - convert scale to gain
+ * @gts:	Gain time scale descriptor
+ * @scale_int:	Integral part of the scale (typically val1)
+ * @scale_nano:	Fractional part of the scale (nano or ppb)
+ * @gain_tot:	pointer to variable where gain is stored
+ *
+ * Convert the scale value to total gain. NOTE: This does not separate gain
+ * generated by hwgain or integration time. It is up to caller to decide what
+ * part of the total gain is due to integration time and what due to hw-gain.
+ *
+ * Return: 0 on success. Negative errno on failure
+ */
+int iio_gts_scale_to_total_gain(struct iio_gts *gts, int scale_int,
+				int scale_nano, int *gain_tot)
+{
+	u64 lin_scale;
+	int ret;
+
+	ret = iio_gts_linearize(scale_int, scale_nano, NANO, &lin_scale);
+	if (ret)
+		return ret;
+
+	ret = iio_gts_get_gain(gts->max_scale, lin_scale);
+	if (ret < 0)
+		return ret;
+
+	*gain_tot = ret;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iio_gts_scale_to_total_gain);
+
+/**
+ * iio_init_iio_gts - Initialize the gain-time-scale helper
+ * @max_scale_int:	integer part of the maximum scale value
+ * @max_scale_nano:	fraction part of the maximum scale value
+ * @gain_tbl:		table describing supported gains
+ * @num_gain:		number of gains in the gaintable
+ * @tim_tbl:		table describing supported integration times. Provide
+ *			the integration time table sorted so that the preferred
+ *			integration time is in the first array index. The search
+ *			functions like the
+ *			iio_gts_find_time_and_gain_sel_for_scale() start search
+ *			from first provided time.
+ * @num_times:		number of times in the time table
+ * @gts:		pointer to the helper struct
+ *
+ * Initialize the gain-time-scale helper for use.
+ *
+ * Return: 0 on success.
+ */
+int iio_init_iio_gts(int max_scale_int, int max_scale_nano,
+		     const struct iio_gain_sel_pair *gain_tbl, int num_gain,
+		     const struct iio_itime_sel_mul *tim_tbl, int num_times,
+		     struct iio_gts *gts)
+{
+	int ret;
+
+	ret = iio_gts_linearize(max_scale_int, max_scale_nano, NANO,
+				   &gts->max_scale);
+	if (ret)
+		return ret;
+
+	gts->hwgain_table = gain_tbl;
+	gts->num_hwgain = num_gain;
+	gts->itime_table = tim_tbl;
+	gts->num_itime = num_times;
+	gts->per_time_avail_scale_tables = NULL;
+	gts->avail_time_tables = NULL;
+	gts->avail_all_scales_table = NULL;
+	gts->num_avail_all_scales = 0;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iio_init_iio_gts);
+
+/**
+ * iio_gts_purge_avail_scale_table - free-up the available scale tables
+ * @gts:	Gain time scale descriptor
+ *
+ * Free the space reserved by iio_gts_build_avail_scale_table(). Please note
+ * that the helpers for getting available scales like the
+ * iio_gts_all_avail_scales() are not usable after this call. Thus, this should
+ * be only called after these helpers can no longer be called (Eg. after
+ * the iio-device has been deregistered).
+ */
+void iio_gts_purge_avail_scale_table(struct iio_gts *gts)
+{
+	int i;
+
+	if (gts->per_time_avail_scale_tables) {
+		for (i = 0; i < gts->num_itime; i++)
+			kfree(gts->per_time_avail_scale_tables[i]);
+
+		kfree(gts->per_time_avail_scale_tables);
+		gts->per_time_avail_scale_tables = NULL;
+	}
+
+	kfree(gts->avail_all_scales_table);
+	gts->avail_all_scales_table = NULL;
+
+	gts->num_avail_all_scales = 0;
+}
+EXPORT_SYMBOL_GPL(iio_gts_purge_avail_scale_table);
+
+static int iio_gts_gain_cmp(const void *a, const void *b)
+{
+	return *(int *)a - *(int *)b;
+}
+
+static int gain_to_scaletables(struct iio_gts *gts, int **gains, int **scales)
+{
+	int ret, i, j, new_idx;
+	int *all_gains;
+
+	for (i = 0; i < gts->num_itime; i++) {
+		/*
+		 * Sort the tables for nice output and for easier finding of
+		 * unique values
+		 */
+		sort(gains[i], gts->num_hwgain, sizeof(int), iio_gts_gain_cmp,
+		     NULL);
+
+		/* Convert gains to scales */
+		for (j = 0; j < gts->num_hwgain; j++) {
+			ret = iio_gts_total_gain_to_scale(gts, gains[i][j],
+							  &scales[i][2 * j],
+							  &scales[i][2 * j + 1]);
+			if (ret)
+				return ret;
+		}
+	}
+
+	all_gains = kcalloc(gts->num_itime * gts->num_hwgain, sizeof(int),
+			    GFP_KERNEL);
+	if (!all_gains)
+		return -ENOMEM;
+
+	/*
+	 * We assume all the gains for same integration time were unique.
+	 * It is likely the first time table had greatest time multiplier as
+	 * the times are in the order of preference and greater times are
+	 * usually preferred. Hence we start from the last table which is likely
+	 * to have the smallest total gains
+	 */
+	memcpy(all_gains, gains[gts->num_itime - 1], gts->num_hwgain * sizeof(int));
+	new_idx = gts->num_hwgain;
+
+	for (i = gts->num_itime - 2; i >= 0; i--)
+		for (j = 0; j < gts->num_hwgain; j++) {
+			int candidate = gains[i][j];
+			int chk;
+
+			if (candidate > all_gains[new_idx - 1]) {
+				all_gains[new_idx] = candidate;
+				new_idx++;
+
+				continue;
+			}
+			for (chk = 0; chk < new_idx; chk++)
+				if (candidate <= all_gains[chk])
+					break;
+
+			if (candidate == all_gains[chk])
+				continue;
+
+			memmove(&all_gains[chk + 1], &all_gains[chk],
+				(new_idx - chk) * sizeof(int));
+			all_gains[chk] = candidate;
+			new_idx++;
+		}
+
+	gts->num_avail_all_scales = new_idx;
+
+	gts->avail_all_scales_table = kcalloc(gts->num_avail_all_scales,
+					      2 * sizeof(int), GFP_KERNEL);
+	if (!gts->avail_all_scales_table)
+		ret = -ENOMEM;
+	else
+		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)
+			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
+					&gts->avail_all_scales_table[i * 2],
+					&gts->avail_all_scales_table[i * 2 + 1]);
+
+	kfree(all_gains);
+	if (ret && gts->avail_all_scales_table)
+		kfree(gts->avail_all_scales_table);
+
+	return ret;
+}
+
+/**
+ * iio_gts_build_avail_scale_table - create tables of available scales
+ * @gts:	Gain time scale descriptor
+ *
+ * Build the tables which can represent the available scales based on the
+ * originally given gain and time tables. When both time and gain tables are
+ * given this results:
+ * 1. A set of tables representing available scales for each supported
+ *    integration time.
+ * 2. A single table listing all the unique scales that any combination of
+ *    supported gains and times can provide.
+ *
+ * NOTE: Space allocated for the tables must be freed using
+ * iio_gts_purge_avail_scale_table() when the tables are no longer needed.
+ *
+ * Return: 0 on success.
+ */
+int iio_gts_build_avail_scale_table(struct iio_gts *gts)
+{
+	int **per_time_gains, **per_time_scales, i, j, ret = -ENOMEM;
+
+	per_time_gains = kcalloc(gts->num_itime, sizeof(int *), GFP_KERNEL);
+	if (!per_time_gains)
+		return ret;
+
+	per_time_scales = kcalloc(gts->num_itime, sizeof(int *), GFP_KERNEL);
+	if (!per_time_scales)
+		goto free_gains;
+
+	for (i = 0; i < gts->num_itime; i++) {
+		per_time_scales[i] = kcalloc(gts->num_hwgain, 2 * sizeof(int),
+					     GFP_KERNEL);
+		if (!per_time_scales[i])
+			goto err_free_out;
+
+		per_time_gains[i] = kcalloc(gts->num_hwgain, sizeof(int),
+					    GFP_KERNEL);
+		if (!per_time_gains[i])
+			goto err_free_out;
+
+
+		for (j = 0; j < gts->num_hwgain; j++)
+			per_time_gains[i][j] = gts->hwgain_table[j].gain *
+					       gts->itime_table[i].mul;
+	}
+
+	ret = gain_to_scaletables(gts, per_time_gains, per_time_scales);
+	if (ret)
+		goto err_free_out;
+
+	kfree(per_time_gains);
+	gts->per_time_avail_scale_tables = per_time_scales;
+
+	return 0;
+
+err_free_out:
+	while (i) {
+		/*
+		 * It does not matter if i'th alloc was not succesfull as
+		 * kfree(NULL) is safe.
+		 */
+		kfree(per_time_gains[i]);
+		kfree(per_time_scales[i]);
+
+		i--;
+	}
+	kfree(per_time_scales);
+free_gains:
+	kfree(per_time_gains);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(iio_gts_build_avail_scale_table);
+
+static inline void devm_iio_gts_avail_scale_drop(void *res)
+{
+	iio_gts_purge_avail_scale_table(res);
+}
+
+/**
+ * devm_iio_gts_build_avail_scale_table - do managed tables of available scales
+ * @gts:	Gain time scale descriptor
+ * @dev:	Device whose life-time tables are bound to
+ *
+ * Create tables of available scales which are freed upon the device detach.
+ * See the iio_gts_build_avail_scale_table() for more information.
+ *
+ * NOTE: after the tables have been purged, the helpers for getting the
+ * available scales are no longer usable. Care must be taken that unwinding
+ * is done in correct order (iio device is deregistered prior purging the
+ * tables).
+ *
+ * Return: 0 on success.
+ */
+int devm_iio_gts_build_avail_scale_table(struct device *dev,
+					 struct iio_gts *gts)
+{
+	int ret;
+
+	ret = iio_gts_build_avail_scale_table(gts);
+	if (ret)
+		return ret;
+
+	return devm_add_action_or_reset(dev, devm_iio_gts_avail_scale_drop, gts);
+}
+EXPORT_SYMBOL_GPL(devm_iio_gts_build_avail_scale_table);
+
+/**
+ * iio_gts_build_avail_time_table - build table of available integration times
+ * @gts:	Gain time scale descriptor
+ *
+ * Build the table which can represent the available times to be returned
+ * to users using the read_avail-callback.
+ *
+ * NOTE: Space allocated for the tables must be freed using
+ * iio_gts_purge_avail_time_table() when the tables are no longer needed.
+ *
+ * Return: 0 on success.
+ */
+int iio_gts_build_avail_time_table(struct iio_gts *gts)
+{
+	int *times, i, j, idx = 0;
+
+	if (!gts->num_itime)
+		return 0;
+
+	times = kcalloc(gts->num_itime, sizeof(int), GFP_KERNEL);
+	if (!times)
+		return -ENOMEM;
+
+	for (i = gts->num_itime - 1; i >= 0; i--) {
+		int new = gts->itime_table[i].time_us;
+
+		if (times[idx] < new) {
+			times[idx++] = new;
+			continue;
+		}
+
+		for (j = 0; j <= idx; j++) {
+			if (times[j] > new) {
+				memmove(&times[j + 1], &times[j], (idx - j) * sizeof(int));
+				times[j] = new;
+				idx++;
+			}
+		}
+	}
+	gts->avail_time_tables = times;
+	/*
+	 * This is just to survive a unlikely corner-case where times in the
+	 * given time table were not unique. Else we could just trust the
+	 * gts->num_itime.
+	 */
+	gts->num_avail_time_tables = idx;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iio_gts_build_avail_time_table);
+
+/**
+ * iio_gts_purge_avail_time_table - free-up the available integration time table
+ * @gts:	Gain time scale descriptor
+ *
+ * Free the space reserved by iio_gts_build_avail_time_table(). Please note
+ * that the helpers for getting available integration times like the
+ * iio_gts_avail_times() are not usable after this call. Thus, this should
+ * be only called after these helpers can no longer be called (Eg. after
+ * the iio-device has been deregistered).
+ */
+void iio_gts_purge_avail_time_table(struct iio_gts *gts)
+{
+	if (gts->num_avail_time_tables) {
+		kfree(gts->avail_time_tables);
+		gts->avail_time_tables = NULL;
+		gts->num_avail_time_tables = 0;
+	}
+}
+EXPORT_SYMBOL_GPL(iio_gts_purge_avail_time_table);
+
+static inline void devm_iio_gts_avail_time_drop(void *res)
+{
+	iio_gts_purge_avail_time_table(res);
+}
+
+/**
+ * devm_iio_gts_build_avail_time_table - do managed tables of available times
+ * @gts:	Gain time scale descriptor
+ * @dev:	Device whose life-time tables are bound to
+ *
+ * Create a table of available integration times. Table is freed upon the
+ * device detach. See the iio_gts_build_avail_time_table() for more information.
+ *
+ * NOTE: after the tables have been purged, the helpers for getting
+ * available integration times are no longer usable. Care must be taken that
+ * unwinding is done in correct order (iio device is deregistered prior
+ * purging the tables).
+ *
+ * Return: 0 on success.
+ */
+int devm_iio_gts_build_avail_time_table(struct device *dev, struct iio_gts *gts)
+{
+	int ret;
+
+	if (!gts->num_itime)
+		return 0;
+
+	ret = iio_gts_build_avail_time_table(gts);
+	if (ret)
+		return ret;
+
+	return devm_add_action_or_reset(dev, devm_iio_gts_avail_time_drop, gts);
+}
+EXPORT_SYMBOL_GPL(devm_iio_gts_build_avail_time_table);
+
+/**
+ * iio_gts_build_avail_tables - create tables of available scales and int times
+ * @gts:	Gain time scale descriptor
+ *
+ * Build the tables which can represent the available scales and available
+ * integration times. Availability tables are built based on the originally
+ * given gain and given time tables.
+ *
+ * When both time and gain tables are
+ * given this results:
+ * 1. A set of sorted tables representing available scales for each supported
+ *    integration time.
+ * 2. A single sorted table listing all the unique scales that any combination
+ *    of supported gains and times can provide.
+ * 3. A sorted table of supported integration times
+ *
+ * After these tables are built one can use the iio_gts_all_avail_scales(),
+ * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
+ * implement the read_avail opeations.
+ *
+ * NOTE: Space allocated for the tables must be freed using
+ * iio_gts_purge_avail_tables() when the tables are no longer needed.
+ *
+ * Return: 0 on success.
+ */
+int iio_gts_build_avail_tables(struct iio_gts *gts)
+{
+	int ret;
+
+	ret = iio_gts_build_avail_scale_table(gts);
+	if (ret)
+		return ret;
+
+	ret = iio_gts_build_avail_time_table(gts);
+	if (ret)
+		iio_gts_purge_avail_scale_table(gts);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(iio_gts_build_avail_tables);
+
+/**
+ * iio_gts_purge_avail_tables - free-up the availability tables
+ * @gts:	Gain time scale descriptor
+ *
+ * Free the space reserved by iio_gts_build_avail_tables(). Frees both the
+ * integration time and scale tables.
+ *
+ * Note  that the helpers for getting available integration times or scales
+ * like the iio_gts_avail_times() are not usable after this call. Thus, this
+ * should be only called after these helpers can no longer be called (Eg.
+ * after the iio-device has been deregistered).
+ */
+void iio_gts_purge_avail_tables(struct iio_gts *gts)
+{
+	iio_gts_purge_avail_time_table(gts);
+	iio_gts_purge_avail_scale_table(gts);
+}
+EXPORT_SYMBOL_GPL(iio_gts_purge_avail_tables);
+
+static void devm_iio_gts_avail_all_drop(void *res)
+{
+	iio_gts_purge_avail_tables(res);
+}
+
+/**
+ * devm_iio_gts_build_avail_tables - manged add availability tables
+ * @gts:	Gain time scale descriptor
+ *
+ * Build the tables which can represent the available scales and available
+ * integration times. Availability tables are built based on the originally
+ * given gain and given time tables.
+ *
+ * When both time and gain tables are
+ * given this results:
+ * 1. A set of sorted tables representing available scales for each supported
+ *    integration time.
+ * 2. A single sorted table listing all the unique scales that any combination
+ *    of supported gains and times can provide.
+ * 3. A sorted table of supported integration times
+ *
+ * After these tables are built one can use the iio_gts_all_avail_scales(),
+ * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
+ * implement the read_avail opeations.
+ *
+ * The tables are automatically released upon device detach.
+ *
+ * NOTE: after the tables have been purged, the helpers for getting
+ * available scales / integration times are no longer usable. Care must be
+ * taken that unwinding is done in correct order (iio device is deregistered
+ * prior purging the tables).
+ *
+ * Return: 0 on success.
+ */
+int devm_iio_gts_build_avail_tables(struct device *dev, struct iio_gts *gts)
+{
+	int ret;
+
+	ret = iio_gts_build_avail_tables(gts);
+	if (ret)
+		return ret;
+
+	return devm_add_action_or_reset(dev, devm_iio_gts_avail_all_drop, gts);
+}
+EXPORT_SYMBOL_GPL(devm_iio_gts_build_avail_tables);
+
+/**
+ * iio_gts_all_avail_scales - helper for listing all available scales
+ * @gts:	Gain time scale descriptor
+ * @vals:	Returned array of supported scales
+ * @type:	Type of returned scale values
+ * @length:	Amount of returned values in array
+ *
+ * Returns a value suitable to be returned from read_avail or a negative error
+ */
+int iio_gts_all_avail_scales(struct iio_gts *gts, const int **vals, int *type,
+			     int *length)
+{
+	/*
+	 * Using this function prior building the tables is a driver-error
+	 * which should be fixed when the driver is tested for a first time
+	 */
+	if (WARN_ON(!gts->num_avail_all_scales))
+		return -EINVAL;
+
+	*vals = gts->avail_all_scales_table;
+	*type = IIO_VAL_INT_PLUS_NANO;
+	*length = gts->num_avail_all_scales * 2;
+
+	return IIO_AVAIL_LIST;
+}
+EXPORT_SYMBOL_GPL(iio_gts_all_avail_scales);
+
+/**
+ * iio_gts_avail_scales_for_time - list scales for integration time
+ * @gts:	Gain time scale descriptor
+ * @time:	Integration time for which the scales are listed
+ * @vals:	Returned array of supported scales
+ * @type:	Type of returned scale values
+ * @length:	Amount of returned values in array
+ *
+ * Drivers which do not allow scale setting to change integration time can
+ * use this helper to list only the scales whic are valid for given integration
+ * time.
+ *
+ * Returns a value suitable to be returned from read_avail or a negative error
+ */
+int iio_gts_avail_scales_for_time(struct iio_gts *gts, int time,
+				  const int **vals, int *type, int *length)
+{
+	int i;
+
+	for (i = 0; i < gts->num_itime; i++)
+		if (gts->itime_table[i].time_us == time)
+			break;
+
+	if (i == gts->num_itime)
+		return -EINVAL;
+
+	*vals = gts->per_time_avail_scale_tables[i];
+	*type = IIO_VAL_INT_PLUS_NANO;
+	*length = gts->num_hwgain * 2;
+
+	return IIO_AVAIL_LIST;
+}
+EXPORT_SYMBOL_GPL(iio_gts_avail_scales_for_time);
+
+/**
+ * iio_gts_avail_times - helper for listing available integration times
+ * @gts:	Gain time scale descriptor
+ * @vals:	Returned array of supported timees
+ * @type:	Type of returned scale values
+ * @length:	Amount of returned values in array
+ *
+ * Returns a value suitable to be returned from read_avail or a negative error
+ */
+int iio_gts_avail_times(struct iio_gts *gts,  const int **vals, int *type,
+			int *length)
+{
+	/*
+	 * Using this function prior building the tables is a driver-error
+	 * which should be fixed when the driver is tested for a first time
+	 */
+	if (WARN_ON(!gts->num_avail_time_tables))
+		return -EINVAL;
+
+	*vals = gts->avail_time_tables;
+	*type = IIO_VAL_INT;
+	*length = gts->num_avail_time_tables;
+
+	return IIO_AVAIL_LIST;
+}
+EXPORT_SYMBOL_GPL(iio_gts_avail_times);
+
+/**
+ * iio_gts_valid_time - check if given integration time is valid
+ * @gts:	Gain time scale descriptor
+ * @time_us:	Integration time to check
+ *
+ * Return:	True if given time is supported by device. False if not
+ */
+bool iio_gts_valid_time(struct iio_gts *gts, int time_us)
+{
+	return iio_gts_find_itime_by_time(gts, time_us);
+}
+EXPORT_SYMBOL_GPL(iio_gts_valid_time);
+
+int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain)
+{
+	int i;
+
+	for (i = 0; i < gts->num_hwgain; i++)
+		if (gts->hwgain_table[i].gain == gain)
+			return gts->hwgain_table[i].sel;
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(iio_gts_find_sel_by_gain);
+
+bool iio_gts_valid_gain(struct iio_gts *gts, int gain)
+{
+	return iio_gts_find_sel_by_gain(gts, gain) >= 0;
+}
+EXPORT_SYMBOL_GPL(iio_gts_valid_gain);
+
+int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel)
+{
+	int i;
+
+	for (i = 0; i < gts->num_hwgain; i++)
+		if (gts->hwgain_table[i].sel == sel)
+			return gts->hwgain_table[i].gain;
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(iio_gts_find_gain_by_sel);
+
+int iio_gts_get_min_gain(struct iio_gts *gts)
+{
+	int i, min = -EINVAL;
+
+	for (i = 0; i < gts->num_hwgain; i++) {
+		int gain = gts->hwgain_table[i].gain;
+
+		if (min == -EINVAL)
+			min = gain;
+		else
+			min = min(min, gain);
+	}
+
+	return min;
+}
+EXPORT_SYMBOL_GPL(iio_gts_get_min_gain);
+
+/**
+ * iio_find_closest_gain_low - Find the closest lower matching gain
+ * @gts:	Gain time scale descriptor
+ * @gain:	reference gain for which the closest match is searched
+ * @in_range:	indicate if the reference gain was actually in the range of
+ *		supported gains.
+ *
+ * Search for closest supported gain that is lower than or equal to the
+ * gain given as a parameter. This is usable for drivers which do not require
+ * user to request exact matching gain but rather fo rounding to a supported
+ * gain value which is equal or lower (setting lower gain is typical for
+ * avoiding saturation)
+ *
+ * Return:	The closest matching supported gain or -EINVAL is reference
+ *		gain was smaller than the smallest supported gain.
+ */
+int iio_find_closest_gain_low(struct iio_gts *gts, int gain, bool *in_range)
+{
+	int i, diff = 0, min = 0;
+	int best = -1;
+
+	*in_range = false;
+
+	for (i = 0; i < gts->num_hwgain; i++) {
+		/*
+		 * It is not expected this function is called for an exactly
+		 * matching gain.
+		 */
+		if (unlikely(gain == gts->hwgain_table[i].gain)) {
+			*in_range = true;
+			return gain;
+		}
+		if (!min)
+			min = gts->hwgain_table[i].gain;
+		else
+			min = min(min, gts->hwgain_table[i].gain);
+		if (gain > gts->hwgain_table[i].gain) {
+			if (!diff) {
+				diff = gain - gts->hwgain_table[i].gain;
+				best = i;
+			} else {
+				int tmp = gain - gts->hwgain_table[i].gain;
+
+				if (tmp < diff) {
+					diff = tmp;
+					best = i;
+				}
+			}
+		} else {
+			/*
+			 * We found valid hwgain which is greater than
+			 * reference. So, unless we return a failure below we
+			 * will have found an in-range gain
+			 */
+			*in_range = true;
+		}
+	}
+	/* The requested gain was smaller than anything we support */
+	if (!diff) {
+		*in_range = false;
+
+		return -EINVAL;
+	}
+
+	return gts->hwgain_table[best].gain;
+}
+EXPORT_SYMBOL_GPL(iio_find_closest_gain_low);
+
+int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts,
+						       int sel)
+{
+	const struct iio_itime_sel_mul *time;
+
+	time = iio_gts_find_itime_by_sel(gts, sel);
+	if (!time)
+		return -EINVAL;
+
+	return time->mul;
+}
+EXPORT_SYMBOL_GPL(iio_gts_get_int_time_gain_multiplier_by_sel);
+
+/**
+ * iio_gts_find_gain_for_scale_using_time - Find gain by time and scale
+ * @gts:	Gain time scale descriptor
+ * @time_sel:	Integration time selector correspondig to the time gain is
+ *		searhed for
+ * @scale_int:	Integral part of the scale (typically val1)
+ * @scale_nano:	Fractional part of the scale (nano or ppb)
+ * @gain:	Pointer to value where gain is stored.
+ *
+ * In some cases the light sensors may want to find a gain setting which
+ * corresponds given scale and integration time. Sensors which fill the
+ * gain and time tables may use this helper to retrieve the gain.
+ *
+ * Return:	0 on success. -EINVAL if gain matching the parameters is not
+ *		found.
+ */
+int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel,
+					   int scale_int, int scale_nano,
+					   int *gain)
+{
+	u64 scale_linear;
+	int ret, mul;
+
+	ret = iio_gts_linearize(scale_int, scale_nano, NANO, &scale_linear);
+	if (ret)
+		return ret;
+
+	ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, time_sel);
+	if (ret < 0)
+		return ret;
+
+	mul = ret;
+
+	ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain);
+
+	if (ret || !iio_gts_valid_gain(gts, *gain))
+		return -EINVAL;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iio_gts_find_gain_for_scale_using_time);
+
+/*
+ * iio_gts_find_gain_sel_for_scale_using_time - Fetch gain selector.
+ * See iio_gts_find_gain_for_scale_using_time() for more information
+ */
+int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel,
+					       int scale_int, int scale_nano,
+					       int *gain_sel)
+{
+	int gain, ret;
+
+	ret = iio_gts_find_gain_for_scale_using_time(gts, time_sel, scale_int,
+						     scale_nano, &gain);
+	if (ret)
+		return ret;
+
+	ret = iio_gts_find_sel_by_gain(gts, gain);
+	if (ret < 0)
+		return ret;
+
+	*gain_sel = ret;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iio_gts_find_gain_sel_for_scale_using_time);
+
+int iio_gts_find_time_and_gain_sel_for_scale(struct iio_gts *gts, int scale_int,
+					     int scale_nano, int *gain_sel,
+					     int *time_sel)
+{
+	int ret, i;
+
+	for (i = 0; i < gts->num_itime; i++) {
+		*time_sel = gts->itime_table[i].sel;
+		ret = iio_gts_find_gain_sel_for_scale_using_time(gts, *time_sel,
+					scale_int, scale_nano, gain_sel);
+		if (!ret)
+			return 0;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(iio_gts_find_time_and_gain_sel_for_scale);
+
+int iio_gts_find_int_time_by_sel(struct iio_gts *gts, int sel)
+{
+	const struct iio_itime_sel_mul *itime;
+
+	itime = iio_gts_find_itime_by_sel(gts, sel);
+	if (!itime)
+		return -EINVAL;
+
+	return itime->time_us;
+}
+EXPORT_SYMBOL_GPL(iio_gts_find_int_time_by_sel);
+
+int iio_gts_find_sel_by_int_time(struct iio_gts *gts, int time)
+{
+	const struct iio_itime_sel_mul *itime;
+
+	itime = iio_gts_find_itime_by_time(gts, time);
+	if (!itime)
+		return -EINVAL;
+
+	return itime->sel;
+}
+EXPORT_SYMBOL_GPL(iio_gts_find_sel_by_int_time);
+
+int iio_gts_get_total_gain_by_sel(struct iio_gts *gts, int gsel, int tsel)
+{
+	int ret, tmp;
+
+	ret = iio_gts_find_gain_by_sel(gts, gsel);
+	if (ret < 0)
+		return ret;
+
+	tmp = ret;
+
+	/*
+	 * TODO: Would these helpers provde any value for cases where we just
+	 * use table of gains and no integration time? This would be a standard
+	 * format for gain table representation and regval => gain / gain =>
+	 * regval conversions. OTOH, a dummy table based conversion is a memory
+	 * hog in cases where the gain could be computed simply based on simple
+	 * multiplication / bit-shift or by linear_ranges helpers.
+	 *
+	 * Currently we return an error if int-time table is not populated.
+	 */
+	ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, tsel);
+	if (ret < 0)
+		return ret;
+
+	return tmp * ret;
+}
+EXPORT_SYMBOL_GPL(iio_gts_get_total_gain_by_sel);
+
+int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time)
+{
+	const struct iio_itime_sel_mul *itime;
+
+	if (!iio_gts_valid_gain(gts, gain))
+		return -EINVAL;
+
+	if (!gts->num_itime)
+		return gain;
+
+	itime = iio_gts_find_itime_by_time(gts, time);
+	if (!itime)
+		return -EINVAL;
+
+	return gain * itime->mul;
+}
+EXPORT_SYMBOL(iio_gts_get_total_gain);
+
+static int iio_gts_get_scale_linear(struct iio_gts *gts, int gain, int time,
+				    u64 *scale)
+{
+	int total_gain;
+	u64 tmp;
+
+	total_gain = iio_gts_get_total_gain(gts, gain, time);
+	if (total_gain < 0)
+		return total_gain;
+
+	tmp = gts->max_scale;
+
+	do_div(tmp, total_gain);
+
+	*scale = tmp;
+
+	return 0;
+}
+
+int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int,
+		      int *scale_nano)
+{
+	u64 lin_scale;
+	int ret;
+
+	ret = iio_gts_get_scale_linear(gts, gain, time, &lin_scale);
+	if (ret)
+		return ret;
+
+	return iio_gts_delinearize(lin_scale, NANO, scale_int, scale_nano);
+}
+EXPORT_SYMBOL_GPL(iio_gts_get_scale);
+
+/**
+ * iio_gts_find_new_gain_sel_by_old_gain_time - compensate time change
+ * @gts:		Gain time scale descriptor
+ * @old_gain:		Previously set gain
+ * @old_time_sel:	Selector corresponding previously set time
+ * @new_time_sel:	Selector corresponding new time to be set
+ * @new_gain:		Pointer to value where new gain is to be written
+ *
+ * We may want to mitigate the scale change caused by setting a new integration
+ * time (for a light sensor) by also updating the (HW)gain. This helper computes
+ * new gain value to maintain the scale with new integration time.
+ *
+ * Return: 0 on success. -EINVAL if gain matching the new time is not found.
+ */
+int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts,
+					       int old_gain, int old_time_sel,
+					       int new_time_sel, int *new_gain)
+{
+	const struct iio_itime_sel_mul *itime_old, *itime_new;
+	u64 scale;
+	int ret;
+
+	itime_old = iio_gts_find_itime_by_sel(gts, old_time_sel);
+	if (!itime_old)
+		return -EINVAL;
+
+	itime_new = iio_gts_find_itime_by_sel(gts, new_time_sel);
+	if (!itime_new)
+		return -EINVAL;
+
+	ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us,
+				       &scale);
+	if (ret)
+		return ret;
+
+	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
+				      new_gain);
+	if (ret)
+		return -EINVAL;
+
+	if (!iio_gts_valid_gain(gts, *new_gain))
+		return -EINVAL;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iio_gts_find_new_gain_sel_by_old_gain_time);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>");
+MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers");
diff --git a/drivers/iio/light/iio-gts-helper.h b/drivers/iio/light/iio-gts-helper.h
new file mode 100644
index 000000000000..7b1f6a0eeb75
--- /dev/null
+++ b/drivers/iio/light/iio-gts-helper.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* gain-time-scale conversion helpers for IIO light sensors
+ *
+ * Copyright (c) 2023 Matti Vaittinen <mazziesaccount@gmail.com>
+ */
+
+#ifndef __GAIN_TIME_SCALE_HELPER__
+#define __GAIN_TIME_SCALE_HELPER__
+
+/**
+ * struct iio_gain_sel_pair - gain - selector values
+ *
+ * In many cases devices like light sensors allow setting signal amplification
+ * (gain) using a register interface. This structure describes amplification
+ * and corresponding selector (register value)
+ *
+ * @gain:	Gain (multiplication) value.
+ * @sel:	Selector (usually register value) used to indicate this gain
+ */
+struct iio_gain_sel_pair {
+	int gain;
+	int sel;
+};
+
+/**
+ * struct iio_itime_sel_mul - integration time description
+ *
+ * In many cases devices like light sensors allow setting the duration of
+ * collecting data. Typically this duration has also an impact to the magnitude
+ * of measured values (gain). This structure describes the relation of
+ * integration time and amplification as well as corresponding selector
+ * (register value).
+ *
+ * An example could be a sensor allowing 50, 100, 200 and 400 mS times. The
+ * respective multiplication values could be 50 mS => 1, 100 mS => 2,
+ * 200 mS => 4 and 400 mS => 8 assuming the impact of integration time would be
+ * linear in a way that when collecting data for 50 mS caused value X, doubling
+ * the data collection time caused value 2X etc..
+ *
+ * @time_us:	Integration time in microseconds.
+ * @sel:	Selector (usually register value) used to indicate this time
+ * @mul:	Multiplication to the values caused by this time.
+ */
+struct iio_itime_sel_mul {
+	int time_us;
+	int sel;
+	int mul;
+};
+
+struct iio_gts {
+	u64 max_scale;
+	const struct iio_gain_sel_pair *hwgain_table;
+	int num_hwgain;
+	const struct iio_itime_sel_mul *itime_table;
+	int num_itime;
+	int **per_time_avail_scale_tables;
+	int *avail_all_scales_table;
+	int num_avail_all_scales;
+	int *avail_time_tables;
+	int num_avail_time_tables;
+};
+
+#define GAIN_SCALE_GAIN(_gain, _sel)			\
+{							\
+	.gain = (_gain),				\
+	.sel = (_sel),					\
+}
+
+#define GAIN_SCALE_ITIME_US(_itime, _sel, _mul)		\
+{							\
+	.time_us = (_itime),				\
+	.sel = (_sel),					\
+	.mul = (_mul),					\
+}
+
+int iio_init_iio_gts(int max_scale_int, int max_scale_nano,
+		     const struct iio_gain_sel_pair *gain_tbl, int num_gain,
+		     const struct iio_itime_sel_mul *tim_tbl, int num_times,
+		     struct iio_gts *gts);
+
+bool iio_gts_valid_gain(struct iio_gts *gts, int gain);
+bool iio_gts_valid_time(struct iio_gts *gts, int time_us);
+
+int iio_gts_get_total_gain_by_sel(struct iio_gts *gts, int gsel, int tsel);
+int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time);
+
+int iio_find_closest_gain_low(struct iio_gts *gts, int gain, bool *in_range);
+int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel);
+int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain);
+int iio_gts_get_min_gain(struct iio_gts *gts);
+int iio_gts_find_int_time_by_sel(struct iio_gts *gts, int sel);
+int iio_gts_find_sel_by_int_time(struct iio_gts *gts, int time);
+
+int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts,
+						       int sel);
+int iio_gts_total_gain_to_scale(struct iio_gts *gts, int total_gain,
+				int *scale_int, int *scale_nano);
+int iio_gts_scale_to_total_gain(struct iio_gts *gts, int scale_int,
+				int scale_nano, int *gain_tot);
+int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel,
+					       int scale_int, int scale_nano,
+					       int *gain_sel);
+int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel,
+					   int scale_int, int scale_nano,
+					   int *gain);
+int iio_gts_find_time_and_gain_sel_for_scale(struct iio_gts *gts, int scale_int,
+					     int scale_nano, int *gain_sel,
+					     int *time_sel);
+int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int,
+		      int *scale_nano);
+int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts,
+					       int old_gain, int old_time_sel,
+					       int new_time_sel, int *new_gain);
+int iio_gts_build_avail_tables(struct iio_gts *gts);
+int devm_iio_gts_build_avail_tables(struct device *dev, struct iio_gts *gts);
+int iio_gts_build_avail_scale_table(struct iio_gts *gts);
+int devm_iio_gts_build_avail_scale_table(struct device *dev, struct iio_gts *gts);
+int iio_gts_build_avail_time_table(struct iio_gts *gts);
+int devm_iio_gts_build_avail_time_table(struct device *dev, struct iio_gts *gts);
+void iio_gts_purge_avail_scale_table(struct iio_gts *gts);
+void iio_gts_purge_avail_time_table(struct iio_gts *gts);
+void iio_gts_purge_avail_tables(struct iio_gts *gts);
+int iio_gts_avail_times(struct iio_gts *gts,  const int **vals, int *type,
+			int *length);
+int iio_gts_all_avail_scales(struct iio_gts *gts, const int **vals, int *type,
+			     int *length);
+int iio_gts_avail_scales_for_time(struct iio_gts *gts, int time,
+				  const int **vals, int *type, int *length);
+
+#endif
-- 
2.39.2


-- 
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* [PATCH v2 3/6] iio: test: test gain-time-scale helpers
  2023-03-02 10:57 [PATCH v2 0/6] Support ROHM BU27034 ALS sensor Matti Vaittinen
  2023-03-02 10:57 ` [PATCH v2 1/6] dt-bindings: iio: light: Support ROHM BU27034 Matti Vaittinen
  2023-03-02 10:57 ` [PATCH v2 2/6] iio: light: Add gain-time-scale helpers Matti Vaittinen
@ 2023-03-02 10:58 ` Matti Vaittinen
  2023-03-02 10:58 ` [PATCH v2 4/6] MAINTAINERS: Add IIO " Matti Vaittinen
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-02 10:58 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Jonathan Cameron, Lars-Peter Clausen, Matti Vaittinen,
	Liam Beguin, Randy Dunlap, Masahiro Yamada, linux-kernel,
	linux-iio

[-- Attachment #1: Type: text/plain, Size: 20099 bytes --]

Some light sensors can adjust both the HW-gain and integration time.
There are cases where adjusting the integration time has similar impact
to the scale of the reported values as gain setting has.

IIO users do typically expect to handle scale by a single writable 'scale'
entry. Driver should then adjust the gain/time accordingly.

It however is difficult for a driver to know whether it should change
gain or integration time to meet the requested scale. Usually it is
preferred to have longer integration time which usually improves
accuracy, but there may be use-cases where long measurement times can be
an issue. Thus it can be preferable to allow also changing the
integration time - but mitigate the scale impact by also changing the gain
underneath. Eg, if integration time change doubles the measured values,
the driver can reduce the HW-gain to half.

The theory of the computations of gain-time-scale is simple. However,
some people (undersigned) got that implemented wrong for more than once.
Hence some gain-time-scale helpers were introduced.

Add some simple tests to verify the most hairy functions.

Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>

---
Changes since RFCv1
- add tests for available scales/times helpers
- adapt to renamed iio-gts-helpers.h header
---
 drivers/iio/test/Kconfig        |  15 +
 drivers/iio/test/Makefile       |   1 +
 drivers/iio/test/iio-test-gts.c | 537 ++++++++++++++++++++++++++++++++
 3 files changed, 553 insertions(+)
 create mode 100644 drivers/iio/test/iio-test-gts.c

diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig
index 0b6e4e278a2f..b57f1fc440e6 100644
--- a/drivers/iio/test/Kconfig
+++ b/drivers/iio/test/Kconfig
@@ -4,6 +4,19 @@
 #
 
 # Keep in alphabetical order
+config IIO_GTS_KUNIT_TEST
+	tristate "Test IIO formatting functions" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	select IIO_GTS_HELPER
+	default KUNIT_ALL_TESTS
+	help
+	  build unit tests for the IIO light sensor gain-time-scale helpers.
+
+	  For more information on KUnit and unit tests in general, please refer
+	  to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+	  If unsure, say N. Keep in alphabetical order
+
 config IIO_RESCALE_KUNIT_TEST
 	tristate "Test IIO rescale conversion functions" if !KUNIT_ALL_TESTS
 	depends on KUNIT && IIO_RESCALE
@@ -27,3 +40,5 @@ config IIO_FORMAT_KUNIT_TEST
 	  to the KUnit documentation in Documentation/dev-tools/kunit/.
 
 	  If unsure, say N.
+
+
diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile
index d76eaf36da82..e9a4cf1ff57f 100644
--- a/drivers/iio/test/Makefile
+++ b/drivers/iio/test/Makefile
@@ -6,4 +6,5 @@
 # Keep in alphabetical order
 obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o
 obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o
+obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o
 CFLAGS_iio-test-format.o += $(DISABLE_STRUCTLEAK_PLUGIN)
diff --git a/drivers/iio/test/iio-test-gts.c b/drivers/iio/test/iio-test-gts.c
new file mode 100644
index 000000000000..7b5370c3e09a
--- /dev/null
+++ b/drivers/iio/test/iio-test-gts.c
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Unit tests for IIO light sensor gain-time-scale helpers
+ *
+ * Copyright (c) 2023 Matti Vaittinen <mazziesaccount@gmail.com>
+ */
+
+#include <kunit/test.h>
+#include <linux/iio/types.h>
+
+#include "../light/iio-gts-helper.h"
+
+/*
+ * Please, read the "rant" from the top of the lib/test_linear_ranges.c if
+ * you see a line of helper code which is not being tested.
+ *
+ * Then, please look at the line which is not being tested. Is this line
+ * somehow unusually complex? If answer is "no", then chances are that the
+ * "development inertia" caused by adding a test exceeds the benefits.
+ *
+ * If yes, then adding a test is probably a good idea but please stop for a
+ * moment and consider the effort of changing all the tests when code gets
+ * refactored. Eventually it neeeds to be.
+ */
+
+#define TEST_TSEL_50		1
+#define TEST_TSEL_X_MIN		TEST_TSEL_50
+#define TEST_TSEL_100		0
+#define TEST_TSEL_200		2
+#define TEST_TSEL_400		4
+#define TEST_TSEL_X_MAX		TEST_TSEL_400
+
+#define TEST_GSEL_1		0x00
+#define TEST_GSEL_X_MIN		TEST_GSEL_1
+#define TEST_GSEL_4		0x08
+#define TEST_GSEL_16		0x0a
+#define TEST_GSEL_32		0x0b
+#define TEST_GSEL_64		0x0c
+#define TEST_GSEL_256		0x18
+#define TEST_GSEL_512		0x19
+#define TEST_GSEL_1024		0x1a
+#define TEST_GSEL_2048		0x1b
+#define TEST_GSEL_4096		0x1c
+#define TEST_GSEL_X_MAX		TEST_GSEL_4096
+
+#define TEST_SCALE_1X		64
+#define TEST_SCALE_MIN_X	TEST_SCALE_1X
+#define TEST_SCALE_2X		32
+#define TEST_SCALE_4X		16
+#define TEST_SCALE_8X		8
+#define TEST_SCALE_16X		4
+#define TEST_SCALE_32X		2
+#define TEST_SCALE_64X		1
+
+#define TEST_SCALE_NANO_128X	500000000
+#define TEST_SCALE_NANO_256X	250000000
+#define TEST_SCALE_NANO_512X	125000000
+#define TEST_SCALE_NANO_1024X	62500000
+#define TEST_SCALE_NANO_2048X	31250000
+#define TEST_SCALE_NANO_4096X	15625000
+#define TEST_SCALE_NANO_4096X2	7812500
+#define TEST_SCALE_NANO_4096X4	3906250
+#define TEST_SCALE_NANO_4096X8	1953125
+
+#define TEST_SCALE_NANO_MAX_X TEST_SCALE_NANO_4096X8
+
+static const struct iio_gain_sel_pair gts_test_gains[] = {
+	GAIN_SCALE_GAIN(1, TEST_GSEL_1),
+	GAIN_SCALE_GAIN(4, TEST_GSEL_4),
+	GAIN_SCALE_GAIN(16, TEST_GSEL_16),
+	GAIN_SCALE_GAIN(32, TEST_GSEL_32),
+	GAIN_SCALE_GAIN(64, TEST_GSEL_64),
+	GAIN_SCALE_GAIN(256, TEST_GSEL_256),
+	GAIN_SCALE_GAIN(512, TEST_GSEL_512),
+	GAIN_SCALE_GAIN(1024, TEST_GSEL_1024),
+	GAIN_SCALE_GAIN(2048, TEST_GSEL_2048),
+	GAIN_SCALE_GAIN(4096, TEST_GSEL_4096),
+#define HWGAIN_MAX 4096
+};
+
+static const struct iio_itime_sel_mul gts_test_itimes[] = {
+	GAIN_SCALE_ITIME_US(400 * 1000, TEST_TSEL_400, 8),
+	GAIN_SCALE_ITIME_US(200 * 1000, TEST_TSEL_200, 4),
+	GAIN_SCALE_ITIME_US(100 * 1000, TEST_TSEL_100, 2),
+	GAIN_SCALE_ITIME_US(50 * 1000, TEST_TSEL_50, 1),
+#define TIMEGAIN_MAX 8
+};
+#define TOTAL_GAIN_MAX	(HWGAIN_MAX * TIMEGAIN_MAX)
+
+static int test_init_iio_gain_scale(struct iio_gts *gts, int max_scale_int,
+				int max_scale_nano)
+{
+	int ret;
+
+	ret = iio_init_iio_gts(max_scale_int, max_scale_nano, gts_test_gains,
+			       ARRAY_SIZE(gts_test_gains), gts_test_itimes,
+			       ARRAY_SIZE(gts_test_itimes), gts);
+
+	return ret;
+}
+
+static void test_iio_gts_find_gain_for_scale_using_time(struct kunit *test)
+{
+	struct iio_gts gts;
+	int ret, gain;
+
+	ret = test_init_iio_gain_scale(&gts, TEST_SCALE_1X, 0);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	ret = iio_gts_find_gain_for_scale_using_time(&gts, TEST_TSEL_100,
+						     TEST_SCALE_8X, 0, &gain);
+	/*
+	 * Meas time 100 => gain by time 2x
+	 * TEST_SCALE_8X matches total gain 8x
+	 * => required HWGAIN 4x
+	 */
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, 4, gain);
+
+	ret = iio_gts_find_gain_for_scale_using_time(&gts, TEST_TSEL_200, 0,
+						TEST_SCALE_NANO_256X, &gain);
+	/*
+	 * Meas time 200 => gain by time 4x
+	 * TEST_SCALE_256X matches total gain 256x
+	 * => required HWGAIN 256/4 => 64x
+	 */
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, 64, gain);
+
+	/* Min time, Min gain */
+	ret = iio_gts_find_gain_for_scale_using_time(&gts, TEST_TSEL_X_MIN,
+						TEST_SCALE_MIN_X, 0, &gain);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, 1, gain);
+
+	/* Max time, Max gain */
+	ret = iio_gts_find_gain_for_scale_using_time(&gts, TEST_TSEL_X_MAX, 0,
+						TEST_SCALE_NANO_MAX_X, &gain);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, 4096, gain);
+
+	ret = iio_gts_find_gain_for_scale_using_time(&gts, TEST_TSEL_100, 0,
+						TEST_SCALE_NANO_256X, &gain);
+	/*
+	 * Meas time 100 => gain by time 2x
+	 * TEST_SCALE_256X matches total gain 256x
+	 * => required HWGAIN 256/2 => 128x (not in gain-table - unsupported)
+	 */
+	KUNIT_EXPECT_NE(test, 0, ret);
+
+	ret = iio_gts_find_gain_for_scale_using_time(&gts, TEST_TSEL_200, 0,
+						TEST_SCALE_NANO_MAX_X, &gain);
+	/* We can't reach the max gain with integration time smaller than MAX */
+	KUNIT_EXPECT_NE(test, 0, ret);
+
+	ret = iio_gts_find_gain_for_scale_using_time(&gts, TEST_TSEL_50, 0,
+						TEST_SCALE_NANO_MAX_X, &gain);
+	/* We can't reach the max gain with integration time smaller than MAX */
+	KUNIT_EXPECT_NE(test, 0, ret);
+}
+
+static void test_iio_gts_find_time_and_gain_sel_for_scale(struct kunit *test)
+{
+	struct iio_gts gts;
+	int ret, gain_sel, time_sel;
+
+	ret = test_init_iio_gain_scale(&gts, TEST_SCALE_1X, 0);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	ret = iio_gts_find_time_and_gain_sel_for_scale(&gts, 0,
+			TEST_SCALE_NANO_256X, &gain_sel, &time_sel);
+	/*
+	 * We should find time 400 (8x) and gain 256/8 => 32x because the
+	 * time 400 is listed first
+	 */
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, TEST_GSEL_32, gain_sel);
+	KUNIT_EXPECT_EQ(test, TEST_TSEL_400, time_sel);
+
+	ret = iio_gts_find_time_and_gain_sel_for_scale(&gts, TEST_SCALE_64X,
+						       0, &gain_sel, &time_sel);
+	/*
+	 * We should find time 200 (4x) and gain 64/4 => 16x. The most
+	 * preferred time 400 (8x) would require gain 8x - which is not
+	 * "supported".
+	 */
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, TEST_GSEL_16, gain_sel);
+	KUNIT_EXPECT_EQ(test, TEST_TSEL_200, time_sel);
+
+	/* Min gain */
+	ret = iio_gts_find_time_and_gain_sel_for_scale(&gts, TEST_SCALE_MIN_X,
+						0, &gain_sel, &time_sel);
+	/*
+	 * We should find time 400 (8x) and gain 256/8 => 32x because the
+	 * time 400 is listed first
+	 */
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, TEST_GSEL_1, gain_sel);
+	KUNIT_EXPECT_EQ(test, TEST_TSEL_50, time_sel);
+
+	/* Max gain */
+	ret = iio_gts_find_time_and_gain_sel_for_scale(&gts, 0,
+			TEST_SCALE_NANO_MAX_X, &gain_sel, &time_sel);
+	/*
+	 * We should find time 400 (8x) and gain 256/8 => 32x because the
+	 * time 400 is listed first
+	 */
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, TEST_GSEL_X_MAX, gain_sel);
+	KUNIT_EXPECT_EQ(test, TEST_TSEL_X_MAX, time_sel);
+}
+
+static void test_iio_gts_get_total_gain_by_sel(struct kunit *test)
+{
+	struct iio_gts gts;
+	int ret, gain_sel, time_sel;
+
+	ret = test_init_iio_gain_scale(&gts, TEST_SCALE_1X, 0);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	/* gain x32, time x4 => total gain 32 * 4 = 128 */
+	gain_sel = TEST_GSEL_32;
+	time_sel = TEST_TSEL_200;
+
+	ret = iio_gts_get_total_gain_by_sel(&gts, gain_sel, time_sel);
+	/* gain x32, time x8 => total gain 32 * 4 = 128 */
+	KUNIT_EXPECT_EQ(test, 128, ret);
+
+	gain_sel = TEST_GSEL_X_MAX;
+	time_sel = TEST_TSEL_X_MAX;
+	ret = iio_gts_get_total_gain_by_sel(&gts, gain_sel, time_sel);
+	KUNIT_EXPECT_EQ(test, TOTAL_GAIN_MAX, ret);
+
+	gain_sel = TEST_GSEL_X_MIN;
+	time_sel = TEST_TSEL_X_MIN;
+	ret = iio_gts_get_total_gain_by_sel(&gts, gain_sel, time_sel);
+	KUNIT_EXPECT_EQ(test, 1, ret);
+}
+
+static void test_iio_gts_find_new_gain_sel_by_old_gain_time(struct kunit *test)
+{
+	struct iio_gts gts;
+	int ret, old_gain, new_gain, old_time_sel, new_time_sel;
+
+	ret = test_init_iio_gain_scale(&gts, TEST_SCALE_1X, 0);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	old_gain = 32;
+	old_time_sel = TEST_TSEL_200;
+	new_time_sel = TEST_TSEL_400;
+
+	ret = iio_gts_find_new_gain_sel_by_old_gain_time(&gts, old_gain,
+					old_time_sel, new_time_sel, &new_gain);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	/*
+	 * Doubling the integration time doubles the total gain - so old
+	 * (hw)gain must be divided by two to compensate. => 32 / 2 => 16
+	 */
+	KUNIT_EXPECT_EQ(test, 16, new_gain);
+
+	old_gain = 4;
+	old_time_sel = TEST_TSEL_50;
+	new_time_sel = TEST_TSEL_200;
+	ret = iio_gts_find_new_gain_sel_by_old_gain_time(&gts, old_gain,
+					old_time_sel, new_time_sel, &new_gain);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	/*
+	 * gain by time 1x => 4x - (hw)gain 4x => 1x
+	 */
+	KUNIT_EXPECT_EQ(test, 1, new_gain);
+
+	old_gain = 512;
+	old_time_sel = TEST_TSEL_400;
+	new_time_sel = TEST_TSEL_50;
+	ret = iio_gts_find_new_gain_sel_by_old_gain_time(&gts, old_gain,
+					old_time_sel, new_time_sel, &new_gain);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	/*
+	 * gain by time 8x => 1x - (hw)gain 512x => 4096x)
+	 */
+	KUNIT_EXPECT_EQ(test, 4096, new_gain);
+
+	/* Unsupported gain 2x */
+	old_gain = 4;
+	old_time_sel = TEST_TSEL_200;
+	new_time_sel = TEST_TSEL_400;
+	ret = iio_gts_find_new_gain_sel_by_old_gain_time(&gts, old_gain,
+					old_time_sel, new_time_sel, &new_gain);
+	KUNIT_EXPECT_NE(test, 0, ret);
+
+	/* Too small gain */
+	old_gain = 4;
+	old_time_sel = TEST_TSEL_50;
+	new_time_sel = TEST_TSEL_400;
+	ret = iio_gts_find_new_gain_sel_by_old_gain_time(&gts, old_gain,
+					old_time_sel, new_time_sel, &new_gain);
+	KUNIT_EXPECT_NE(test, 0, ret);
+
+	/* Too big gain */
+	old_gain = 1024;
+	old_time_sel = TEST_TSEL_400;
+	new_time_sel = TEST_TSEL_50;
+	ret = iio_gts_find_new_gain_sel_by_old_gain_time(&gts, old_gain,
+					old_time_sel, new_time_sel, &new_gain);
+	KUNIT_EXPECT_NE(test, 0, ret);
+}
+
+static void test_iio_find_closest_gain_low(struct kunit *test)
+{
+	struct iio_gts gts;
+	bool in_range;
+	int ret;
+
+	const struct iio_gain_sel_pair gts_test_gains_gain_low[] = {
+		GAIN_SCALE_GAIN(4, TEST_GSEL_4),
+		GAIN_SCALE_GAIN(16, TEST_GSEL_16),
+		GAIN_SCALE_GAIN(32, TEST_GSEL_32),
+	};
+
+	ret = test_init_iio_gain_scale(&gts, TEST_SCALE_1X, 0);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	ret = iio_find_closest_gain_low(&gts, 2, &in_range);
+	KUNIT_EXPECT_EQ(test, 1, ret);
+	KUNIT_EXPECT_EQ(test, true, in_range);
+
+	ret = iio_find_closest_gain_low(&gts, 1, &in_range);
+	KUNIT_EXPECT_EQ(test, 1, ret);
+	KUNIT_EXPECT_EQ(test, true, in_range);
+
+	ret = iio_find_closest_gain_low(&gts, 4095, &in_range);
+	KUNIT_EXPECT_EQ(test, 2048, ret);
+	KUNIT_EXPECT_EQ(test, true, in_range);
+
+	ret = iio_find_closest_gain_low(&gts, 4097, &in_range);
+	KUNIT_EXPECT_EQ(test, 4096, ret);
+	KUNIT_EXPECT_EQ(test, false, in_range);
+
+	ret = iio_init_iio_gts(TEST_SCALE_1X, 0, gts_test_gains_gain_low,
+			       ARRAY_SIZE(gts_test_gains_gain_low),
+			       gts_test_itimes, ARRAY_SIZE(gts_test_itimes),
+			       &gts);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	ret = iio_find_closest_gain_low(&gts, 3, &in_range);
+	KUNIT_EXPECT_EQ(test, -EINVAL, ret);
+	KUNIT_EXPECT_EQ(test, false, in_range);
+}
+
+static void test_iio_gts_total_gain_to_scale(struct kunit *test)
+{
+	struct iio_gts gts;
+	int ret, scale_int, scale_nano;
+
+	ret = test_init_iio_gain_scale(&gts, TEST_SCALE_1X, 0);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	ret = iio_gts_total_gain_to_scale(&gts, 1, &scale_int, &scale_nano);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, TEST_SCALE_1X, scale_int);
+	KUNIT_EXPECT_EQ(test, 0, scale_nano);
+
+	ret = iio_gts_total_gain_to_scale(&gts, 1, &scale_int, &scale_nano);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, TEST_SCALE_1X, scale_int);
+	KUNIT_EXPECT_EQ(test, 0, scale_nano);
+
+	ret = iio_gts_total_gain_to_scale(&gts, 4096 * 8, &scale_int,
+					  &scale_nano);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	KUNIT_EXPECT_EQ(test, 0, scale_int);
+	KUNIT_EXPECT_EQ(test, TEST_SCALE_NANO_4096X8, scale_nano);
+}
+
+static void test_iio_gts_chk_times(struct kunit *test, const int *vals)
+{
+	static const int expected[] = {50000, 100000, 200000, 400000};
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(expected); i++)
+		KUNIT_EXPECT_EQ(test, expected[i], vals[i]);
+}
+
+static void test_iio_gts_chk_scales_all(struct kunit *test, struct iio_gts *gts,
+					const int *vals, int len)
+{
+	static const int gains[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512,
+				    1024, 2048, 4096, 4096 * 2, 4096 * 4,
+				    4096 * 8};
+
+	int expected[ARRAY_SIZE(gains) * 2];
+	int i, ret;
+	int exp_len = ARRAY_SIZE(gains) * 2;
+
+	KUNIT_EXPECT_EQ(test, exp_len, len);
+	if (len != exp_len)
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(gains); i++) {
+		ret = iio_gts_total_gain_to_scale(gts, gains[i],
+						  &expected[2 * i],
+						  &expected[2 * i + 1]);
+		KUNIT_EXPECT_EQ(test, 0, ret);
+		if (ret)
+			return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(expected); i++)
+		KUNIT_EXPECT_EQ(test, expected[i], vals[i]);
+}
+
+static void test_iio_gts_chk_scales_t200(struct kunit *test, struct iio_gts *gts,
+					 const int *vals, int len)
+{
+	/* The gain caused by time 200 is 4x */
+	static const int gains[] = {
+		1 * 4,
+		4 * 4,
+		16 * 4,
+		32 * 4,
+		64 * 4,
+		256 * 4,
+		512 * 4,
+		1024 * 4,
+		2048 * 4,
+		4096 * 4
+	};
+	int expected[ARRAY_SIZE(gains) * 2];
+	int i, ret;
+
+	KUNIT_EXPECT_EQ(test, 2 * ARRAY_SIZE(gains), len);
+	if (len < 2 * ARRAY_SIZE(gains))
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(gains); i++) {
+		ret = iio_gts_total_gain_to_scale(gts, gains[i],
+						  &expected[2 * i],
+						  &expected[2 * i + 1]);
+		KUNIT_EXPECT_EQ(test, 0, ret);
+		if (ret)
+			return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(expected); i++)
+		KUNIT_EXPECT_EQ(test, expected[i], vals[i]);
+}
+
+static void test_iio_gts_avail_test(struct kunit *test)
+{
+	struct iio_gts gts;
+	int ret;
+	int type, len;
+	const int *vals;
+
+	ret = test_init_iio_gain_scale(&gts, TEST_SCALE_1X, 0);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	ret = iio_gts_build_avail_tables(&gts);
+	KUNIT_EXPECT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	/* test table building for times and iio_gts_avail_times() */
+	ret = iio_gts_avail_times(&gts, &vals, &type, &len);
+	KUNIT_EXPECT_EQ(test, IIO_AVAIL_LIST, ret);
+	if (ret)
+		return;
+
+	KUNIT_EXPECT_EQ(test, IIO_VAL_INT, type);
+	KUNIT_EXPECT_EQ(test, 4, len);
+	if (len < 4)
+		return;
+
+	test_iio_gts_chk_times(test, vals);
+
+	/* Test table building for all scales and iio_gts_all_avail_scales() */
+	ret = iio_gts_all_avail_scales(&gts, &vals, &type, &len);
+	KUNIT_EXPECT_EQ(test, IIO_AVAIL_LIST, ret);
+	if (ret)
+		return;
+
+	KUNIT_EXPECT_EQ(test, IIO_VAL_INT_PLUS_NANO, type);
+
+	test_iio_gts_chk_scales_all(test, &gts, vals, len);
+
+	/*
+	 * Test table building for scales/time and
+	 * iio_gts_avail_scales_for_time()
+	 */
+	ret = iio_gts_avail_scales_for_time(&gts, 200000, &vals, &type, &len);
+	KUNIT_EXPECT_EQ(test, IIO_AVAIL_LIST, ret);
+	if (ret)
+		return;
+
+	KUNIT_EXPECT_EQ(test, IIO_VAL_INT_PLUS_NANO, type);
+	test_iio_gts_chk_scales_t200(test, &gts, vals, len);
+
+	iio_gts_purge_avail_tables(&gts);
+}
+
+static struct kunit_case iio_gts_test_cases[] = {
+		KUNIT_CASE(test_iio_gts_find_gain_for_scale_using_time),
+		KUNIT_CASE(test_iio_gts_find_time_and_gain_sel_for_scale),
+		KUNIT_CASE(test_iio_gts_get_total_gain_by_sel),
+		KUNIT_CASE(test_iio_gts_find_new_gain_sel_by_old_gain_time),
+		KUNIT_CASE(test_iio_find_closest_gain_low),
+		KUNIT_CASE(test_iio_gts_total_gain_to_scale),
+		KUNIT_CASE(test_iio_gts_avail_test),
+		{}
+};
+
+static struct kunit_suite iio_gts_test_suite = {
+	.name = "iio-gain-time-scale",
+	.test_cases = iio_gts_test_cases,
+};
+
+kunit_test_suite(iio_gts_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>");
+MODULE_DESCRIPTION("Test IIO light sensor gain-time-scale helpers");
-- 
2.39.2


-- 
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* [PATCH v2 4/6] MAINTAINERS: Add IIO gain-time-scale helpers
  2023-03-02 10:57 [PATCH v2 0/6] Support ROHM BU27034 ALS sensor Matti Vaittinen
                   ` (2 preceding siblings ...)
  2023-03-02 10:58 ` [PATCH v2 3/6] iio: test: test " Matti Vaittinen
@ 2023-03-02 10:58 ` Matti Vaittinen
  2023-03-02 10:58 ` [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor Matti Vaittinen
  2023-03-02 10:59 ` [PATCH v2 6/6] MAINTAINERS: Add ROHM BU27034 Matti Vaittinen
  5 siblings, 0 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-02 10:58 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Jonathan Cameron, linux-iio, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1328 bytes --]

Add myself as a maintainer for IIO light sensor helpers (helpers for
maintaining the scale while adjusting intergration time or gain) and
related Kunit tests.

Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
---
 MAINTAINERS | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 135d93368d36..af8516d5df36 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10059,6 +10059,14 @@ F:	Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector
 F:	Documentation/devicetree/bindings/iio/adc/envelope-detector.yaml
 F:	drivers/iio/adc/envelope-detector.c
 
+IIO LIGHT SENSOR GAIN-TIME-SCALE HELPERS
+M:	Matti Vaittinen <mazziesaccount@gmail.com>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+F:	drivers/iio/light/gain-time-scale-helper.c
+F:	drivers/iio/light/gain-time-scale-helper.h
+F:	drivers/iio/test/iio-test-gts.c
+
 IIO MULTIPLEXER
 M:	Peter Rosin <peda@axentia.se>
 L:	linux-iio@vger.kernel.org
-- 
2.39.2


-- 
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-02 10:57 [PATCH v2 0/6] Support ROHM BU27034 ALS sensor Matti Vaittinen
                   ` (3 preceding siblings ...)
  2023-03-02 10:58 ` [PATCH v2 4/6] MAINTAINERS: Add IIO " Matti Vaittinen
@ 2023-03-02 10:58 ` Matti Vaittinen
  2023-03-02 14:17   ` Matti Vaittinen
                     ` (2 more replies)
  2023-03-02 10:59 ` [PATCH v2 6/6] MAINTAINERS: Add ROHM BU27034 Matti Vaittinen
  5 siblings, 3 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-02 10:58 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Jonathan Cameron, Lars-Peter Clausen, Matti Vaittinen,
	Andy Shevchenko, Paul Gazzillo, Zhigang Shi, Shreeya Patel,
	Dmitry Osipenko, linux-kernel, linux-iio

[-- Attachment #1: Type: text/plain, Size: 45714 bytes --]

ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes
capable of detecting a very wide range of illuminance. Typical application
is adjusting LCD and backlight power of TVs and mobile phones.

Add initial  support for the ROHM BU27034 ambient light sensor.

NOTE:
	- Driver exposes 4 channels. One IIO_LIGHT channel providing the
	  calculated lux values based on measured data from diodes #0 and
	  #1. Additionally 3 IIO_INTENSITY channels are emitting the raw
	  register data from all diodes for more intense user-space
	  computations.
	- Sensor has adjustible GAIN values ranging from 1x to 4096x.
	- Sensor has adjustible measurement times 5, 55, 100, 200 and
	  400 mS. Driver does not support 5 mS which has special
	  limitations.
	- Driver exposes standard 'scale' adjustment which is
	  implemented by:
		1) Trying to adjust only the GAIN
		2) If GAIN adjustment only can't provide requested
		   scale, adjusting both the time and the gain is
		   attempted.
	- Driver exposes writable INT_TIME property which can be used
	  for adjusting the measurement time. Time adjustment will also
	  cause the driver to adjust the GAIN so that the overall scale
	  is not changed.
	- Runtime PM is not implemented.
	- Driver starts the measurement on the background when it is
	  probed. This improves the respnse time to read-requests
	  compared to starting the read only when data is requested.
	  When the most accurate 400 mS measurement time is used, data reads
	  would last quite long if measurement was started only on
	  demand. This, however, is not appealing for users who would
	  prefere power saving over measurement response time.

Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>

---
Changes since RFCv1
- (really) protect read-only registers
- fix get and set gain
- buffered mode
- Protect the whole sequences including meas_en/meas_dis to avoid messing
  up the enable / disable order
- typofixes / doc improvements
- change dropped GAIN_SCALE_ITIME_MS() to GAIN_SCALE_ITIME_US()
- use more accurate scale for lux channel (milli lux)
- provide available scales / integration times (using helpers).
- adapt to renamed iio-gts-helpers.h file
- bu27034 - longer lines in Kconfig
- Drop bu27034_meas_en and bu27034_meas_dis wrappers.
- Change device-name from bu27034-als to bu27034
---
 drivers/iio/light/Kconfig        |   12 +
 drivers/iio/light/Makefile       |    1 +
 drivers/iio/light/rohm-bu27034.c | 1491 ++++++++++++++++++++++++++++++
 3 files changed, 1504 insertions(+)
 create mode 100644 drivers/iio/light/rohm-bu27034.c

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 671d84f98c56..14e03732f57d 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -292,6 +292,18 @@ config JSA1212
 	  To compile this driver as a module, choose M here:
 	  the module will be called jsa1212.
 
+config ROHM_BU27034
+	tristate "ROHM BU27034 ambient light sensor"
+	depends on I2C
+	select REGMAP_I2C
+	select IIO_GTS_HELPER
+	help
+	  Enable support for the ROHM BU27034 ambient light sensor. ROHM BU27034
+	  is an ambient light sesnor with 3 channels and 3 photo diodes capable
+	  of detecting a very wide range of illuminance.
+	  Typical application is adjusting LCD and backlight power of TVs and
+	  mobile phones.
+
 config RPR0521
 	tristate "ROHM RPR0521 ALS and proximity sensor driver"
 	depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 82d14ebd3c11..e81c5ce60ccd 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_MAX44009)		+= max44009.o
 obj-$(CONFIG_NOA1305)		+= noa1305.o
 obj-$(CONFIG_OPT3001)		+= opt3001.o
 obj-$(CONFIG_PA12203001)	+= pa12203001.o
+obj-$(CONFIG_ROHM_BU27034)	+= rohm-bu27034.o
 obj-$(CONFIG_RPR0521)		+= rpr0521.o
 obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
 obj-$(CONFIG_SI1133)		+= si1133.o
diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c
new file mode 100644
index 000000000000..b8ca29af22e7
--- /dev/null
+++ b/drivers/iio/light/rohm-bu27034.c
@@ -0,0 +1,1491 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BU27034 ROHM Ambient Light Sensor
+ *
+ * Copyright (c) 2023, ROHM Semiconductor.
+ * https://fscdn.rohm.com/en/products/databook/datasheet/ic/sensor/light/bu27034nuc-e.pdf
+ */
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.h>
+
+#include "iio-gts-helper.h"
+
+#define BU27034_REG_SYSTEM_CONTROL	0x40
+#define BU27034_MASK_SW_RESET		BIT(7)
+#define BU27034_MASK_PART_ID		GENMASK(5, 0)
+#define BU27034_ID			0x19
+#define BU27034_REG_MODE_CONTROL1	0x41
+#define BU27034_MASK_MEAS_MODE		GENMASK(2, 0)
+
+#define BU27034_REG_MODE_CONTROL2	0x42
+#define BU27034_MASK_D01_GAIN		GENMASK(7, 3)
+#define BU27034_SHIFT_D01_GAIN		3
+#define BU27034_MASK_D2_GAIN_HI		GENMASK(7, 6)
+#define BU27034_MASK_D2_GAIN_LO		GENMASK(2, 0)
+#define BU27034_SHIFT_D2_GAIN		3
+
+#define BU27034_REG_MODE_CONTROL3	0x43
+#define BU27034_REG_MODE_CONTROL4	0x44
+#define BU27034_MASK_MEAS_EN		BIT(0)
+#define BU27034_MASK_VALID		BIT(7)
+#define BU27034_REG_DATA0_LO		0x50
+#define BU27034_REG_DATA1_LO		0x52
+#define BU27034_REG_DATA2_LO		0x54
+#define BU27034_REG_DATA2_HI		0x55
+#define BU27034_REG_MANUFACTURER_ID	0x92
+#define BU27034_REG_MAX BU27034_REG_MANUFACTURER_ID
+
+/*
+ * The BU27034 does not have interrupt or any other mechanism of triggering
+ * the data read when measurement has finished. Hence we poll the VALID bit in
+ * a thread. We will try to wake the thread BU27034_MEAS_WAIT_PREMATURE_MS
+ * milliseconds before the expected sampling time to prevent the drifting. Eg,
+ * If we constantly wake up a bit too late we would eventually skip a sample.
+ * And because the sleep can't wake up _exactly_ at given time this would be
+ * inevitable even if the sensor clock would be perfectly phase-locked to CPU
+ * clock - which we can't say is the case.
+ *
+ * This is still fragile. No matter how big advance do we have, we will still
+ * risk of losing a sample because things can in a rainy-day skenario be
+ * delayed a lot. Yet, more we reserve the time for polling, more we also lose
+ * the performance by spending cycles polling the register. So, selecting this
+ * value is a balancing dance between severity of wasting CPU time and severity
+ * of losing samples.
+ *
+ * In most cases losing the samples is not _that_ crucial because light levels
+ * tend to change slowly.
+ */
+#define BU27034_MEAS_WAIT_PREMATURE_MS	5
+#define BU27034_DATA_WAIT_TIME_US	1000
+#define BU27034_TOTAL_DATA_WAIT_TIME_US (BU27034_MEAS_WAIT_PREMATURE_MS * 1000)
+
+
+enum {
+	BU27034_CHAN_ALS,
+	BU27034_CHAN_DATA0,
+	BU27034_CHAN_DATA1,
+	BU27034_CHAN_DATA2,
+	BU27034_NUM_CHANS
+};
+
+static const unsigned long bu27034_scan_masks[] = {
+	BIT(BU27034_CHAN_ALS) | BIT(BU27034_CHAN_DATA0) |
+	BIT(BU27034_CHAN_DATA1) | BIT(BU27034_CHAN_DATA2), 0
+};
+
+/*
+ * Available scales with gain 1x - 4096x, timings 55, 100, 200, 400 mS
+ * Time impacts to gain: 1x, 2x, 4x, 8x.
+ *
+ * => Max total gain is HWGAIN * gain by integration time (8 * 4096) = 32768
+ *
+ * Using NANO precision for scale we must use scale 64x corresponding gain 1x
+ * to avoid precision loss. (32x would result scale 976 562.5(nanos).
+ */
+#define BU27034_SCALE_1X	64
+
+#define BU27034_GSEL_1X		0x00
+#define BU27034_GSEL_4X		0x08
+#define BU27034_GSEL_16X	0x0a
+#define BU27034_GSEL_32X	0x0b
+#define BU27034_GSEL_64X	0x0c
+#define BU27034_GSEL_256X	0x18
+#define BU27034_GSEL_512X	0x19
+#define BU27034_GSEL_1024X	0x1a
+#define BU27034_GSEL_2048X	0x1b
+#define BU27034_GSEL_4096X	0x1c
+
+/* Available gain settings */
+static const struct iio_gain_sel_pair bu27034_gains[] = {
+	GAIN_SCALE_GAIN(1, BU27034_GSEL_1X),
+	GAIN_SCALE_GAIN(4, BU27034_GSEL_4X),
+	GAIN_SCALE_GAIN(16, BU27034_GSEL_16X),
+	GAIN_SCALE_GAIN(32, BU27034_GSEL_32X),
+	GAIN_SCALE_GAIN(64, BU27034_GSEL_64X),
+	GAIN_SCALE_GAIN(256, BU27034_GSEL_256X),
+	GAIN_SCALE_GAIN(512, BU27034_GSEL_512X),
+	GAIN_SCALE_GAIN(1024, BU27034_GSEL_1024X),
+	GAIN_SCALE_GAIN(2048, BU27034_GSEL_2048X),
+	GAIN_SCALE_GAIN(4096, BU27034_GSEL_4096X),
+};
+
+/*
+ * The IC has 5 modes for sampling time. 5 mS mode is exceptional as it limits
+ * the data collection to data0-channel only and cuts the supported range to
+ * 10 bit. It is not supported by the driver.
+ *
+ * "normal" modes are 55, 100, 200 and 400 mS modes - which do have direct
+ * multiplying impact to the register values (similar to gain).
+ *
+ * This means that if meas-mode is changed for example from 400 => 200,
+ * the scale is doubled. Eg, time impact to total gain is x1, x2, x4, x8.
+ */
+#define BU27034_MEAS_MODE_100MS		0
+#define BU27034_MEAS_MODE_55MS		1
+#define BU27034_MEAS_MODE_200MS		2
+#define BU27034_MEAS_MODE_400MS		4
+
+static const struct iio_itime_sel_mul bu27034_itimes[] = {
+	GAIN_SCALE_ITIME_US(400000, BU27034_MEAS_MODE_400MS, 8),
+	GAIN_SCALE_ITIME_US(200000, BU27034_MEAS_MODE_200MS, 4),
+	GAIN_SCALE_ITIME_US(100000, BU27034_MEAS_MODE_100MS, 2),
+	GAIN_SCALE_ITIME_US(50000, BU27034_MEAS_MODE_55MS, 1),
+};
+
+#define BU27034_CHAN_DATA(_name, _ch2)					\
+{									\
+	.type = IIO_INTENSITY,						\
+	.channel = BU27034_CHAN_##_name,				\
+	.channel2 = (_ch2),						\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
+			      BIT(IIO_CHAN_INFO_SCALE),			\
+	.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),	\
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),		\
+	.info_mask_shared_by_all_available =				\
+					BIT(IIO_CHAN_INFO_INT_TIME),	\
+	.address = BU27034_REG_##_name##_LO,				\
+	.scan_index = BU27034_CHAN_##_name,				\
+	.scan_type = {							\
+		.sign = 'u',						\
+		.realbits = 16,						\
+		.storagebits = 16,					\
+		.endianness = IIO_LE,					\
+	},								\
+	.indexed = 1							\
+}
+
+static const struct iio_chan_spec bu27034_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.channel = BU27034_CHAN_ALS,
+		.scan_index = BU27034_CHAN_ALS,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 32,
+			.storagebits = 32,
+			.endianness = IIO_CPU,
+		},
+	},
+	/*
+	 * The BU27034 DATA0 and DATA1 channels are both on the visible light
+	 * area (mostly). The data0 sensitivity peaks at 500nm, DATA1 at 600nm.
+	 * These wave lengths are pretty much on the border of colours making
+	 * these a poor candidates for R/G/B standardization. Hence they're both
+	 * marked as clear channels
+	 */
+	BU27034_CHAN_DATA(DATA0, IIO_MOD_LIGHT_CLEAR),
+	BU27034_CHAN_DATA(DATA1, IIO_MOD_LIGHT_CLEAR),
+	BU27034_CHAN_DATA(DATA2, IIO_MOD_LIGHT_IR),
+	IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+struct bu27034_data {
+	struct regmap *regmap;
+	struct device *dev;
+	/*
+	 * Protect gain and time during scale adjustment and data reading.
+	 * Protect measurement enabling/disabling.
+	 */
+	struct mutex mutex;
+	struct iio_gts gts;
+	struct task_struct *task;
+	__le16 raw[3];
+	struct {
+		u32 lux;
+		__le16 channels[3];
+		s64 ts __aligned(8);
+	} scan;
+};
+
+struct bu27034_result {
+	u16 ch0;
+	u16 ch1;
+	u16 ch2;
+};
+
+static const struct regmap_range bu27034_volatile_ranges[] = {
+	{
+		.range_min = BU27034_REG_MODE_CONTROL4,
+		.range_max = BU27034_REG_MODE_CONTROL4,
+	}, {
+		.range_min = BU27034_REG_DATA0_LO,
+		.range_max = BU27034_REG_DATA2_HI,
+	},
+};
+
+static const struct regmap_access_table bu27034_volatile_regs = {
+	.yes_ranges = &bu27034_volatile_ranges[0],
+	.n_yes_ranges = ARRAY_SIZE(bu27034_volatile_ranges),
+};
+
+static const struct regmap_range bu27034_read_only_ranges[] = {
+	{
+		.range_min = BU27034_REG_DATA0_LO,
+		.range_max = BU27034_REG_DATA2_HI,
+	}, {
+		.range_min = BU27034_REG_MANUFACTURER_ID,
+		.range_max = BU27034_REG_MANUFACTURER_ID,
+	}
+};
+
+static const struct regmap_access_table bu27034_ro_regs = {
+	.no_ranges = &bu27034_read_only_ranges[0],
+	.n_no_ranges = ARRAY_SIZE(bu27034_read_only_ranges),
+};
+
+static const struct regmap_config bu27034_regmap = {
+	.reg_bits	= 8,
+	.val_bits	= 8,
+
+	.max_register	= BU27034_REG_MAX,
+	.cache_type	= REGCACHE_RBTREE,
+	.volatile_table = &bu27034_volatile_regs,
+	.wr_table	= &bu27034_ro_regs,
+};
+
+static int bu27034_validate_int_time(struct bu27034_data *data, int time_us)
+{
+	/*
+	 * The BU27034 has 55 mS integration time which is in the vendor tests
+	 * handled as 50 mS in all of the internal computations. We keep same
+	 * approach here.
+	 */
+	if (time_us == 55000)
+		return 50000;
+
+	if (iio_gts_valid_time(&data->gts, time_us))
+		return time_us;
+
+	return -EINVAL;
+}
+
+struct bu27034_gain_check {
+	int old_gain;
+	int new_gain;
+	int chan;
+};
+
+static int bu27034_get_gain_sel(struct bu27034_data *data, int chan)
+{
+	int ret, val;
+
+	switch (chan) {
+	case BU27034_CHAN_DATA0:
+	case BU27034_CHAN_DATA1:
+	{
+		int reg[] = {
+			[BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2,
+			[BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3,
+		};
+		ret = regmap_read(data->regmap, reg[chan], &val);
+		if (ret)
+			return ret;
+
+		val &= BU27034_MASK_D01_GAIN;
+		return val >> BU27034_SHIFT_D01_GAIN;
+	}
+	case BU27034_CHAN_DATA2:
+		ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL2, &val);
+		if (ret)
+			return ret;
+
+		return (val & BU27034_MASK_D2_GAIN_HI) >> BU27034_SHIFT_D2_GAIN
+		       | (val & BU27034_MASK_D2_GAIN_LO);
+	}
+
+	return -EINVAL;
+}
+
+static int bu27034_get_gain(struct bu27034_data *data, int chan, int *gain)
+{
+	int ret, sel;
+
+	ret = bu27034_get_gain_sel(data, chan);
+	if (ret < 0)
+		return ret;
+
+	sel = ret;
+
+	ret = iio_gts_find_gain_by_sel(&data->gts, sel);
+	if (ret < 0) {
+		dev_err(data->dev, "chan %u: unknown gain value 0x%x\n", chan,
+			sel);
+
+		return ret;
+	}
+
+	*gain = ret;
+
+	return 0;
+}
+
+static int bu27034_get_int_time(struct bu27034_data *data)
+{
+	int ret, sel;
+
+	ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &sel);
+	if (ret)
+		return ret;
+
+	return iio_gts_find_int_time_by_sel(&data->gts,
+					    sel & BU27034_MASK_MEAS_MODE);
+}
+
+static int _bu27034_get_scale(struct bu27034_data *data, int channel, int *val,
+			      int *val2)
+{
+	int gain, ret;
+
+	ret = bu27034_get_gain(data, channel, &gain);
+	if (ret)
+		return ret;
+
+	ret = bu27034_get_int_time(data);
+	if (ret < 0)
+		return ret;
+
+	return iio_gts_get_scale(&data->gts, gain, ret, val, val2);
+}
+
+static int bu27034_get_scale(struct bu27034_data *data, int channel, int *val,
+			      int *val2)
+{
+	int ret;
+
+	if (channel == BU27034_CHAN_ALS) {
+		*val = 0;
+		*val2 = 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+
+	mutex_lock(&data->mutex);
+	ret = _bu27034_get_scale(data, channel, val, val2);
+	mutex_unlock(&data->mutex);
+	if (!ret)
+		return IIO_VAL_INT_PLUS_NANO;
+
+	return ret;
+}
+
+/* Caller should hold the lock to protect lux reading */
+static int bu27034_write_gain_sel(struct bu27034_data *data, int chan, int sel)
+{
+	static const int reg[] = {
+		[BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2,
+		[BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3,
+		[BU27034_CHAN_DATA2] = BU27034_REG_MODE_CONTROL2
+	};
+	int mask;
+
+	if (chan < BU27034_CHAN_DATA0 || chan > BU27034_CHAN_DATA2)
+		return -EINVAL;
+
+	if (chan == BU27034_CHAN_DATA0 || chan == BU27034_CHAN_DATA1) {
+		sel <<= BU27034_SHIFT_D01_GAIN;
+		mask = BU27034_MASK_D01_GAIN;
+	} else {
+		/*
+		 * We don't allow setting high bits for DATA2 gain because
+		 * that impacts to DATA0 as well.
+		 */
+		mask =  BU27034_MASK_D2_GAIN_LO;
+	}
+
+	return regmap_update_bits(data->regmap, reg[chan], mask, sel);
+}
+
+static int bu27034_set_gain(struct bu27034_data *data, int chan, int gain)
+{
+	int ret;
+
+	ret = iio_gts_find_sel_by_gain(&data->gts, gain);
+	if (ret < 0)
+		return ret;
+
+	return bu27034_write_gain_sel(data, chan, ret);
+}
+
+/* Caller should hold the lock to protect data->int_time */
+static int bu27034_set_int_time(struct bu27034_data *data, int time)
+{
+	int ret;
+
+	ret = iio_gts_find_sel_by_int_time(&data->gts, time);
+	if (ret < 0)
+		return ret;
+
+	return regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1,
+				 BU27034_MASK_MEAS_MODE, ret);
+
+	return time;
+}
+
+/*
+ * We try to change the time in such way that the scale is maintained for
+ * given channels by adjusting gain so that it compensates the time change.
+ */
+static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us)
+{
+	int ret, int_time_old, int_time_new, i;
+	struct bu27034_gain_check gains[3] = {
+		{ .chan = BU27034_CHAN_DATA0, },
+		{ .chan = BU27034_CHAN_DATA1, },
+		{ .chan = BU27034_CHAN_DATA2 }
+	};
+	int numg = ARRAY_SIZE(gains);
+
+	mutex_lock(&data->mutex);
+	ret = bu27034_get_int_time(data);
+	if (ret < 0)
+		goto unlock_out;
+
+	int_time_old = ret;
+
+	ret = bu27034_validate_int_time(data, time_us);
+	if (ret < 0) {
+		dev_err(data->dev, "Unsupported integration time %u\n",
+			time_us);
+
+		goto unlock_out;
+	}
+
+	int_time_new = ret;
+
+	if (int_time_new == int_time_old) {
+		ret = 0;
+		goto unlock_out;
+	}
+
+	for (i = 0; i < numg; i++) {
+		ret = bu27034_get_gain(data, gains[i].chan,
+				       &gains[i].old_gain);
+		if (ret)
+			goto unlock_out;
+
+		gains[i].new_gain = gains[i].old_gain * int_time_old /
+				    int_time_new;
+
+		if (!iio_gts_valid_gain(&data->gts, gains[i].new_gain)) {
+			int scale1, scale2;
+			bool ok;
+
+			_bu27034_get_scale(data, gains[i].chan, &scale1, &scale2);
+			dev_dbg(data->dev,
+				"chan %u, can't support time %u with scale %u %u\n",
+				gains[i].chan, time_us, scale1, scale2);
+
+			/*
+			 * As Jonathan put it, if caller requests for
+			 * integration time change and we can't support the
+			 * scale - then the caller should be prepared to
+			 * 'pick up the pieces and deal with the fact that the
+			 * scale changed'.
+			 */
+			ret = iio_find_closest_gain_low(&data->gts,
+							gains[i].new_gain, &ok);
+
+			if (!ok) {
+				dev_dbg(data->dev,
+					"optimal gain out of range for chan %u\n",
+					gains[i].chan);
+			}
+			if (ret < 0) {
+				dev_warn(data->dev,
+					 "Total gain increase. Risk of saturation");
+				ret = iio_gts_get_min_gain(&data->gts);
+				if (ret < 0)
+					goto unlock_out;
+			}
+			dev_warn(data->dev, "chan %u scale changed\n",
+				 gains[i].chan);
+			gains[i].new_gain = ret;
+			dev_dbg(data->dev, "chan %u new gain %u\n",
+				gains[i].chan, gains[i].new_gain);
+		}
+	}
+
+	/*
+	 * The new integration time can be supported while keeping the scale of
+	 * channels intact by tuning the gains.
+	 */
+	for (i = 0; i < numg; i++) {
+		ret = bu27034_set_gain(data, gains[i].chan, gains[i].new_gain);
+		if (ret)
+			goto unlock_out;
+	}
+
+	ret = bu27034_set_int_time(data, int_time_new);
+
+unlock_out:
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static int bu27034_set_scale(struct bu27034_data *data, int chan,
+			    int val, int val2)
+{
+	int ret, time_sel, gain_sel, i;
+	bool found = false;
+
+	if (chan == BU27034_CHAN_ALS) {
+		if (val == 0 && val2 == 1000)
+			return 0;
+		else
+			return -EINVAL;
+	}
+
+	mutex_lock(&data->mutex);
+	ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &time_sel);
+	if (ret)
+		goto unlock_out;
+
+	ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, time_sel,
+						val, val2 * 1000, &gain_sel);
+	if (ret) {
+		/*
+		 * Could'n support scale with given time. Need to change time.
+		 * We still want to maintain the scale for all channels
+		 */
+		int new_time_sel;
+		struct bu27034_gain_check gains[2];
+
+		/*
+		 * Populate information for the two other channels which should
+		 * maintain the scale.
+		 */
+		if (chan == BU27034_CHAN_DATA0) {
+			gains[0].chan = BU27034_CHAN_DATA1;
+			gains[1].chan = BU27034_CHAN_DATA2;
+		} else if (chan == BU27034_CHAN_DATA1) {
+			gains[0].chan = BU27034_CHAN_DATA0;
+			gains[1].chan = BU27034_CHAN_DATA2;
+		} else {
+			gains[0].chan = BU27034_CHAN_DATA0;
+			gains[1].chan = BU27034_CHAN_DATA1;
+		}
+
+		for (i = 0; i < 2; i++) {
+			ret = bu27034_get_gain(data, gains[i].chan,
+					       &gains[i].old_gain);
+			if (ret)
+				goto unlock_out;
+		}
+
+		/*
+		 * Iterate through all the times to see if we find one which
+		 * can support requested scale for requested channel, while
+		 * maintaining the scale for other channels
+		 */
+		for (i = 0; i < data->gts.num_itime; i++) {
+			new_time_sel = data->gts.itime_table[i].sel;
+
+			if (new_time_sel == time_sel)
+				continue;
+
+			/* Can we provide requested scale with this time? */
+			ret = iio_gts_find_gain_sel_for_scale_using_time(
+				&data->gts, new_time_sel, val, val2 * 1000,
+				&gain_sel);
+			if (ret)
+				continue;
+
+			/* Can the two other channels maintain scales? */
+			ret = iio_gts_find_new_gain_sel_by_old_gain_time(
+				&data->gts, gains[0].old_gain, time_sel,
+				new_time_sel, &gains[0].new_gain);
+			if (ret)
+				continue;
+
+			ret = iio_gts_find_new_gain_sel_by_old_gain_time(
+				&data->gts, gains[1].old_gain, time_sel,
+				new_time_sel, &gains[1].new_gain);
+			if (!ret) {
+				/* Yes - we found suitable time */
+				found = true;
+				break;
+			}
+		}
+		if (!found) {
+			dev_err(data->dev,
+				"Can't set scale maintaining other channels\n");
+			ret = -EINVAL;
+
+			goto unlock_out;
+		}
+
+		for (i = 0; i < 2; i++) {
+			ret = bu27034_set_gain(data, gains[i].chan,
+						gains[i].new_gain);
+			if (ret)
+				goto unlock_out;
+		}
+
+		ret = regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1,
+				  BU27034_MASK_MEAS_MODE, new_time_sel);
+		if (ret)
+			goto unlock_out;
+	}
+
+	ret = bu27034_write_gain_sel(data, chan, gain_sel);
+unlock_out:
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+/*
+ * for (D1/D0 < 0.87):
+ * lx = 0.004521097 * D1 - 0.002663996 * D0 +
+ *	0.00012213 * D1 * D1 / D0
+ *
+ * =>	115.7400832 * ch1 / gain1 / mt -
+ *	68.1982976 * ch0 / gain0 / mt +
+ *	0.00012213 * 25600 * (ch1 / gain1 / mt) * 25600 *
+ *	(ch1 /gain1 / mt) / (25600 * ch0 / gain0 / mt)
+ *
+ * A =	0.00012213 * 25600 * (ch1 /gain1 / mt) * 25600 *
+ *	(ch1 /gain1 / mt) / (25600 * ch0 / gain0 / mt)
+ * =>	0.00012213 * 25600 * (ch1 /gain1 / mt) *
+ *	(ch1 /gain1 / mt) / (ch0 / gain0 / mt)
+ * =>	0.00012213 * 25600 * (ch1 / gain1) * (ch1 /gain1 / mt) /
+ *	(ch0 / gain0)
+ * =>	0.00012213 * 25600 * (ch1 / gain1) * (ch1 /gain1 / mt) *
+ *	gain0 / ch0
+ * =>	3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / mt /ch0
+ *
+ * lx = (115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0) /
+ *	mt + A
+ * =>	(115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0) /
+ *	mt + 3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / mt /
+ *	ch0
+ *
+ * =>	(115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0 +
+ *	  3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / ch0) /
+ *	  mt
+ *
+ * For (0.87 <= D1/D0 < 1.00)
+ * lx = (0.001331* D0 + 0.0000354 * D1) * ((D1/D0 – 0.87) * (0.385) + 1)
+ * =>	(0.001331 * 256 * 100 * ch0 / gain0 / mt + 0.0000354 * 256 *
+ *	100 * ch1 / gain1 / mt) * ((D1/D0 -  0.87) * (0.385) + 1)
+ * =>	(34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
+ *	((D1/D0 -  0.87) * (0.385) + 1)
+ * =>	(34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
+ *	(0.385 * D1/D0 - 0.66505)
+ * =>	(34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
+ *	(0.385 * 256 * 100 * ch1 / gain1 / mt / (256 * 100 * ch0 / gain0 / mt) - 0.66505)
+ * =>	(34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
+ *	(9856 * ch1 / gain1 / mt / (25600 * ch0 / gain0 / mt) + 0.66505)
+ * =>	13.118336 * ch1 / (gain1 * mt)
+ *	+ 22.66064768 * ch0 / (gain0 * mt)
+ *	+ 8931.90144 * ch1 * ch1 * gain0 /
+ *	  (25600 * ch0 * gain1 * gain1 * mt)
+ *	+ 0.602694912 * ch1 / (gain1 * mt)
+ *
+ * =>	[0.3489024 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1)
+ *	 + 22.66064768 * ch0 / gain0
+ *	 + 13.721030912 * ch1 / gain1
+ *	] / mt
+ *
+ * For (D1/D0 >= 1.00)
+ *
+ * lx	= (0.001331* D0 + 0.0000354 * D1) * ((D1/D0 – 2.0) * (-0.05) + 1)
+ *	=> (0.001331* D0 + 0.0000354 * D1) * (-0.05D1/D0 + 1.1)
+ *	=> (0.001331 * 256 * 100 * ch0 / gain0 / mt + 0.0000354 * 256 *
+ *	   100 * ch1 / gain1 / mt) * (-0.05D1/D0 + 1.1)
+ *	=> (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
+ *	   (-0.05 * 256 * 100 * ch1 / gain1 / mt / (256 * 100 * ch0 / gain0 / mt) + 1.1)
+ *	=> (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
+ *	   (-1280 * ch1 / (gain1 * mt * 25600 * ch0 / gain0 / mt) + 1.1)
+ *	=> (34.0736 * ch0 * -1280 * ch1 * gain0 * mt /( gain0 * mt * gain1 * mt * 25600 * ch0)
+ *	    + 34.0736 * 1.1 * ch0 / (gain0 * mt)
+ *	    + 0.90624 * ch1 * -1280 * ch1 *gain0 * mt / (gain1 * mt *gain1 * mt * 25600 * ch0)
+ *	    + 1.1 * 0.90624 * ch1 / (gain1 * mt)
+ *	=> -43614.208 * ch1 / (gain1 * mt * 25600)
+ *	    + 37.48096  ch0 / (gain0 * mt)
+ *	    - 1159.9872 * ch1 * ch1 * gain0 / (gain1 * gain1 * mt * 25600 * ch0)
+ *	    + 0.996864 ch1 / (gain1 * mt)
+ *	=> [
+ *		- 0.045312 * ch1 * ch1 * gain0 / (gain1 * gain1 * ch0)
+ *		- 0.706816 * ch1 / gain1
+ *		+ 37.48096  ch0 /gain0
+ *	   ] * mt
+ *
+ *
+ * So, the first case (D1/D0 < 0.87) can be computed to a form:
+ *
+ * lx = (3.126528 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) +
+ *	 115.7400832 * ch1 / gain1 +
+ *	-68.1982976 * ch0 / gain0
+ *	 / mt
+ *
+ * Second case (0.87 <= D1/D0 < 1.00) goes to form:
+ *
+ *	=> [0.3489024 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) +
+ *	    13.721030912 * ch1 / gain1 +
+ *	    22.66064768 * ch0 / gain0
+ *	   ] / mt
+ *
+ * Third case (D1/D0 >= 1.00) goes to form:
+ *	=> [-0.045312 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) +
+ *	    -0.706816 * ch1 / gain1 +
+ *	    37.48096  ch0 /(gain0
+ *	   ] / mt
+ *
+ * This can be unified to format:
+ * lx = [
+ *	 A * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) +
+ *	 B * ch1 / gain1 +
+ *	 C * ch0 / gain0
+ *	] / mt
+ *
+ * For case 1:
+ * A = 3.126528,
+ * B = 115.7400832
+ * C = -68.1982976
+ *
+ * For case 2:
+ * A = 0.3489024
+ * B = 13.721030912
+ * C = 22.66064768
+ *
+ * For case 3:
+ * A = -0.045312
+ * B = -0.706816
+ * C = 37.48096
+ */
+
+struct bu27034_lx_coeff {
+	unsigned int A;
+	unsigned int B;
+	unsigned int C;
+	/* Indicate which of the coefficients above are negative */
+	bool is_neg[3];
+};
+
+static u64 bu27034_fixp_calc_t1(unsigned int coeff, unsigned int ch0,
+				unsigned int ch1, unsigned int gain0,
+				unsigned int gain1)
+{
+	unsigned int helper, tmp;
+	u64 helper64;
+
+	/*
+	 * Here we could overflow even the 64bit value. Hence we
+	 * multiply with gain0 only after the divisions - even though
+	 * it may result loss of accuracy
+	 */
+	helper64 = (u64)coeff * (u64)ch1 * (u64)ch1;
+	helper = coeff * ch1 * ch1;
+	tmp = helper * gain0;
+
+	if (helper == helper64 && (tmp / gain0 == helper))
+		return tmp / (gain1 * gain1) / ch0;
+
+	helper = gain1 * gain1;
+	if (helper > ch0) {
+		do_div(helper64, helper);
+		/*
+		 * Max gain for a channel is 4096. The max u64
+		 * (0xffffffffffffffffULL) divided by 4096 is 0xFFFFFFFFFFFFF
+		 * (floored). Thus, the 0xFFFFFFFFFFFFF is the largest value
+		 * we can safely multiply with the gain, no matter what gain
+		 * is set.
+		 *
+		 * So, multiplication with max gain may overflow
+		 * if helper64 is greater than 0xFFFFFFFFFFFFF.
+		 *
+		 * If this is the case we divide first.
+		 */
+		if (helper64 < 0xFFFFFFFFFFFFFLLU) {
+			helper64 *= gain0;
+			do_div(helper64, ch0);
+		} else {
+			do_div(helper64, ch0);
+			helper64 *= gain0;
+		}
+
+		return helper64;
+	}
+
+	do_div(helper64, ch0);
+	/* Same overflow check here */
+	if (helper64 < 0xFFFFFFFFFFFFFLLU) {
+		helper64 *= gain0;
+		do_div(helper64, helper);
+	} else {
+		do_div(helper64, helper);
+		helper64 *= gain0;
+	}
+
+	return helper64;
+}
+
+static u64 bu27034_fixp_calc_t23(unsigned int coeff, unsigned int ch,
+				 unsigned int gain)
+{
+	unsigned int helper;
+	u64 helper64;
+
+	helper64 = (u64)coeff * (u64)ch;
+	helper = coeff * ch;
+
+	if (helper == helper64)
+		return helper / gain;
+
+	do_div(helper64, gain);
+
+	return helper64;
+}
+
+static int bu27034_fixp_calc_lx(unsigned int ch0, unsigned int ch1,
+				unsigned int gain0, unsigned int gain1,
+				unsigned int meastime, int coeff_idx)
+{
+	static const struct bu27034_lx_coeff coeff[] = {
+		{
+			.A = 31265280,		/* 3.126528 */
+			.B = 1157400832,	/*115.7400832 */
+			.C = 681982976,		/* -68.1982976 */
+			.is_neg = {false, false, true},
+		}, {
+			.A = 3489024,		/* 0.3489024 */
+			.B = 137210309,		/* 13.721030912 */
+			.C = 226606476,		/* 22.66064768 */
+			/* All terms positive */
+		}, {
+			.A = 453120,		/* -0.045312 */
+			.B = 7068160,		/* -0.706816 */
+			.C = 374809600,		/* 37.48096 */
+			.is_neg = {true, true, false},
+		}
+	};
+	const struct bu27034_lx_coeff *c = &coeff[coeff_idx];
+	u64 res = 0, terms[3];
+	int i;
+
+	if (coeff_idx >= ARRAY_SIZE(coeff))
+		return -EINVAL;
+
+	terms[0] = bu27034_fixp_calc_t1(c->A, ch0, ch1, gain0, gain1);
+	terms[1] = bu27034_fixp_calc_t23(c->B, ch1, gain1);
+	terms[2] = bu27034_fixp_calc_t23(c->C, ch0, gain0);
+
+	/* First, add positive terms */
+	for (i = 0; i < 3; i++)
+		if (!c->is_neg[i])
+			res += terms[i];
+
+	/* No positive term => zero lux */
+	if (!res)
+		return 0;
+
+	/* Then, subtract negative terms (if any) */
+	for (i = 0; i < 3; i++)
+		if (c->is_neg[i]) {
+			/*
+			 * If the negative term is greater than positive - then
+			 * the darknes has taken over and we are all doomed! Eh,
+			 * I mean, then we can just return 0 lx and go out
+			 */
+			if (terms[i] >= res)
+				return 0;
+
+			res -= terms[i];
+		}
+
+	meastime *= 10;
+	do_div(res, meastime);
+
+	return (int) res;
+}
+
+static bool bu27034_has_valid_sample(struct bu27034_data *data)
+{
+	int ret, val;
+
+	ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL4, &val);
+	if (ret)
+		dev_err(data->dev, "Read failed %d\n", ret);
+
+	return (val & BU27034_MASK_VALID);
+}
+
+/*
+ * Reading the register where VALID bit is clears this bit. (So does changing
+ * any gain / integration time configuration registers) The bit gets
+ * set when we have acquired new data. We use this bit to indicate data
+ * validity.
+ */
+static void bu27034_invalidate_read_data(struct bu27034_data *data)
+{
+	bu27034_has_valid_sample(data);
+}
+
+static int bu27034_read_result(struct bu27034_data *data, int chan, int *res)
+{
+	int reg[] = {
+		[BU27034_CHAN_DATA0] = BU27034_REG_DATA0_LO,
+		[BU27034_CHAN_DATA1] = BU27034_REG_DATA1_LO,
+		[BU27034_CHAN_DATA2] = BU27034_REG_DATA2_LO,
+	};
+	int valid, ret;
+	__le16 val;
+
+	ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4,
+				       valid, (valid & BU27034_MASK_VALID),
+				       BU27034_DATA_WAIT_TIME_US, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_bulk_read(data->regmap, reg[chan], &val, sizeof(val));
+	if (ret)
+		return ret;
+
+	*res = le16_to_cpu(val);
+
+	return 0;
+}
+
+static int bu27034_get_result_unlocked(struct bu27034_data *data, __le16 *res,
+				       int size)
+{
+	int ret = 0;
+
+retry:
+	/* Get new value from sensor if data is ready */
+	if (bu27034_has_valid_sample(data)) {
+		ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO,
+				       res, size);
+		if (ret)
+			return ret;
+
+		bu27034_invalidate_read_data(data);
+	} else {
+		/* No new data in sensor. Wait and retry */
+		msleep(25);
+
+		goto retry;
+	}
+
+	return ret;
+}
+
+static int bu27034_meas_set(struct bu27034_data *data, bool en)
+{
+	if (en)
+		return regmap_set_bits(data->regmap, BU27034_REG_MODE_CONTROL4,
+				       BU27034_MASK_MEAS_EN);
+
+	return regmap_clear_bits(data->regmap, BU27034_REG_MODE_CONTROL4,
+				 BU27034_MASK_MEAS_EN);
+}
+
+static int bu27034_get_single_result(struct bu27034_data *data, int chan,
+				     int *val)
+{
+	int ret;
+
+
+	ret = bu27034_meas_set(data, true);
+	if (ret)
+		return ret;
+
+	ret = bu27034_get_int_time(data);
+	if (ret < 0)
+		return ret;
+
+	msleep(ret / 1000);
+
+	return bu27034_read_result(data, chan, val);
+}
+
+/*
+ * The formula given by vendor for computing luxes out of data0 and data1
+ * (in open air) is as follows:
+ *
+ * Let's mark:
+ * D0 = data0/ch0_gain/meas_time_ms * 25600
+ * D1 = data1/ch1_gain/meas_time_ms * 25600
+ *
+ * Then:
+ * if (D1/D0 < 0.87)
+ *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 0.87) * 3.45 + 1)
+ * else if (D1/D0 < 1)
+ *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 0.87) * 0.385 + 1)
+ * else
+ *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 2) * -0.05 + 1)
+ *
+ * We use it here. Users who have for example some colored lens
+ * need to modify the calculation but I hope this gives a starting point for
+ * those working with such devices.
+ */
+
+static int bu27034_calc_lux(struct bu27034_data *data, __le16 *res, int *val)
+{
+	unsigned int gain0, gain1, meastime;
+	unsigned int d1_d0_ratio_scaled;
+	u16  ch0, ch1;
+	u64 helper64;
+	int ret;
+
+	/*
+	 * We return 0 luxes if calculation fails. This should be reasonably
+	 * easy to spot from the buffers especially if raw-data channels show
+	 * valid values
+	 */
+	*val = 0;
+
+	/*
+	 * Avoid div by zeroi. Not using max() as the data may not be in
+	 * native endianes
+	 */
+	if (!res[0])
+		ch0 = 1;
+	else
+		ch0 = le16_to_cpu(res[0]);
+
+	if (!res[1])
+		ch1 = 1;
+	else
+		ch1 = le16_to_cpu(res[1]);
+
+
+	ret = bu27034_get_gain(data, BU27034_CHAN_DATA0, &gain0);
+	if (ret)
+		return ret;
+
+	ret = bu27034_get_gain(data, BU27034_CHAN_DATA1, &gain1);
+	if (ret)
+		return ret;
+
+	ret = bu27034_get_int_time(data);
+	if (ret < 0)
+		return ret;
+
+	meastime = ret;
+
+	d1_d0_ratio_scaled = (unsigned int)ch1 * (unsigned int)gain0 * 100;
+	helper64 = (u64)ch1 * (u64)gain0 * 100LLU;
+
+	if (helper64 != d1_d0_ratio_scaled) {
+		unsigned int div = (unsigned int)ch0 * gain1;
+
+		do_div(helper64, div);
+		d1_d0_ratio_scaled = helper64;
+	} else {
+		d1_d0_ratio_scaled /= ch0 * gain1;
+	}
+
+	if (d1_d0_ratio_scaled < 87)
+		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 0);
+	else if (d1_d0_ratio_scaled < 100)
+		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 1);
+	else
+		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 2);
+
+	return 0;
+
+}
+
+static int bu27034_get_lux(struct bu27034_data *data, int *val)
+{
+	__le16 res[3];
+	int ret;
+
+	ret = bu27034_get_result_unlocked(data, &res[0], sizeof(res));
+	if (ret)
+		return ret;
+
+	return bu27034_calc_lux(data, res, val);
+}
+
+static int bu27034_read_raw(struct iio_dev *idev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct bu27034_data *data = iio_priv(idev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = bu27034_get_int_time(data);
+		if (*val < 0)
+			return *val;
+
+		/*
+		 * We use 50000 uS internally for all calculations and only
+		 * convert it to 55000 before returning it to the user.
+		 *
+		 * This is because the data-sheet says the time is 55 mS - but
+		 * vendor provided computations used 50 mS.
+		 */
+		if (*val == 50000)
+			*val = 55000;
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		return bu27034_get_scale(data, chan->channel, val, val2);
+
+	case IIO_CHAN_INFO_RAW:
+	{
+		if (chan->type != IIO_INTENSITY)
+			return -EINVAL;
+
+		if (chan->channel < BU27034_CHAN_DATA0 ||
+		    chan->channel > BU27034_CHAN_DATA2)
+			return -EINVAL;
+
+		/* Don't mess with measurement enabling while buffering */
+		ret = iio_device_claim_direct_mode(idev);
+		if (ret)
+			return ret;
+
+		mutex_lock(&data->mutex);
+		/*
+		 * Reading one channel at a time is ineffiecient but we don't
+		 * care here. Buffered version should be used if performance is
+		 * an issue.
+		 */
+		ret = bu27034_get_single_result(data, chan->channel, val);
+		if (ret)
+			goto release_err_out;
+
+		mutex_unlock(&data->mutex);
+		iio_device_release_direct_mode(idev);
+
+		return IIO_VAL_INT;
+	}
+
+	case IIO_CHAN_INFO_PROCESSED:
+		if (chan->type != IIO_LIGHT)
+			return -EINVAL;
+
+		/* Don't mess with measurement enabling while buffering */
+		ret = iio_device_claim_direct_mode(idev);
+		if (ret)
+			return ret;
+
+		mutex_lock(&data->mutex);
+		ret = bu27034_meas_set(data, true);
+		if (ret)
+			goto release_err_out;
+
+		ret = bu27034_get_lux(data, val);
+		if (ret)
+			goto release_err_out;
+
+		ret = bu27034_meas_set(data, false);
+		if (ret)
+			dev_err(data->dev, "failed to disable measurement\n");
+
+		mutex_unlock(&data->mutex);
+		iio_device_release_direct_mode(idev);
+
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+
+	}
+
+release_err_out:
+	mutex_unlock(&data->mutex);
+	iio_device_release_direct_mode(idev);
+
+	return ret;
+}
+
+static int bu27034_write_raw(struct iio_dev *idev,
+			     struct iio_chan_spec const *chan,
+			     int val, int val2, long mask)
+{
+	struct bu27034_data *data = iio_priv(idev);
+	int ret;
+
+	ret = iio_device_claim_direct_mode(idev);
+	if (ret)
+		return ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		ret = bu27034_set_scale(data, chan->channel, val, val2);
+		break;
+	case IIO_CHAN_INFO_INT_TIME:
+		ret = bu27034_try_set_int_time(data, val);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	iio_device_release_direct_mode(idev);
+
+	return ret;
+}
+
+static int bu27034_read_avail(struct iio_dev *idev,
+			      struct iio_chan_spec const *chan, const int **vals,
+			      int *type, int *length, long mask)
+{
+	struct bu27034_data *data = iio_priv(idev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_INT_TIME:
+		return iio_gts_avail_times(&data->gts, vals, type, length);
+	case IIO_CHAN_INFO_SCALE:
+		return iio_gts_all_avail_scales(&data->gts, vals, type, length);
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+static const struct iio_info bu27034_info = {
+	.read_raw = &bu27034_read_raw,
+	.write_raw = &bu27034_write_raw,
+	.read_avail = &bu27034_read_avail,
+};
+
+static int bu27034_chip_init(struct bu27034_data *data)
+{
+	int ret, sel;
+
+	/* Reset */
+	ret = regmap_update_bits(data->regmap, BU27034_REG_SYSTEM_CONTROL,
+			   BU27034_MASK_SW_RESET, BU27034_MASK_SW_RESET);
+	if (ret)
+		return dev_err_probe(data->dev, ret, "Sensor reset failed\n");
+
+	/*
+	 * Delay to allow IC to initialize. We don't care if we delay
+	 * for more than 1 ms so msleep() is Ok. We just don't want to
+	 * block
+	 */
+	msleep(1);
+	/*
+	 * Read integration time here to ensure it is in regmap cache. We do
+	 * this to speed-up the int-time acquisition in the start of the buffer
+	 * handling thread where longer delays could make it more likely we end
+	 * up skipping a sample, and where the longer delays make timestamps
+	 * less accurate.
+	 */
+	ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &sel);
+	if (ret)
+		dev_err(data->dev, "reading integration time failed\n");
+
+	return 0;
+}
+
+static int bu27034_wait_for_data(struct bu27034_data *data)
+{
+	int ret, val;
+
+	ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4,
+				       val, (val & BU27034_MASK_VALID),
+				       BU27034_DATA_WAIT_TIME_US,
+				       BU27034_TOTAL_DATA_WAIT_TIME_US);
+	if (ret) {
+		dev_err(data->dev, "data polling %s\n",
+			!(val & BU27034_MASK_VALID) ? "timeout" : "fail");
+		return ret;
+	}
+	ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO,
+			       &data->scan.channels[0],
+			       sizeof(data->scan.channels));
+	bu27034_invalidate_read_data(data);
+
+	return ret;
+}
+
+static int bu27034_buffer_thread(void *arg)
+{
+	struct iio_dev *idev = arg;
+	struct bu27034_data *data;
+	int wait_ms;
+
+	data = iio_priv(idev);
+
+	wait_ms = bu27034_get_int_time(data);
+	wait_ms /= 1000;
+
+	wait_ms -=  BU27034_MEAS_WAIT_PREMATURE_MS;
+
+	while (!kthread_should_stop()) {
+		int ret;
+		int64_t tstamp;
+
+		msleep(wait_ms);
+		ret = bu27034_wait_for_data(data);
+		if (ret)
+			continue;
+
+		tstamp = iio_get_time_ns(idev);
+
+		if (*idev->active_scan_mask & BIT(BU27034_CHAN_ALS)) {
+			ret = bu27034_calc_lux(data, &data->scan.channels[0],
+					       &data->scan.lux);
+			if (ret)
+				dev_err(data->dev, "failed to calculate lux\n");
+		}
+		iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp);
+	}
+
+	return 0;
+}
+
+static int bu27034_buffer_enable(struct iio_dev *idev)
+{
+	struct bu27034_data *data = iio_priv(idev);
+	struct task_struct *task;
+	int ret;
+
+	mutex_lock(&data->mutex);
+	ret = bu27034_meas_set(data, true);
+	if (ret)
+		goto unlock_out;
+
+	task = kthread_run(bu27034_buffer_thread, idev,
+				 "bu27034-buffering-%u",
+				 iio_device_id(idev));
+	if (IS_ERR(task)) {
+		ret = PTR_ERR(task);
+		goto unlock_out;
+	}
+
+	data->task = task;
+
+unlock_out:
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static int bu27034_buffer_disable(struct iio_dev *idev)
+{
+	struct bu27034_data *data = iio_priv(idev);
+	int ret;
+
+	mutex_lock(&data->mutex);
+	if (data->task) {
+		kthread_stop(data->task);
+		data->task = NULL;
+	}
+
+	ret = bu27034_meas_set(data, false);
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static const struct iio_buffer_setup_ops bu27034_buffer_ops = {
+	.postenable = &bu27034_buffer_enable,
+	.predisable = &bu27034_buffer_disable,
+};
+
+static int bu27034_probe(struct i2c_client *i2c)
+{
+	struct device *dev = &i2c->dev;
+	struct fwnode_handle *fwnode;
+	struct bu27034_data *data;
+	struct regmap *regmap;
+	struct iio_dev *idev;
+	unsigned int part_id;
+	int ret;
+
+	regmap = devm_regmap_init_i2c(i2c, &bu27034_regmap);
+	if (IS_ERR(regmap))
+		return dev_err_probe(dev, PTR_ERR(regmap),
+				     "Failed to initialize Regmap\n");
+
+	fwnode = dev_fwnode(dev);
+	if (!fwnode)
+		return -ENODEV;
+
+	idev = devm_iio_device_alloc(dev, sizeof(*data));
+	if (!idev)
+		return -ENOMEM;
+
+	ret = devm_regulator_get_enable(dev, "vdd");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(dev, ret, "Failed to get regulator\n");
+
+	data = iio_priv(idev);
+
+	ret = regmap_read(regmap, BU27034_REG_SYSTEM_CONTROL, &part_id);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to access sensor\n");
+
+	part_id &= BU27034_MASK_PART_ID;
+
+	if (part_id != BU27034_ID)
+		dev_warn(dev, "unsupported device 0x%x\n", part_id);
+
+	ret = iio_init_iio_gts(BU27034_SCALE_1X, 0, bu27034_gains,
+			       ARRAY_SIZE(bu27034_gains), bu27034_itimes,
+			       ARRAY_SIZE(bu27034_itimes), &data->gts);
+	if (ret)
+		return ret;
+
+	ret = devm_iio_gts_build_avail_tables(dev, &data->gts);
+	if (ret)
+		return ret;
+
+	mutex_init(&data->mutex);
+	data->regmap = regmap;
+	data->dev = dev;
+
+	idev->channels = bu27034_channels;
+	idev->num_channels = ARRAY_SIZE(bu27034_channels);
+	idev->name = "bu27034";
+	idev->info = &bu27034_info;
+
+	idev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
+	idev->available_scan_masks = bu27034_scan_masks;
+
+	ret = bu27034_chip_init(data);
+	if (ret)
+		return ret;
+
+	ret = devm_iio_kfifo_buffer_setup(dev, idev, &bu27034_buffer_ops);
+
+	ret = devm_iio_device_register(dev, idev);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Unable to register iio device\n");
+
+	return ret;
+}
+
+static const struct of_device_id bu27034_of_match[] = {
+	{ .compatible = "rohm,bu27034", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, bu27034_of_match);
+
+static struct i2c_driver bu27034_i2c_driver = {
+	.driver = {
+		.name  = "bu27034-als",
+		.of_match_table = bu27034_of_match,
+	  },
+	.probe_new    = bu27034_probe,
+};
+module_i2c_driver(bu27034_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_DESCRIPTION("ROHM BU27034 ambient light sensor driver");
-- 
2.39.2


-- 
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* [PATCH v2 6/6] MAINTAINERS: Add ROHM BU27034
  2023-03-02 10:57 [PATCH v2 0/6] Support ROHM BU27034 ALS sensor Matti Vaittinen
                   ` (4 preceding siblings ...)
  2023-03-02 10:58 ` [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor Matti Vaittinen
@ 2023-03-02 10:59 ` Matti Vaittinen
  5 siblings, 0 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-02 10:59 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Jonathan Cameron, linux-iio, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1153 bytes --]

Add myself as a maintainer for ROHM BU27034 ALS driver.

Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>

---
Changes since RFCv1:
- Add iio-list
---
 MAINTAINERS | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index af8516d5df36..f75b38e6052d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18102,6 +18102,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/light/bh1750.yaml
 F:	drivers/iio/light/bh1750.c
 
+ROHM BU27034 AMBIENT LIGHT SENSOR DRIVER
+M:	Matti Vaittinen <mazziesaccount@gmail.com>
+L:	linux-iio@vger.kernel.org
+S:	Supported
+F:	drivers/iio/light/rohm-bu27034.c
+
 ROHM MULTIFUNCTION BD9571MWV-M PMIC DEVICE DRIVERS
 M:	Marek Vasut <marek.vasut+renesas@gmail.com>
 L:	linux-kernel@vger.kernel.org
-- 
2.39.2


-- 
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-02 10:58 ` [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor Matti Vaittinen
@ 2023-03-02 14:17   ` Matti Vaittinen
  2023-03-02 15:34   ` Andy Shevchenko
  2023-03-04 20:17   ` Jonathan Cameron
  2 siblings, 0 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-02 14:17 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Jonathan Cameron, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

to 2. maalisk. 2023 klo 12.59 Matti Vaittinen
(mazziesaccount@gmail.com) kirjoitti:
>
> ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes
> capable of detecting a very wide range of illuminance. Typical application
> is adjusting LCD and backlight power of TVs and mobile phones.
>
> Add initial  support for the ROHM BU27034 ambient light sensor.
>
> NOTE:
>         - Driver exposes 4 channels. One IIO_LIGHT channel providing the
>           calculated lux values based on measured data from diodes #0 and
>           #1. Additionally 3 IIO_INTENSITY channels are emitting the raw
>           register data from all diodes for more intense user-space
>           computations.
>         - Sensor has adjustible GAIN values ranging from 1x to 4096x.
>         - Sensor has adjustible measurement times 5, 55, 100, 200 and
>           400 mS. Driver does not support 5 mS which has special
>           limitations.
>         - Driver exposes standard 'scale' adjustment which is
>           implemented by:
>                 1) Trying to adjust only the GAIN
>                 2) If GAIN adjustment only can't provide requested
>                    scale, adjusting both the time and the gain is
>                    attempted.
>         - Driver exposes writable INT_TIME property which can be used
>           for adjusting the measurement time. Time adjustment will also
>           cause the driver to adjust the GAIN so that the overall scale
>           is not changed.
>         - Runtime PM is not implemented.
>         - Driver starts the measurement on the background when it is
>           probed. This improves the respnse time to read-requests

Seems like I forgot to update the commit message. Note to self: change
this when preparing the v3

,,,

> +config ROHM_BU27034
> +       tristate "ROHM BU27034 ambient light sensor"
> +       depends on I2C
> +       select REGMAP_I2C
> +       select IIO_GTS_HELPER

And, judging some other fixes - it seems this one would also need the
select IIO_BUFFER and IIO_KFIFO_BUF now...


-- 

Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~

Discuss - Estimate - Plan - Report and finally accomplish this:
void do_work(int time) __attribute__ ((const));

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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-02 10:57 ` [PATCH v2 2/6] iio: light: Add gain-time-scale helpers Matti Vaittinen
@ 2023-03-02 15:05   ` Andy Shevchenko
  2023-03-03  7:54     ` Vaittinen, Matti
  2023-03-04 18:35   ` Jonathan Cameron
  1 sibling, 1 reply; 36+ messages in thread
From: Andy Shevchenko @ 2023-03-02 15:05 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Jonathan Cameron, Lars-Peter Clausen,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:
> Some light sensors can adjust both the HW-gain and integration time.
> There are cases where adjusting the integration time has similar impact
> to the scale of the reported values as gain setting has.
> 
> IIO users do typically expect to handle scale by a single writable 'scale'
> entry. Driver should then adjust the gain/time accordingly.
> 
> It however is difficult for a driver to know whether it should change
> gain or integration time to meet the requested scale. Usually it is
> preferred to have longer integration time which usually improves
> accuracy, but there may be use-cases where long measurement times can be
> an issue. Thus it can be preferable to allow also changing the
> integration time - but mitigate the scale impact by also changing the gain
> underneath. Eg, if integration time change doubles the measured values,
> the driver can reduce the HW-gain to half.
> 
> The theory of the computations of gain-time-scale is simple. However,
> some people (undersigned) got that implemented wrong for more than once.
> 
> Add some gain-time-scale helpers in order to not dublicate errors in all
> drivers needing these computations.

...

> +/*

Is it intentionally _not_ a kernel doc?

> + * iio_gts_get_gain - Convert scale to total gain

> + * Internal helper for converting scale to total gain.

Otherwise this line should go after the fields, I remember kernel doc warnings
on the similar cases.

> + * @max:	Maximum linearized scale. As an example, when scale is creted in

creted?

IIRC I already pointed out to the very same mistake in your code in the past
(sorry, if my memory doesn't serve me well).

> + *		magnitude of NANOs and max scale is 64.1 - The linearized
> + *		scale is 64 100 000 000.
> + * @scale:	Linearized scale to compte the gain for.
> + *
> + * Return:	(floored) gain corresponding to the scales. -EINVAL if scale

scales? (Plural?)

> + *		is invalid.
> + */

Same remark to all of the comments.

> +{
> +	int tmp = 1;
> +
> +	if (scale > max || !scale)
> +		return -EINVAL;
> +
> +	if (U64_MAX - max < scale) {
> +		/* Risk of overflow */
> +		if (max - scale < scale)
> +			return 1;

> +		while (max - scale > scale * (u64) tmp)

Space is not required after casting.

> +			tmp++;
> +
> +		return tmp + 1;

Wondering why you can't simplify this to

		max -= scale;
		tmp++;

> +	}
> +
> +	while (max > scale * (u64) tmp)
> +		tmp++;
> +
> +	return tmp;
> +}

Missing blank line.

> +/*
> + * gain_get_scale_fraction - get the gain or time based on scale and known one
> + *
> + * Internal helper for computing unknown fraction of total gain.
> + * Compute either gain or time based on scale and either the gain or time
> + * depending on which one is known.
> + *
> + * @max:	Maximum linearized scale. As an example, when scale is creted in

creted?

Is it mistakenly stored in your spellcheck database? Or is it simply
copy'n'paste typo?

> + *		magnitude of NANOs and max scale is 64.1 - The linearized
> + *		scale is 64 100 000 000.
> + * @scale:	Linearized scale to compute the gain/time for.
> + * @known:	Either integration time or gain depending on which one is known
> + * @unknown:	Pointer to variable where the computed gain/time is stored
> + *
> + * Return:	0 on success
> + */

...

> +static const struct iio_itime_sel_mul *
> +			iio_gts_find_itime_by_time(struct iio_gts *gts, int time)

Strange indentation.

Ditto for all these types of cases.

...

> +	*lin_scale = (u64) scale_whole * (u64)scaler + (u64)(scale_nano
> +		     / (NANO / scaler));

Strange indentation. Split on the logical (math) parts better.

...

> +EXPORT_SYMBOL_GPL(iio_init_iio_gts);

I haven't noticed if you put these all exports into a proper namespace.
If no, please do.

...

> +		sort(gains[i], gts->num_hwgain, sizeof(int), iio_gts_gain_cmp,
> +		     NULL);

One line is okay.

...

> +	all_gains = kcalloc(gts->num_itime * gts->num_hwgain, sizeof(int),

Something from overflow.h is very suitable here.

> +			    GFP_KERNEL);
> +	if (!all_gains)
> +		return -ENOMEM;

...

> +	memcpy(all_gains, gains[gts->num_itime - 1], gts->num_hwgain * sizeof(int));

Maybe it's better to have a temporary which will be calculated as array_size()
for allocation and reused here?

...

> +	for (i = gts->num_itime - 2; i >= 0; i--)

Yeah, if you put this into temporary, like

	i = gts->num_itime - 1;

this becomes

	while (i--) {

Note, you missed {} for better reading.

Note, you may re-use that i (maybe renamed to something better in the memcpy()
above as well).

> +		for (j = 0; j < gts->num_hwgain; j++) {
> +			int candidate = gains[i][j];
> +			int chk;
> +
> +			if (candidate > all_gains[new_idx - 1]) {
> +				all_gains[new_idx] = candidate;
> +				new_idx++;
> +
> +				continue;
> +			}
> +			for (chk = 0; chk < new_idx; chk++)
> +				if (candidate <= all_gains[chk])
> +					break;
> +
> +			if (candidate == all_gains[chk])
> +				continue;
> +
> +			memmove(&all_gains[chk + 1], &all_gains[chk],
> +				(new_idx - chk) * sizeof(int));
> +			all_gains[chk] = candidate;
> +			new_idx++;
> +		}

...

> +	gts->avail_all_scales_table = kcalloc(gts->num_avail_all_scales,
> +					      2 * sizeof(int), GFP_KERNEL);
> +	if (!gts->avail_all_scales_table)
> +		ret = -ENOMEM;
> +	else
> +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)

Much easier to read if you move this...

> +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
> +					&gts->avail_all_scales_table[i * 2],
> +					&gts->avail_all_scales_table[i * 2 + 1]);

...here as

		if (ret)
			break;

> +	kfree(all_gains);
> +	if (ret && gts->avail_all_scales_table)
> +		kfree(gts->avail_all_scales_table);
> +
> +	return ret;

But Wouldn't be better to use goto labels?

...

> +	while (i) {

Instead of doing standard

	while (i--) {

> +		/*
> +		 * It does not matter if i'th alloc was not succesfull as
> +		 * kfree(NULL) is safe.
> +		 */

You add this comment, ...

> +		kfree(per_time_gains[i]);
> +		kfree(per_time_scales[i]);

...an additional loop, ...

> +
> +		i--;

...and a line of code.

> +	}

Why?

> +	for (i = gts->num_itime - 1; i >= 0; i--) {

	i = gts->num_itime;

	while (i--) {

> +		int new = gts->itime_table[i].time_us;
> +
> +		if (times[idx] < new) {
> +			times[idx++] = new;
> +			continue;
> +		}
> +
> +		for (j = 0; j <= idx; j++) {
> +			if (times[j] > new) {
> +				memmove(&times[j + 1], &times[j], (idx - j) * sizeof(int));
> +				times[j] = new;
> +				idx++;
> +			}
> +		}
> +	}

...

> +void iio_gts_purge_avail_time_table(struct iio_gts *gts)
> +{
> +	if (gts->num_avail_time_tables) {

	if (!...)
		return;

> +		kfree(gts->avail_time_tables);
> +		gts->avail_time_tables = NULL;
> +		gts->num_avail_time_tables = 0;
> +	}
> +}

...

> +			if (!diff) {

Why not positive conditional?

			if (diff) {
				...
			} else {
				...
			}

> +				diff = gain - gts->hwgain_table[i].gain;
> +				best = i;
> +			} else {
> +				int tmp = gain - gts->hwgain_table[i].gain;
> +
> +				if (tmp < diff) {
> +					diff = tmp;
> +					best = i;
> +				}
> +			}

...

> +	ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain);

> +

Redundant blank line.

> +	if (ret || !iio_gts_valid_gain(gts, *gain))

Why error code is shadowed?

> +		return -EINVAL;
> +

...

> +	ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us,
> +				       &scale);

Single line if fine.

> +	if (ret)
> +		return ret;
> +
> +	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
> +				      new_gain);

Ditto.

> +	if (ret)
> +		return -EINVAL;

...

> +#ifndef __GAIN_TIME_SCALE_HELPER__
> +#define __GAIN_TIME_SCALE_HELPER__

__IIO_... ?

Missing types.h (at least, haven't checked for more).

Missing some forward declarations, at least for struct device.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-02 10:58 ` [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor Matti Vaittinen
  2023-03-02 14:17   ` Matti Vaittinen
@ 2023-03-02 15:34   ` Andy Shevchenko
  2023-03-03  9:17     ` Matti Vaittinen
  2023-03-04 20:17   ` Jonathan Cameron
  2 siblings, 1 reply; 36+ messages in thread
From: Andy Shevchenko @ 2023-03-02 15:34 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Jonathan Cameron, Lars-Peter Clausen,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On Thu, Mar 02, 2023 at 12:58:59PM +0200, Matti Vaittinen wrote:
> ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes
> capable of detecting a very wide range of illuminance. Typical application
> is adjusting LCD and backlight power of TVs and mobile phones.
> 
> Add initial  support for the ROHM BU27034 ambient light sensor.
> 
> NOTE:
> 	- Driver exposes 4 channels. One IIO_LIGHT channel providing the
> 	  calculated lux values based on measured data from diodes #0 and
> 	  #1. Additionally 3 IIO_INTENSITY channels are emitting the raw
> 	  register data from all diodes for more intense user-space
> 	  computations.
> 	- Sensor has adjustible GAIN values ranging from 1x to 4096x.
> 	- Sensor has adjustible measurement times 5, 55, 100, 200 and
> 	  400 mS. Driver does not support 5 mS which has special
> 	  limitations.
> 	- Driver exposes standard 'scale' adjustment which is
> 	  implemented by:
> 		1) Trying to adjust only the GAIN
> 		2) If GAIN adjustment only can't provide requested
> 		   scale, adjusting both the time and the gain is
> 		   attempted.
> 	- Driver exposes writable INT_TIME property which can be used
> 	  for adjusting the measurement time. Time adjustment will also
> 	  cause the driver to adjust the GAIN so that the overall scale
> 	  is not changed.
> 	- Runtime PM is not implemented.
> 	- Driver starts the measurement on the background when it is
> 	  probed. This improves the respnse time to read-requests
> 	  compared to starting the read only when data is requested.
> 	  When the most accurate 400 mS measurement time is used, data reads
> 	  would last quite long if measurement was started only on
> 	  demand. This, however, is not appealing for users who would
> 	  prefere power saving over measurement response time.

...

> +config ROHM_BU27034
> +	tristate "ROHM BU27034 ambient light sensor"

> +	depends on I2C

How? I do not see a such.

> +	select REGMAP_I2C
> +	select IIO_GTS_HELPER
> +	help
> +	  Enable support for the ROHM BU27034 ambient light sensor. ROHM BU27034
> +	  is an ambient light sesnor with 3 channels and 3 photo diodes capable
> +	  of detecting a very wide range of illuminance.
> +	  Typical application is adjusting LCD and backlight power of TVs and
> +	  mobile phones.

Module name?

...

>  obj-$(CONFIG_OPT3001)		+= opt3001.o
>  obj-$(CONFIG_PA12203001)	+= pa12203001.o

> +obj-$(CONFIG_ROHM_BU27034)	+= rohm-bu27034.o

If you see, most of the components are without vendor prefix, why rohm is
special? Like you are expecting the very same filename for something else?

>  obj-$(CONFIG_RPR0521)		+= rpr0521.o
>  obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
>  obj-$(CONFIG_SI1133)		+= si1133.o

...

> +#include <linux/iio/iio.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/kfifo_buf.h>

Sorted?

...

> +#define BU27034_REG_DATA0_LO		0x50
> +#define BU27034_REG_DATA1_LO		0x52
> +#define BU27034_REG_DATA2_LO		0x54

I would drop _LO in all these

> +#define BU27034_REG_DATA2_HI		0x55

and rename somehow this to something like _END / _MAX (similar to the fields.
Perhaps you would need _START / _MIN above.

...

> +/*
> + * Available scales with gain 1x - 4096x, timings 55, 100, 200, 400 mS
> + * Time impacts to gain: 1x, 2x, 4x, 8x.
> + *
> + * => Max total gain is HWGAIN * gain by integration time (8 * 4096) = 32768
> + *
> + * Using NANO precision for scale we must use scale 64x corresponding gain 1x
> + * to avoid precision loss. (32x would result scale 976 562.5(nanos).
> + */
> +#define BU27034_SCALE_1X	64
> +
> +#define BU27034_GSEL_1X		0x00
> +#define BU27034_GSEL_4X		0x08
> +#define BU27034_GSEL_16X	0x0a
> +#define BU27034_GSEL_32X	0x0b
> +#define BU27034_GSEL_64X	0x0c
> +#define BU27034_GSEL_256X	0x18
> +#define BU27034_GSEL_512X	0x19
> +#define BU27034_GSEL_1024X	0x1a
> +#define BU27034_GSEL_2048X	0x1b
> +#define BU27034_GSEL_4096X	0x1c

Shouldn't the values be in plain decimal?

Otherwise I would like to understand bit mapping inside these hex values.

...

> +	.indexed = 1							\

+ Comma at the end.

...

> +	static const int reg[] = {
> +		[BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2,
> +		[BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3,
> +		[BU27034_CHAN_DATA2] = BU27034_REG_MODE_CONTROL2

Ditto.

> +	};

...

> +	struct bu27034_gain_check gains[3] = {
> +		{ .chan = BU27034_CHAN_DATA0, },
> +		{ .chan = BU27034_CHAN_DATA1, },

Inner commas are not needed.

> +		{ .chan = BU27034_CHAN_DATA2 }

But here the outer one is good to have.

> +	};

...

> +	if (chan == BU27034_CHAN_ALS) {
> +		if (val == 0 && val2 == 1000)
> +			return 0;
> +		else

Redundant 'else'. And probably here is better to use standard pattern for
"checking for error first".

> +			return -EINVAL;
> +	}

...

> +		if (helper64 < 0xFFFFFFFFFFFFFLLU) {

Perhaps this needs a definition.

> +			helper64 *= gain0;
> +			do_div(helper64, ch0);
> +		} else {
> +			do_div(helper64, ch0);
> +			helper64 *= gain0;
> +		}


> +	/* Same overflow check here */

Why not a helper function?

> +	if (helper64 < 0xFFFFFFFFFFFFFLLU) {
> +		helper64 *= gain0;
> +		do_div(helper64, helper);
> +	} else {
> +		do_div(helper64, helper);
> +		helper64 *= gain0;
> +	}

...

> +	return (val & BU27034_MASK_VALID);

Unneeded parentheses.

...

> +retry:
> +	/* Get new value from sensor if data is ready */
> +	if (bu27034_has_valid_sample(data)) {
> +		ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO,
> +				       res, size);
> +		if (ret)
> +			return ret;
> +
> +		bu27034_invalidate_read_data(data);
> +	} else {
> +		/* No new data in sensor. Wait and retry */
> +		msleep(25);
> +
> +		goto retry;

There is no way out. What might go wrong?

> +	}

...

> +	ret = bu27034_get_int_time(data);

_get_int_time_us() ? (Looking at the below code)

> +	if (ret < 0)
> +		return ret;
> +
> +	msleep(ret / 1000);

...

> +	 * Avoid div by zeroi. Not using max() as the data may not be in

zeroi?

...

> +	if (!res[0])

Positive conditional?

> +		ch0 = 1;
> +	else
> +		ch0 = le16_to_cpu(res[0]);
> +
> +	if (!res[1])
> +		ch1 = 1;

Ditto.

> +	else
> +		ch1 = le16_to_cpu(res[1]);

But why not to read and convert first and then check. This at least will
correctly compare 0 to the LE16 0 (yes, it's the same for 0, but strictly
speaking the bits order of lvalue and rvalue is different).

...

> +	switch (mask) {
> +	case IIO_CHAN_INFO_INT_TIME:
> +		return iio_gts_avail_times(&data->gts, vals, type, length);
> +	case IIO_CHAN_INFO_SCALE:
> +		return iio_gts_all_avail_scales(&data->gts, vals, type, length);
> +	default:
> +		break;
> +	}
> +
> +	return -EINVAL;

You may do it from default case.

...

> +	ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4,
> +				       val, (val & BU27034_MASK_VALID),

Redundant parentheses.

> +				       BU27034_DATA_WAIT_TIME_US,
> +				       BU27034_TOTAL_DATA_WAIT_TIME_US);
> +	if (ret) {
> +		dev_err(data->dev, "data polling %s\n",
> +			!(val & BU27034_MASK_VALID) ? "timeout" : "fail");

Why not positive conditional in ternary?

> +		return ret;
> +	}

...

> +	fwnode = dev_fwnode(dev);
> +	if (!fwnode)
> +		return -ENODEV;

So, you deliberately disable a possibility to instantiate this from user space,
why?

...

> +	ret = devm_iio_kfifo_buffer_setup(dev, idev, &bu27034_buffer_ops);
> +
> +	ret = devm_iio_device_register(dev, idev);

Don't you find something strange in between?

> +	if (ret < 0)
> +		return dev_err_probe(dev, ret,
> +				     "Unable to register iio device\n");

...

> +	{ .compatible = "rohm,bu27034", },

Inner comma is not needed.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-02 15:05   ` Andy Shevchenko
@ 2023-03-03  7:54     ` Vaittinen, Matti
  2023-03-06 11:13       ` Andy Shevchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Vaittinen, Matti @ 2023-03-03  7:54 UTC (permalink / raw)
  To: Andy Shevchenko, Matti Vaittinen
  Cc: Jonathan Cameron, Lars-Peter Clausen, Paul Gazzillo,
	Dmitry Osipenko, Shreeya Patel, Zhigang Shi, linux-kernel,
	linux-iio

Thanks for the review Andy,

On 3/2/23 17:05, Andy Shevchenko wrote:
> On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:
>> Some light sensors can adjust both the HW-gain and integration time.
>> There are cases where adjusting the integration time has similar impact
>> to the scale of the reported values as gain setting has.
>>
>> IIO users do typically expect to handle scale by a single writable 'scale'
>> entry. Driver should then adjust the gain/time accordingly.
>>
>> It however is difficult for a driver to know whether it should change
>> gain or integration time to meet the requested scale. Usually it is
>> preferred to have longer integration time which usually improves
>> accuracy, but there may be use-cases where long measurement times can be
>> an issue. Thus it can be preferable to allow also changing the
>> integration time - but mitigate the scale impact by also changing the gain
>> underneath. Eg, if integration time change doubles the measured values,
>> the driver can reduce the HW-gain to half.
>>
>> The theory of the computations of gain-time-scale is simple. However,
>> some people (undersigned) got that implemented wrong for more than once.
>>
>> Add some gain-time-scale helpers in order to not dublicate errors in all
>> drivers needing these computations.
> 
> ...
> 
>> +/*
> 
> Is it intentionally _not_ a kernel doc?

yes.

>> + * iio_gts_get_gain - Convert scale to total gain
> 
>> + * Internal helper for converting scale to total gain.
> 
> Otherwise this line should go after the fields, I remember kernel doc warnings
> on the similar cases.
> 
>> + * @max:	Maximum linearized scale. As an example, when scale is creted in
> 
> creted?
> 
> IIRC I already pointed out to the very same mistake in your code in the past
> (sorry, if my memory doesn't serve me well).

I Don't think you have previously reviewed this patch.

>> +{
>> +	int tmp = 1;
>> +
>> +	if (scale > max || !scale)
>> +		return -EINVAL;
>> +
>> +	if (U64_MAX - max < scale) {
>> +		/* Risk of overflow */
>> +		if (max - scale < scale)
>> +			return 1;
> 
>> +		while (max - scale > scale * (u64) tmp)
> 
> Space is not required after casting.
> 
>> +			tmp++;
>> +
>> +		return tmp + 1;
> 
> Wondering why you can't simplify this to
> 
> 		max -= scale;
> 		tmp++;
> 

Sorry, but I don't follow. Can you please show me the complete 
suggestion? Exactly what should be replaced by the:
 > 		max -= scale;
 > 		tmp++;


>> +	}
>> +
>> +	while (max > scale * (u64) tmp)
>> +		tmp++;
>> +
>> +	return tmp;
>> +}

>> +/*
>> + * gain_get_scale_fraction - get the gain or time based on scale and known one
>> + *
>> + * Internal helper for computing unknown fraction of total gain.
>> + * Compute either gain or time based on scale and either the gain or time
>> + * depending on which one is known.
>> + *
>> + * @max:	Maximum linearized scale. As an example, when scale is creted in
> 
> creted?
> 
> Is it mistakenly stored in your spellcheck database? Or is it simply
> copy'n'paste typo?

Typo.

>> + *		magnitude of NANOs and max scale is 64.1 - The linearized
>> + *		scale is 64 100 000 000.
>> + * @scale:	Linearized scale to compute the gain/time for.
>> + * @known:	Either integration time or gain depending on which one is known
>> + * @unknown:	Pointer to variable where the computed gain/time is stored
>> + *
>> + * Return:	0 on success
>> + */
> 
>> +EXPORT_SYMBOL_GPL(iio_init_iio_gts);
> 
> I haven't noticed if you put these all exports into a proper namespace.
> If no, please do.

I haven't. And in many cases I actually see very little to no value in 
doing so as collisions are unlikely when symbols are appropriately prefixed.

Anyways, it seems the IIO uses namespaces so let's play along. Any good 
suggestions for a name?

> ...
> 
>> +	all_gains = kcalloc(gts->num_itime * gts->num_hwgain, sizeof(int),
> 
> Something from overflow.h is very suitable here.
> 
>> +			    GFP_KERNEL);
>> +	if (!all_gains)
>> +		return -ENOMEM;
> 
> ...
> 
>> +	memcpy(all_gains, gains[gts->num_itime - 1], gts->num_hwgain * sizeof(int));
> 
> Maybe it's better to have a temporary which will be calculated as array_size()
> for allocation and reused here? 

Good suggestion, thanks.

> ...
> 
>> +	for (i = gts->num_itime - 2; i >= 0; i--)
> 
> Yeah, if you put this into temporary, like
> 
> 	i = gts->num_itime - 1;
> 
> this becomes
> 
> 	while (i--) {
> 

I prefer for(). It looks clearer to me...

 > Note, you may re-use that i (maybe renamed to something better in the 
memcpy()
 > above as well).

...but this makes sense so Ok.

> Note, you missed {} for better reading.

Didn't miss it but left it out intentionally. {} does not help me as 
indentiation makes it clear. Can add one for you though.

> 
>> +		for (j = 0; j < gts->num_hwgain; j++) {
>> +			int candidate = gains[i][j];
>> +			int chk;
>> +
>> +			if (candidate > all_gains[new_idx - 1]) {
>> +				all_gains[new_idx] = candidate;
>> +				new_idx++;
>> +
>> +				continue;
>> +			}
>> +			for (chk = 0; chk < new_idx; chk++)
>> +				if (candidate <= all_gains[chk])
>> +					break;
>> +
>> +			if (candidate == all_gains[chk])
>> +				continue;
>> +
>> +			memmove(&all_gains[chk + 1], &all_gains[chk],
>> +				(new_idx - chk) * sizeof(int));
>> +			all_gains[chk] = candidate;
>> +			new_idx++;
>> +		}
> 
> ...
> 
>> +	gts->avail_all_scales_table = kcalloc(gts->num_avail_all_scales,
>> +					      2 * sizeof(int), GFP_KERNEL);
>> +	if (!gts->avail_all_scales_table)
>> +		ret = -ENOMEM;
>> +	else
>> +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)
> 
> Much easier to read if you move this...
> 
>> +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
>> +					&gts->avail_all_scales_table[i * 2],
>> +					&gts->avail_all_scales_table[i * 2 + 1]);
> 
> ...here as
> 
> 		if (ret)
> 			break;

I think the !ret in loop condition is obvious. Adding break and brackets 
would not improve this.

> 
>> +	kfree(all_gains);
>> +	if (ret && gts->avail_all_scales_table)
>> +		kfree(gts->avail_all_scales_table);
>> +
>> +	return ret;
> 
> But Wouldn't be better to use goto labels?

I don't see benefit in this case. Handling return value and goto would 
require brackets. The current one is much simpler for me. I am just 
considering dropping that 'else' which is not needed.

> ...
> 
>> +	while (i) {
> 
> Instead of doing standard
> 
> 	while (i--) {
> 
>> +		/*
>> +		 * It does not matter if i'th alloc was not succesfull as
>> +		 * kfree(NULL) is safe.
>> +		 */
> 
> You add this comment, ...
> 
>> +		kfree(per_time_gains[i]);
>> +		kfree(per_time_scales[i]);
> 
> ...an additional loop, ...

The comment is there to explain what I think you missed. We have two 
arrays there. We don't know whether the allocation of first one was 
successful so we try freeing both.

> 
>> +
>> +		i--;
> 
> ...and a line of code.
> 
>> +	}
> 
> Why?

Because, as the comment says, it does not matter if allocation was 
succesfull. kfree(NULL) is safe.

> 
>> +	for (i = gts->num_itime - 1; i >= 0; i--) {
> 
> 	i = gts->num_itime;
> 
> 	while (i--) {

I prefer for() when initializing a variable is needed. Furthermore, 
having var-- or --var in a condition is less clear.

> 
>> +		int new = gts->itime_table[i].time_us;
>> +
>> +		if (times[idx] < new) {
>> +			times[idx++] = new;
>> +			continue;
>> +		}
>> +
>> +		for (j = 0; j <= idx; j++) {
>> +			if (times[j] > new) {
>> +				memmove(&times[j + 1], &times[j], (idx - j) * sizeof(int));
>> +				times[j] = new;
>> +				idx++;
>> +			}
>> +		}
>> +	}
> 
> ...
> 
>> +void iio_gts_purge_avail_time_table(struct iio_gts *gts)
>> +{
>> +	if (gts->num_avail_time_tables) {
> 
> 	if (!...)
> 		return;

Does not improve this as we only have just one small conditional block 
of code which we either execute or not. It is clear at a glance. Would 
make sense if we had longer function.

>> +		kfree(gts->avail_time_tables);
>> +		gts->avail_time_tables = NULL;
>> +		gts->num_avail_time_tables = 0;
>> +	}
>> +}
> 
> ...
> 
>> +			if (!diff) {
> 
> Why not positive conditional?

Because !diff is a special condition and we check explicitly for it.

> 
> 			if (diff) {
> 				...
> 			} else {
> 				...
> 			}
> 
>> +				diff = gain - gts->hwgain_table[i].gain;
>> +				best = i;
>> +			} else {
>> +				int tmp = gain - gts->hwgain_table[i].gain;
>> +
>> +				if (tmp < diff) {
>> +					diff = tmp;
>> +					best = i;
>> +				}
>> +			}
> 
> ...
> 
>> +	ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain);
> 
>> +
> 
> Redundant blank line.

Thanks.

> 
>> +	if (ret || !iio_gts_valid_gain(gts, *gain))
> 
> Why error code is shadowed?

Not sure what I thought of. Could have been because -EINVAL is in any 
case the only error I can think of. But yes, it is better to not 
'shadow' the return value from gain_get_scale_fraction(); So, thanks.

> 
>> +		return -EINVAL;
>> +
> 
> ...
> 
>> +	ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us,
>> +				       &scale);
> 
> Single line if fine.

I like to (mostly) keep the 80-column limit. Helps me to fit three 
editor windows next to each-others on my laptop screen.

>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
>> +				      new_gain);
> 
> Ditto.
> 
>> +	if (ret)
>> +		return -EINVAL;
> 
> ...
> 
>> +#ifndef __GAIN_TIME_SCALE_HELPER__
>> +#define __GAIN_TIME_SCALE_HELPER__
> 
> __IIO_... ?

Well spotted. Thanks. I renamed the file and forgot the old guardian.

By the way, I will be online only occasionally during the next week. So 
please don't think I ignore reviews if you don't hear of me.

Best Regards,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-02 15:34   ` Andy Shevchenko
@ 2023-03-03  9:17     ` Matti Vaittinen
  2023-03-04 19:02       ` Jonathan Cameron
  0 siblings, 1 reply; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-03  9:17 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Matti Vaittinen, Jonathan Cameron, Lars-Peter Clausen,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

Hi Andy,

Thanks again for the review. Some nice catches there.

On 3/2/23 17:34, Andy Shevchenko wrote:
> On Thu, Mar 02, 2023 at 12:58:59PM +0200, Matti Vaittinen wrote:
>> ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes
>> capable of detecting a very wide range of illuminance. Typical application
>> is adjusting LCD and backlight power of TVs and mobile phones.
>>
>> Add initial  support for the ROHM BU27034 ambient light sensor.
>>
>> NOTE:
>> 	- Driver exposes 4 channels. One IIO_LIGHT channel providing the
>> 	  calculated lux values based on measured data from diodes #0 and
>> 	  #1. Additionally 3 IIO_INTENSITY channels are emitting the raw
>> 	  register data from all diodes for more intense user-space
>> 	  computations.
>> 	- Sensor has adjustible GAIN values ranging from 1x to 4096x.
>> 	- Sensor has adjustible measurement times 5, 55, 100, 200 and
>> 	  400 mS. Driver does not support 5 mS which has special
>> 	  limitations.
>> 	- Driver exposes standard 'scale' adjustment which is
>> 	  implemented by:
>> 		1) Trying to adjust only the GAIN
>> 		2) If GAIN adjustment only can't provide requested
>> 		   scale, adjusting both the time and the gain is
>> 		   attempted.
>> 	- Driver exposes writable INT_TIME property which can be used
>> 	  for adjusting the measurement time. Time adjustment will also
>> 	  cause the driver to adjust the GAIN so that the overall scale
>> 	  is not changed.
>> 	- Runtime PM is not implemented.
>> 	- Driver starts the measurement on the background when it is
>> 	  probed. This improves the respnse time to read-requests
>> 	  compared to starting the read only when data is requested.
>> 	  When the most accurate 400 mS measurement time is used, data reads
>> 	  would last quite long if measurement was started only on
>> 	  demand. This, however, is not appealing for users who would
>> 	  prefere power saving over measurement response time.
> 
> ...
> 
>> +config ROHM_BU27034
>> +	tristate "ROHM BU27034 ambient light sensor"
> 
>> +	depends on I2C
> 
> How? I do not see a such.

I have assumed we need this because:

We select REGMAP_I2C which depends on I2C.
What happens if I2C=n and we select REGMAP_I2C? I may be wrong but I 
guess the I2C stays 'n' while REGMAP_I2C becomes y/m (?) I think that 
would be unfortunate - but I can't claim I am confident with how config 
dependencies are handled. I can drop this depends on if you're sure 
that's not a problem.

>> +	select REGMAP_I2C
>> +	select IIO_GTS_HELPER
>> +	help
>> +	  Enable support for the ROHM BU27034 ambient light sensor. ROHM BU27034
>> +	  is an ambient light sesnor with 3 channels and 3 photo diodes capable
>> +	  of detecting a very wide range of illuminance.
>> +	  Typical application is adjusting LCD and backlight power of TVs and
>> +	  mobile phones.
> 
> Module name?

I am having a deja-vu.
https://lore.kernel.org/all/10c4663b-dd65-a545-786d-10aed6e6e5e9@fi.rohmeurope.com/

Module name is completely irrelevant when selecting a kernel configuration.

> ...
> 
>>   obj-$(CONFIG_OPT3001)		+= opt3001.o
>>   obj-$(CONFIG_PA12203001)	+= pa12203001.o
> 
>> +obj-$(CONFIG_ROHM_BU27034)	+= rohm-bu27034.o
> 
> If you see, most of the components are without vendor prefix, why rohm is
> special? Like you are expecting the very same filename for something else?

No. I don't.

Using the vendor prefix in _file name_ was suggested to me by Lee 
already a few years ago. And I am actually grateful he did. I've found 
that _very_ useful as it simplifies finding the files I am looking for. 
What comes to the config option name, being able to easily search for 
the configs by vendor name has also been helpful.

> ...
> 
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/buffer.h>
>> +#include <linux/iio/kfifo_buf.h>
> 
> Sorted?

Sure, thanks.

> 
> ...
> 
>> +#define BU27034_REG_DATA0_LO		0x50
>> +#define BU27034_REG_DATA1_LO		0x52
>> +#define BU27034_REG_DATA2_LO		0x54
> 
> I would drop _LO in all these
> 
>> +#define BU27034_REG_DATA2_HI		0x55
> 
> and rename somehow this to something like _END / _MAX (similar to the fields.
> Perhaps you would need _START / _MIN above.

I don't think this would improve anything. The _LO / _HI are descriptive 
as we have only two registers for each channel, _LO and _HI being more 
or less standard abbreviations for low and high.

> ...
> 
>> +/*
>> + * Available scales with gain 1x - 4096x, timings 55, 100, 200, 400 mS
>> + * Time impacts to gain: 1x, 2x, 4x, 8x.
>> + *
>> + * => Max total gain is HWGAIN * gain by integration time (8 * 4096) = 32768
>> + *
>> + * Using NANO precision for scale we must use scale 64x corresponding gain 1x
>> + * to avoid precision loss. (32x would result scale 976 562.5(nanos).
>> + */
>> +#define BU27034_SCALE_1X	64
>> +
>> +#define BU27034_GSEL_1X		0x00
>> +#define BU27034_GSEL_4X		0x08
>> +#define BU27034_GSEL_16X	0x0a
>> +#define BU27034_GSEL_32X	0x0b
>> +#define BU27034_GSEL_64X	0x0c
>> +#define BU27034_GSEL_256X	0x18
>> +#define BU27034_GSEL_512X	0x19
>> +#define BU27034_GSEL_1024X	0x1a
>> +#define BU27034_GSEL_2048X	0x1b
>> +#define BU27034_GSEL_4096X	0x1c
> 
> Shouldn't the values be in plain decimal?

Why?

> Otherwise I would like to understand bit mapping inside these hex values.

I like having register values in hex. It makes it obvious they don't 
necessarily directly match any 'real world' human-readable values.

> ...
> 
>> +	.indexed = 1							\
> 
> + Comma at the end.

ok.

> 
> ...
> 
>> +	static const int reg[] = {
>> +		[BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2,
>> +		[BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3,
>> +		[BU27034_CHAN_DATA2] = BU27034_REG_MODE_CONTROL2
> 
> Ditto.

ok.

> 
>> +	};
> 
> ...
> 
>> +	struct bu27034_gain_check gains[3] = {
>> +		{ .chan = BU27034_CHAN_DATA0, },
>> +		{ .chan = BU27034_CHAN_DATA1, },
> 
> Inner commas are not needed.
> 
>> +		{ .chan = BU27034_CHAN_DATA2 }
> 
> But here the outer one is good to have.
> 
>> +	};
> 

ok

> ...
> 
>> +	if (chan == BU27034_CHAN_ALS) {
>> +		if (val == 0 && val2 == 1000)
>> +			return 0;
>> +		else
> 
> Redundant 'else'

Thanks for pointing out the unnecessary else. Killing it makes this nicer.


>. And probably here is better to use standard pattern for
> "checking for error first".

I prefer to check for this one specific exactly supported case for ALS 
channel. Cheking for 'all other possibilities but what we are expecting' 
would be counter intuitive.

> 
>> +			return -EINVAL;
>> +	}
> 
> ...
> 
>> +		if (helper64 < 0xFFFFFFFFFFFFFLLU) {
> 
> Perhaps this needs a definition.

I like seeing the value here. It makes this less obfuscating. Comment 
makes the purpose obvious so adding a define would not really give any 
extra advantage.

>> +			helper64 *= gain0;
>> +			do_div(helper64, ch0);
>> +		} else {
>> +			do_div(helper64, ch0);
>> +			helper64 *= gain0;
>> +		}
> 
> 
>> +	/* Same overflow check here */
> 
> Why not a helper function?

I actually was thinking of it - but the check is smallish, only done 
twice and felt a tad too specific to warrant own function. I am not 
really against adding a function if you feel strongly about this :)

> 
>> +	if (helper64 < 0xFFFFFFFFFFFFFLLU) {
>> +		helper64 *= gain0;
>> +		do_div(helper64, helper);
>> +	} else {
>> +		do_div(helper64, helper);
>> +		helper64 *= gain0;
>> +	}
> 
> ...
> 
>> +	return (val & BU27034_MASK_VALID);
> 
> Unneeded parentheses.

ok.

> 
> ...
> 
>> +retry:
>> +	/* Get new value from sensor if data is ready */
>> +	if (bu27034_has_valid_sample(data)) {
>> +		ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO,
>> +				       res, size);
>> +		if (ret)
>> +			return ret;
>> +
>> +		bu27034_invalidate_read_data(data);
>> +	} else {
>> +		/* No new data in sensor. Wait and retry */
>> +		msleep(25);
>> +
>> +		goto retry;
> 
> There is no way out. What might go wrong?

Beyond hanging the user process? :)

I think you have a point here. I'll add a timeout.

> 
>> +	}
> 
> ...
> 
>> +	ret = bu27034_get_int_time(data);
> 
> _get_int_time_us() ? (Looking at the below code)
> 
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	msleep(ret / 1000);
> 
> ...
> 
>> +	 * Avoid div by zeroi. Not using max() as the data may not be in
> 
> zeroi?
> 
> ...
> 
>> +	if (!res[0])
> 
> Positive conditional?

No. Again, we check for the very specific case where res has all bits 
zeroed. Inverse condition is counter intuitive.

> 
>> +		ch0 = 1;
>> +	else
>> +		ch0 = le16_to_cpu(res[0]);
>> +
>> +	if (!res[1])
>> +		ch1 = 1;
> 
> Ditto.
> 
>> +	else
>> +		ch1 = le16_to_cpu(res[1]);
> 
> But why not to read and convert first and then check.

Because conversion is not needed if channel data is zero.

> This at least will
> correctly compare 0 to the LE16 0 (yes, it's the same for 0, but strictly
> speaking the bits order of lvalue and rvalue is different).

and hence we check for !res[0]

> 
> ...
> 
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_INT_TIME:
>> +		return iio_gts_avail_times(&data->gts, vals, type, length);
>> +	case IIO_CHAN_INFO_SCALE:
>> +		return iio_gts_all_avail_scales(&data->gts, vals, type, length);
>> +	default:
>> +		break;
>> +	}
>> +
>> +	return -EINVAL;
> 
> You may do it from default case.
> 

I think we have discussed this one in the past too. I like having return 
at the end of a non void function.

> ...
> 
>> +	ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4,
>> +				       val, (val & BU27034_MASK_VALID),
> 
> Redundant parentheses.

ok

> 
>> +				       BU27034_DATA_WAIT_TIME_US,
>> +				       BU27034_TOTAL_DATA_WAIT_TIME_US);
>> +	if (ret) {
>> +		dev_err(data->dev, "data polling %s\n",
>> +			!(val & BU27034_MASK_VALID) ? "timeout" : "fail");
> 
> Why not positive conditional in ternary?

Because I check this for a specific case: "Was it a timeout?" - not for 
unspecified "Was it something else but timeout?"

> 
>> +		return ret;
>> +	}
> 
> ...
> 
>> +	fwnode = dev_fwnode(dev);
>> +	if (!fwnode)
>> +		return -ENODEV;
> 
> So, you deliberately disable a possibility to instantiate this from user space,
> why?

Thanks! (And Sorry. Jonathan pointed this out to me already in the RFC.) 
I thought I already fixed this.

> 
> ...
> 
>> +	ret = devm_iio_kfifo_buffer_setup(dev, idev, &bu27034_buffer_ops);
>> +
>> +	ret = devm_iio_device_register(dev, idev);
> 
> Don't you find something strange in between?

Thanks!

> 
>> +	if (ret < 0)
>> +		return dev_err_probe(dev, ret,
>> +				     "Unable to register iio device\n");
> 
> ...
> 
>> +	{ .compatible = "rohm,bu27034", },
> 
> Inner comma is not needed.

ok




-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-02 10:57 ` [PATCH v2 2/6] iio: light: Add gain-time-scale helpers Matti Vaittinen
  2023-03-02 15:05   ` Andy Shevchenko
@ 2023-03-04 18:35   ` Jonathan Cameron
  2023-03-04 19:42     ` Matti Vaittinen
  1 sibling, 1 reply; 36+ messages in thread
From: Jonathan Cameron @ 2023-03-04 18:35 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On Thu, 2 Mar 2023 12:57:54 +0200
Matti Vaittinen <mazziesaccount@gmail.com> wrote:

> Some light sensors can adjust both the HW-gain and integration time.
> There are cases where adjusting the integration time has similar impact
> to the scale of the reported values as gain setting has.
> 
> IIO users do typically expect to handle scale by a single writable 'scale'
> entry. Driver should then adjust the gain/time accordingly.
> 
> It however is difficult for a driver to know whether it should change
> gain or integration time to meet the requested scale. Usually it is
> preferred to have longer integration time which usually improves
> accuracy, but there may be use-cases where long measurement times can be
> an issue. Thus it can be preferable to allow also changing the
> integration time - but mitigate the scale impact by also changing the gain
> underneath. Eg, if integration time change doubles the measured values,
> the driver can reduce the HW-gain to half.
> 
> The theory of the computations of gain-time-scale is simple. However,
> some people (undersigned) got that implemented wrong for more than once.
> 
> Add some gain-time-scale helpers in order to not dublicate errors in all
> drivers needing these computations.
> 
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>

Probably makes sense to put the exports in their own namespace.

I've been meaning to finish namespacing the rest of IIO but not
gotten around to it yet.
As this is a separate library probably makes sense to have a unique
namespace for it that the users opt in on.
Perhaps IIO_GTS makes sense?

Otherwise, as Andy's done a detailed review of this version I'll let
you update it for those comments and take another look at v3.

Thanks,

Jonathan



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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-03  9:17     ` Matti Vaittinen
@ 2023-03-04 19:02       ` Jonathan Cameron
  2023-03-04 20:28         ` Matti Vaittinen
  0 siblings, 1 reply; 36+ messages in thread
From: Jonathan Cameron @ 2023-03-04 19:02 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Andy Shevchenko, Matti Vaittinen, Lars-Peter Clausen,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio


A few follow up comments.

Jonathan
> >   
> >>   obj-$(CONFIG_OPT3001)		+= opt3001.o
> >>   obj-$(CONFIG_PA12203001)	+= pa12203001.o  
> >   
> >> +obj-$(CONFIG_ROHM_BU27034)	+= rohm-bu27034.o  
> > 
> > If you see, most of the components are without vendor prefix, why rohm is
> > special? Like you are expecting the very same filename for something else?  
> 
> No. I don't.
> 
> Using the vendor prefix in _file name_ was suggested to me by Lee 
> already a few years ago. And I am actually grateful he did. I've found 
> that _very_ useful as it simplifies finding the files I am looking for. 
> What comes to the config option name, being able to easily search for 
> the configs by vendor name has also been helpful.

For filenames I've no strong preference - though perhaps if I'd been
starting out today I might have decided the convention was to always
add a vendor because of the slight risk of a naming clash.


> 
> > ...
> >   
> >> +#include <linux/iio/iio.h>
> >> +#include <linux/iio/buffer.h>
> >> +#include <linux/iio/kfifo_buf.h>  
> > 
> > Sorted?  
> 
> Sure, thanks.
> 
> > 
> > ...
> >   
> >> +#define BU27034_REG_DATA0_LO		0x50
> >> +#define BU27034_REG_DATA1_LO		0x52
> >> +#define BU27034_REG_DATA2_LO		0x54  
> > 
> > I would drop _LO in all these
> >   
> >> +#define BU27034_REG_DATA2_HI		0x55  
> > 
> > and rename somehow this to something like _END / _MAX (similar to the fields.
> > Perhaps you would need _START / _MIN above.  
> 
> I don't think this would improve anything. The _LO / _HI are descriptive 
> as we have only two registers for each channel, _LO and _HI being more 
> or less standard abbreviations for low and high.

Whilst it might be fairly obvious that we are going to do double reg reads
against these, having the register names match the datasheet is useful.

But in this case the datasheet register map does given them a top level
label without the LO, HI so either option would be easy to find.


> 
> > ...
> >   
> >> +/*
> >> + * Available scales with gain 1x - 4096x, timings 55, 100, 200, 400 mS
> >> + * Time impacts to gain: 1x, 2x, 4x, 8x.
> >> + *
> >> + * => Max total gain is HWGAIN * gain by integration time (8 * 4096) = 32768
> >> + *
> >> + * Using NANO precision for scale we must use scale 64x corresponding gain 1x
> >> + * to avoid precision loss. (32x would result scale 976 562.5(nanos).
> >> + */
> >> +#define BU27034_SCALE_1X	64
> >> +
> >> +#define BU27034_GSEL_1X		0x00
> >> +#define BU27034_GSEL_4X		0x08
> >> +#define BU27034_GSEL_16X	0x0a
> >> +#define BU27034_GSEL_32X	0x0b
> >> +#define BU27034_GSEL_64X	0x0c
> >> +#define BU27034_GSEL_256X	0x18
> >> +#define BU27034_GSEL_512X	0x19
> >> +#define BU27034_GSEL_1024X	0x1a
> >> +#define BU27034_GSEL_2048X	0x1b
> >> +#define BU27034_GSEL_4096X	0x1c  
> > 
> > Shouldn't the values be in plain decimal?  
> 
> Why?

Normally go with datasheet on this as it makes reviewer simpler.
But datasheet is in binary so meh.

> 
> > Otherwise I would like to understand bit mapping inside these hex values.  
> 
> I like having register values in hex. It makes it obvious they don't 
> necessarily directly match any 'real world' human-readable values.

Perhaps a cross reference to the table in the spec is appropriate here?

whilst there are some patterns they aren't terribly consistent so probably
best to just point at the table in the spec.


> >> +		if (helper64 < 0xFFFFFFFFFFFFFLLU) {  
> > 
> > Perhaps this needs a definition.  
> 
> I like seeing the value here. It makes this less obfuscating. Comment 
> makes the purpose obvious so adding a define would not really give any 
> extra advantage.

It's not immediately obvious why it is that many f's.  Perhaps change
to refer to number of bits (which is what matters really I think)
and then use GENMASK() to fill this in?  I think it's 52 bits?

> 
> >> +			helper64 *= gain0;
> >> +			do_div(helper64, ch0);
> >> +		} else {
> >> +			do_div(helper64, ch0);
> >> +			helper64 *= gain0;
> >> +		}  

> > 
> > ...> >> +	if (!res[0])  
> > 
> > Positive conditional?  
> 
> No. Again, we check for the very specific case where res has all bits 
> zeroed. Inverse condition is counter intuitive.
> 
> >   
> >> +		ch0 = 1;
> >> +	else
> >> +		ch0 = le16_to_cpu(res[0]);
> >> +
> >> +	if (!res[1])
> >> +		ch1 = 1;  
> > 
> > Ditto.
> >   
> >> +	else
> >> +		ch1 = le16_to_cpu(res[1]);  
> > 
> > But why not to read and convert first and then check.  
> 
> Because conversion is not needed if channel data is zero.

Conversion is trivial. I agree with Andy here that the logic would look
a bit simpler as (taking it a little further)

	ch0 = max(1, le16_to_cpu(res[0]));
> 
> > This at least will
> > correctly compare 0 to the LE16 0 (yes, it's the same for 0, but strictly
> > speaking the bits order of lvalue and rvalue is different).  
> 
> and hence we check for !res[0]
> 
> > 
> > ...
> >   
> >> +	switch (mask) {
> >> +	case IIO_CHAN_INFO_INT_TIME:
> >> +		return iio_gts_avail_times(&data->gts, vals, type, length);
> >> +	case IIO_CHAN_INFO_SCALE:
> >> +		return iio_gts_all_avail_scales(&data->gts, vals, type, length);
> >> +	default:
> >> +		break;
> >> +	}
> >> +
> >> +	return -EINVAL;  
> > 
> > You may do it from default case.
> >   
> 
> I think we have discussed this one in the past too. I like having return 
> at the end of a non void function.

Given all the earlier returns and the fact that the compiler will shout at
you if it you can get here and it is missing, I'd also suggest just putting
it in the switch statement.



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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-04 18:35   ` Jonathan Cameron
@ 2023-03-04 19:42     ` Matti Vaittinen
  2023-03-06 11:15       ` Andy Shevchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-04 19:42 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

Hi Jonathan, all

On 3/4/23 20:35, Jonathan Cameron wrote:
> On Thu, 2 Mar 2023 12:57:54 +0200
> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> 
>> Some light sensors can adjust both the HW-gain and integration time.
>> There are cases where adjusting the integration time has similar impact
>> to the scale of the reported values as gain setting has.
>>
>> IIO users do typically expect to handle scale by a single writable 'scale'
>> entry. Driver should then adjust the gain/time accordingly.
>>
>> It however is difficult for a driver to know whether it should change
>> gain or integration time to meet the requested scale. Usually it is
>> preferred to have longer integration time which usually improves
>> accuracy, but there may be use-cases where long measurement times can be
>> an issue. Thus it can be preferable to allow also changing the
>> integration time - but mitigate the scale impact by also changing the gain
>> underneath. Eg, if integration time change doubles the measured values,
>> the driver can reduce the HW-gain to half.
>>
>> The theory of the computations of gain-time-scale is simple. However,
>> some people (undersigned) got that implemented wrong for more than once.
>>
>> Add some gain-time-scale helpers in order to not dublicate errors in all
>> drivers needing these computations.
>>
>> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
> 
> Probably makes sense to put the exports in their own namespace.

Andy asked for that as well. And, while I do not really see the 
usefulness of the namespaces when all symbols are properly prefixed (I 
only see added complexity there) - I agreed to use one.

> 
> I've been meaning to finish namespacing the rest of IIO but not
> gotten around to it yet.
> As this is a separate library probably makes sense to have a unique
> namespace for it that the users opt in on.
> Perhaps IIO_GTS makes sense?

Thanks. I think I'll use that. Although, as all of the symbols are 
prefixed with iio_gts - if I really saw a risk of symbol clash it would 
probably make more sense to use just about anything else ;) This because 
if someone else were prefixing symbols with iio_gts - he would likely be 
using the exactly same namespace.

> Otherwise, as Andy's done a detailed review of this version I'll let
> you update it for those comments and take another look at v3.

This suits me fine. I have v3 almost prepared - but I'll be very much 
away from the computer next week so it may be the v3 will not be out 
until later. It may be I won't continue work with this until about after 
a week.

Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-02 10:58 ` [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor Matti Vaittinen
  2023-03-02 14:17   ` Matti Vaittinen
  2023-03-02 15:34   ` Andy Shevchenko
@ 2023-03-04 20:17   ` Jonathan Cameron
  2023-03-05 12:22     ` Matti Vaittinen
  2023-03-05 13:10     ` Matti Vaittinen
  2 siblings, 2 replies; 36+ messages in thread
From: Jonathan Cameron @ 2023-03-04 20:17 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On Thu, 2 Mar 2023 12:58:59 +0200
Matti Vaittinen <mazziesaccount@gmail.com> wrote:

> ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes
> capable of detecting a very wide range of illuminance. Typical application
> is adjusting LCD and backlight power of TVs and mobile phones.
> 
> Add initial  support for the ROHM BU27034 ambient light sensor.
> 
> NOTE:
> 	- Driver exposes 4 channels. One IIO_LIGHT channel providing the
> 	  calculated lux values based on measured data from diodes #0 and
> 	  #1. Additionally 3 IIO_INTENSITY channels are emitting the raw
> 	  register data from all diodes for more intense user-space
> 	  computations.
> 	- Sensor has adjustible GAIN values ranging from 1x to 4096x.
> 	- Sensor has adjustible measurement times 5, 55, 100, 200 and
> 	  400 mS. Driver does not support 5 mS which has special
> 	  limitations.
> 	- Driver exposes standard 'scale' adjustment which is
> 	  implemented by:
> 		1) Trying to adjust only the GAIN
> 		2) If GAIN adjustment only can't provide requested
> 		   scale, adjusting both the time and the gain is
> 		   attempted.
> 	- Driver exposes writable INT_TIME property which can be used
> 	  for adjusting the measurement time. Time adjustment will also
> 	  cause the driver to adjust the GAIN so that the overall scale
> 	  is not changed.

Probably want a 'where possible' note here.

> 	- Runtime PM is not implemented.

I wouldn't bother noting things that are not implemented. That's a long list!

> 	- Driver starts the measurement on the background when it is
> 	  probed. This improves the respnse time to read-requests

Spell check.  (I often forget to do this myself)

> 	  compared to starting the read only when data is requested.
> 	  When the most accurate 400 mS measurement time is used, data reads
> 	  would last quite long if measurement was started only on
> 	  demand. This, however, is not appealing for users who would
> 	  prefere power saving over measurement response time.
As you noted this needs updating

> 
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>

Some comments inline to add to Andy's review.#
It's a big driver so we may well need a few more versions to converge completely.

Jonathan


> 
> ---
> Changes since RFCv1
> - (really) protect read-only registers
> - fix get and set gain
> - buffered mode
> - Protect the whole sequences including meas_en/meas_dis to avoid messing
>   up the enable / disable order
> - typofixes / doc improvements
> - change dropped GAIN_SCALE_ITIME_MS() to GAIN_SCALE_ITIME_US()
> - use more accurate scale for lux channel (milli lux)
> - provide available scales / integration times (using helpers).
> - adapt to renamed iio-gts-helpers.h file
> - bu27034 - longer lines in Kconfig
> - Drop bu27034_meas_en and bu27034_meas_dis wrappers.
> - Change device-name from bu27034-als to bu27034
> ---
>> diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c
> new file mode 100644
> index 000000000000..b8ca29af22e7
> --- /dev/null
> +++ b/drivers/iio/light/rohm-bu27034.c
> @@ -0,0 +1,1491 @@

> +/*
> + * The BU27034 does not have interrupt or any other mechanism of triggering
> + * the data read when measurement has finished. Hence we poll the VALID bit in
> + * a thread. We will try to wake the thread BU27034_MEAS_WAIT_PREMATURE_MS
> + * milliseconds before the expected sampling time to prevent the drifting. Eg,
> + * If we constantly wake up a bit too late we would eventually skip a sample.

Lazier approach would be to just sent the sampling frequency at twice the
expected frequency and you'll never miss a sample unless you the wake up is
delayed massively for some reason.  Particularly 'fresh' data might not matter
enough that half a cycle late is a problem.

> + * And because the sleep can't wake up _exactly_ at given time this would be
> + * inevitable even if the sensor clock would be perfectly phase-locked to CPU
> + * clock - which we can't say is the case.
> + *
> + * This is still fragile. No matter how big advance do we have, we will still
> + * risk of losing a sample because things can in a rainy-day skenario be
> + * delayed a lot. Yet, more we reserve the time for polling, more we also lose
> + * the performance by spending cycles polling the register. So, selecting this
> + * value is a balancing dance between severity of wasting CPU time and severity
> + * of losing samples.
> + *
> + * In most cases losing the samples is not _that_ crucial because light levels
> + * tend to change slowly.
> + */
> +#define BU27034_MEAS_WAIT_PREMATURE_MS	5
> +#define BU27034_DATA_WAIT_TIME_US	1000
> +#define BU27034_TOTAL_DATA_WAIT_TIME_US (BU27034_MEAS_WAIT_PREMATURE_MS * 1000)
> +
Single blank line is enough to separate sections.
> +
> +enum {
> +	BU27034_CHAN_ALS,
> +	BU27034_CHAN_DATA0,
> +	BU27034_CHAN_DATA1,
> +	BU27034_CHAN_DATA2,
> +	BU27034_NUM_CHANS
> +};


> +static const struct iio_chan_spec bu27034_channels[] = {
> +	{
> +		.type = IIO_LIGHT,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
> +				      BIT(IIO_CHAN_INFO_SCALE),

What is this scale for?
Given the channel is computed from various different inputs, is there a
clear definition of how it is scaled?  What does a write to it mean?

> +		.channel = BU27034_CHAN_ALS,
> +		.scan_index = BU27034_CHAN_ALS,
> +		.scan_type = {
> +			.sign = 'u',
> +			.realbits = 32,
> +			.storagebits = 32,
> +			.endianness = IIO_CPU,
> +		},
> +	},
> +	/*
> +	 * The BU27034 DATA0 and DATA1 channels are both on the visible light
> +	 * area (mostly). The data0 sensitivity peaks at 500nm, DATA1 at 600nm.
> +	 * These wave lengths are pretty much on the border of colours making
> +	 * these a poor candidates for R/G/B standardization. Hence they're both
> +	 * marked as clear channels
> +	 */
> +	BU27034_CHAN_DATA(DATA0, IIO_MOD_LIGHT_CLEAR),
> +	BU27034_CHAN_DATA(DATA1, IIO_MOD_LIGHT_CLEAR),
> +	BU27034_CHAN_DATA(DATA2, IIO_MOD_LIGHT_IR),
> +	IIO_CHAN_SOFT_TIMESTAMP(4),
> +};


> +/* Caller should hold the lock to protect data->int_time */
> +static int bu27034_set_int_time(struct bu27034_data *data, int time)
> +{
> +	int ret;
> +
> +	ret = iio_gts_find_sel_by_int_time(&data->gts, time);
> +	if (ret < 0)
> +		return ret;
> +
> +	return regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1,
> +				 BU27034_MASK_MEAS_MODE, ret);
> +
> +	return time;
?  Never get to this.
> +}
> +
> +/*
> + * We try to change the time in such way that the scale is maintained for
> + * given channels by adjusting gain so that it compensates the time change.
> + */
> +static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us)
> +{
> +	int ret, int_time_old, int_time_new, i;
> +	struct bu27034_gain_check gains[3] = {
> +		{ .chan = BU27034_CHAN_DATA0, },
> +		{ .chan = BU27034_CHAN_DATA1, },
> +		{ .chan = BU27034_CHAN_DATA2 }
> +	};
> +	int numg = ARRAY_SIZE(gains);
> +
> +	mutex_lock(&data->mutex);
> +	ret = bu27034_get_int_time(data);
> +	if (ret < 0)
> +		goto unlock_out;
> +
> +	int_time_old = ret;
> +
> +	ret = bu27034_validate_int_time(data, time_us);
> +	if (ret < 0) {
> +		dev_err(data->dev, "Unsupported integration time %u\n",
> +			time_us);
> +
> +		goto unlock_out;
> +	}
> +
> +	int_time_new = ret;
> +
> +	if (int_time_new == int_time_old) {
> +		ret = 0;
> +		goto unlock_out;
> +	}
> +
> +	for (i = 0; i < numg; i++) {
> +		ret = bu27034_get_gain(data, gains[i].chan,
> +				       &gains[i].old_gain);
> +		if (ret)
> +			goto unlock_out;
> +
> +		gains[i].new_gain = gains[i].old_gain * int_time_old /
> +				    int_time_new;
> +
> +		if (!iio_gts_valid_gain(&data->gts, gains[i].new_gain)) {
> +			int scale1, scale2;
> +			bool ok;
> +
> +			_bu27034_get_scale(data, gains[i].chan, &scale1, &scale2);
> +			dev_dbg(data->dev,
> +				"chan %u, can't support time %u with scale %u %u\n",
> +				gains[i].chan, time_us, scale1, scale2);
> +
> +			/*
> +			 * As Jonathan put it, if caller requests for

Probably don't reference me directly in a driver.  Keep the 'blame' for the
email threads :)  Comment is fine otherwise.

> +			 * integration time change and we can't support the
> +			 * scale - then the caller should be prepared to
> +			 * 'pick up the pieces and deal with the fact that the
> +			 * scale changed'.
> +			 */
> +			ret = iio_find_closest_gain_low(&data->gts,
> +							gains[i].new_gain, &ok);
> +
> +			if (!ok) {
> +				dev_dbg(data->dev,
> +					"optimal gain out of range for chan %u\n",
> +					gains[i].chan);
> +			}
> +			if (ret < 0) {
> +				dev_warn(data->dev,
> +					 "Total gain increase. Risk of saturation");
dev_dbg()
> +				ret = iio_gts_get_min_gain(&data->gts);
> +				if (ret < 0)
> +					goto unlock_out;
> +			}
> +			dev_warn(data->dev, "chan %u scale changed\n",
> +				 gains[i].chan);
dev_dbg()

We shouldn't convey this sort of info via the kernel log. Most users would never
see it anyway!.
> +			gains[i].new_gain = ret;
> +			dev_dbg(data->dev, "chan %u new gain %u\n",
> +				gains[i].chan, gains[i].new_gain);
> +		}
> +	}
> +
> +	/*
> +	 * The new integration time can be supported while keeping the scale of
> +	 * channels intact by tuning the gains.

This comment is in a path that is hit event if we go through the warnings
above that say this isn't true.

> +	 */
> +	for (i = 0; i < numg; i++) {
> +		ret = bu27034_set_gain(data, gains[i].chan, gains[i].new_gain);
> +		if (ret)
> +			goto unlock_out;
> +	}
> +
> +	ret = bu27034_set_int_time(data, int_time_new);
> +
> +unlock_out:
> +	mutex_unlock(&data->mutex);
> +
> +	return ret;
> +}
> +
> +static int bu27034_set_scale(struct bu27034_data *data, int chan,
> +			    int val, int val2)
> +{
> +	int ret, time_sel, gain_sel, i;
> +	bool found = false;
> +
> +	if (chan == BU27034_CHAN_ALS) {
> +		if (val == 0 && val2 == 1000)
> +			return 0;
> +		else
> +			return -EINVAL;
> +	}
> +
> +	mutex_lock(&data->mutex);
> +	ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &time_sel);
> +	if (ret)
> +		goto unlock_out;
> +
> +	ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, time_sel,
> +						val, val2 * 1000, &gain_sel);
> +	if (ret) {
> +		/*
> +		 * Could'n support scale with given time. Need to change time.

Could not

> +		 * We still want to maintain the scale for all channels
> +		 */
> +		int new_time_sel;
> +		struct bu27034_gain_check gains[2];
> +
> +		/*
> +		 * Populate information for the two other channels which should
> +		 * maintain the scale.
> +		 */
> +		if (chan == BU27034_CHAN_DATA0) {
> +			gains[0].chan = BU27034_CHAN_DATA1;
> +			gains[1].chan = BU27034_CHAN_DATA2;
> +		} else if (chan == BU27034_CHAN_DATA1) {
> +			gains[0].chan = BU27034_CHAN_DATA0;
> +			gains[1].chan = BU27034_CHAN_DATA2;
> +		} else {
> +			gains[0].chan = BU27034_CHAN_DATA0;
> +			gains[1].chan = BU27034_CHAN_DATA1;
> +		}
> +
> +		for (i = 0; i < 2; i++) {
> +			ret = bu27034_get_gain(data, gains[i].chan,
> +					       &gains[i].old_gain);
> +			if (ret)
> +				goto unlock_out;
> +		}
> +
> +		/*
> +		 * Iterate through all the times to see if we find one which
> +		 * can support requested scale for requested channel, while
> +		 * maintaining the scale for other channels
> +		 */
> +		for (i = 0; i < data->gts.num_itime; i++) {
> +			new_time_sel = data->gts.itime_table[i].sel;
> +
> +			if (new_time_sel == time_sel)
> +				continue;
> +
> +			/* Can we provide requested scale with this time? */
> +			ret = iio_gts_find_gain_sel_for_scale_using_time(
> +				&data->gts, new_time_sel, val, val2 * 1000,
> +				&gain_sel);
> +			if (ret)
> +				continue;
> +
> +			/* Can the two other channels maintain scales? */
> +			ret = iio_gts_find_new_gain_sel_by_old_gain_time(
> +				&data->gts, gains[0].old_gain, time_sel,
> +				new_time_sel, &gains[0].new_gain);
> +			if (ret)
> +				continue;
> +
> +			ret = iio_gts_find_new_gain_sel_by_old_gain_time(
> +				&data->gts, gains[1].old_gain, time_sel,
> +				new_time_sel, &gains[1].new_gain);
> +			if (!ret) {
> +				/* Yes - we found suitable time */
> +				found = true;
> +				break;
> +			}
> +		}
> +		if (!found) {
> +			dev_err(data->dev,
> +				"Can't set scale maintaining other channels\n");

We shouldn't print an error to the log in a path like this as we just gave
userspace an easy way to overflow the log.  Just return -EINVAL without the
print.  Fine to add it as dev_dbg() though if you like.

> +			ret = -EINVAL;
> +
> +			goto unlock_out;
> +		}
> +
> +		for (i = 0; i < 2; i++) {
> +			ret = bu27034_set_gain(data, gains[i].chan,
> +						gains[i].new_gain);
> +			if (ret)
> +				goto unlock_out;
> +		}
> +
> +		ret = regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1,
> +				  BU27034_MASK_MEAS_MODE, new_time_sel);
> +		if (ret)
> +			goto unlock_out;
> +	}
> +
> +	ret = bu27034_write_gain_sel(data, chan, gain_sel);
> +unlock_out:
> +	mutex_unlock(&data->mutex);
> +
> +	return ret;
> +}

...

> +
> +static bool bu27034_has_valid_sample(struct bu27034_data *data)
> +{
> +	int ret, val;
> +
> +	ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL4, &val);
> +	if (ret)
> +		dev_err(data->dev, "Read failed %d\n", ret);

Don't use the value if error occurred as unknown state.
Just do return false in that path.

> +
> +	return (val & BU27034_MASK_VALID);
No brackets needed.
> +}

...

> +static int bu27034_get_single_result(struct bu27034_data *data, int chan,
> +				     int *val)
> +{
> +	int ret;
> +
> +
One blank line is enough!

> +	ret = bu27034_meas_set(data, true);
> +	if (ret)
> +		return ret;
> +
> +	ret = bu27034_get_int_time(data);
> +	if (ret < 0)
> +		return ret;
> +
> +	msleep(ret / 1000);
> +
> +	return bu27034_read_result(data, chan, val);
> +}
> +
> +/*
> + * The formula given by vendor for computing luxes out of data0 and data1
> + * (in open air) is as follows:
> + *
> + * Let's mark:
> + * D0 = data0/ch0_gain/meas_time_ms * 25600
> + * D1 = data1/ch1_gain/meas_time_ms * 25600
> + *
> + * Then:
> + * if (D1/D0 < 0.87)
> + *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 0.87) * 3.45 + 1)
> + * else if (D1/D0 < 1)
> + *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 0.87) * 0.385 + 1)
> + * else
> + *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 2) * -0.05 + 1)
> + *
> + * We use it here. Users who have for example some colored lens
> + * need to modify the calculation but I hope this gives a starting point for
> + * those working with such devices.
> + */
> +
> +static int bu27034_calc_lux(struct bu27034_data *data, __le16 *res, int *val)

As you are going to put it in the buffer, make val a fixed size integer.
The current approach of calculate in an int and copy to a u32 is a bit nasty.
Of course if there is a chance of a large enough value you'll have to be careful
for the unsigned to signed conversion on 32 bit platforms. I doubt there is, but
a comment saying why not would be great in the code that is hit from read_raw()

> +{
> +	unsigned int gain0, gain1, meastime;
> +	unsigned int d1_d0_ratio_scaled;
> +	u16  ch0, ch1;
> +	u64 helper64;
> +	int ret;
> +
> +	/*
> +	 * We return 0 luxes if calculation fails. This should be reasonably
> +	 * easy to spot from the buffers especially if raw-data channels show
> +	 * valid values
> +	 */
> +	*val = 0;
> +
> +	/*
> +	 * Avoid div by zeroi. Not using max() as the data may not be in
> +	 * native endianes
> +	 */
> +	if (!res[0])
> +		ch0 = 1;
> +	else
> +		ch0 = le16_to_cpu(res[0]);

As per other branch of the thread.

	ch0 = max(1, le16_to_cpu(res[0]);

would be cleaner.

> +
> +	if (!res[1])
> +		ch1 = 1;
> +	else
> +		ch1 = le16_to_cpu(res[1]);
> +
> +
> +	ret = bu27034_get_gain(data, BU27034_CHAN_DATA0, &gain0);
> +	if (ret)
> +		return ret;
> +
> +	ret = bu27034_get_gain(data, BU27034_CHAN_DATA1, &gain1);
> +	if (ret)
> +		return ret;
> +
> +	ret = bu27034_get_int_time(data);
> +	if (ret < 0)
> +		return ret;
> +
> +	meastime = ret;
> +
> +	d1_d0_ratio_scaled = (unsigned int)ch1 * (unsigned int)gain0 * 100;
> +	helper64 = (u64)ch1 * (u64)gain0 * 100LLU;
> +
> +	if (helper64 != d1_d0_ratio_scaled) {
> +		unsigned int div = (unsigned int)ch0 * gain1;
> +
> +		do_div(helper64, div);
> +		d1_d0_ratio_scaled = helper64;
> +	} else {
> +		d1_d0_ratio_scaled /= ch0 * gain1;
> +	}
> +
> +	if (d1_d0_ratio_scaled < 87)
> +		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 0);
> +	else if (d1_d0_ratio_scaled < 100)
> +		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 1);
> +	else
> +		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 2);
> +
> +	return 0;
> +
> +}
> +
> +
> +static int bu27034_read_raw(struct iio_dev *idev,
> +			   struct iio_chan_spec const *chan,
> +			   int *val, int *val2, long mask)
> +{
> +	struct bu27034_data *data = iio_priv(idev);
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_INT_TIME:
> +		*val = bu27034_get_int_time(data);
> +		if (*val < 0)
> +			return *val;
> +
> +		/*
> +		 * We use 50000 uS internally for all calculations and only
> +		 * convert it to 55000 before returning it to the user.
> +		 *
> +		 * This is because the data-sheet says the time is 55 mS - but
> +		 * vendor provided computations used 50 mS.
> +		 */
> +		if (*val == 50000)
> +			*val = 55000;
> +
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_SCALE:
> +		return bu27034_get_scale(data, chan->channel, val, val2);
> +
> +	case IIO_CHAN_INFO_RAW:
> +	{
> +		if (chan->type != IIO_INTENSITY)
> +			return -EINVAL;
> +
> +		if (chan->channel < BU27034_CHAN_DATA0 ||
> +		    chan->channel > BU27034_CHAN_DATA2)
> +			return -EINVAL;
> +
> +		/* Don't mess with measurement enabling while buffering */
> +		ret = iio_device_claim_direct_mode(idev);
> +		if (ret)
> +			return ret;
> +
> +		mutex_lock(&data->mutex);
> +		/*
> +		 * Reading one channel at a time is ineffiecient but we don't
> +		 * care here. Buffered version should be used if performance is
> +		 * an issue.
> +		 */
> +		ret = bu27034_get_single_result(data, chan->channel, val);
> +		if (ret)
> +			goto release_err_out;

Move the if (ret) after the locks are released then you don't need
the messy goto.

> +
> +		mutex_unlock(&data->mutex);
> +		iio_device_release_direct_mode(idev);
> +
> +		return IIO_VAL_INT;
> +	}
> +
> +	case IIO_CHAN_INFO_PROCESSED:
> +		if (chan->type != IIO_LIGHT)
> +			return -EINVAL;
> +
> +		/* Don't mess with measurement enabling while buffering */
> +		ret = iio_device_claim_direct_mode(idev);
> +		if (ret)
> +			return ret;
> +
> +		mutex_lock(&data->mutex);

See below. I would factor out the rest of this so that you can
unconditionally unlock and then check the return value.

> +		ret = bu27034_meas_set(data, true);
> +		if (ret)
> +			goto release_err_out;
> +
> +		ret = bu27034_get_lux(data, val);
> +		if (ret)
> +			goto release_err_out;
> +
> +		ret = bu27034_meas_set(data, false);
> +		if (ret)
> +			dev_err(data->dev, "failed to disable measurement\n");
> +
> +		mutex_unlock(&data->mutex);
> +		iio_device_release_direct_mode(idev);
> +
> +		return IIO_VAL_INT;
> +
> +	default:
> +		return -EINVAL;
> +
> +	}
> +
> +release_err_out:
> +	mutex_unlock(&data->mutex);
> +	iio_device_release_direct_mode(idev);
> +

Having goto targets that are outside of case statements within the arms
of a switch is usually a strong hint that it makes sense to factor
out the code

> +	return ret;
> +}
> +


blank line here.

> +static const struct iio_info bu27034_info = {
> +	.read_raw = &bu27034_read_raw,
> +	.write_raw = &bu27034_write_raw,
> +	.read_avail = &bu27034_read_avail,
> +};
> +



...

> +
> +static int bu27034_probe(struct i2c_client *i2c)
> +{
> +	struct device *dev = &i2c->dev;
> +	struct fwnode_handle *fwnode;
> +	struct bu27034_data *data;
> +	struct regmap *regmap;
> +	struct iio_dev *idev;
> +	unsigned int part_id;
> +	int ret;
> +
> +	regmap = devm_regmap_init_i2c(i2c, &bu27034_regmap);
> +	if (IS_ERR(regmap))
> +		return dev_err_probe(dev, PTR_ERR(regmap),
> +				     "Failed to initialize Regmap\n");
> +
> +	fwnode = dev_fwnode(dev);
> +	if (!fwnode)
> +		return -ENODEV;

You've already noted this shouldn't be here.

> +
> +	idev = devm_iio_device_alloc(dev, sizeof(*data));
> +	if (!idev)
> +		return -ENOMEM;
> +
> +	ret = devm_regulator_get_enable(dev, "vdd");
> +	if (ret && ret != -ENODEV)
> +		return dev_err_probe(dev, ret, "Failed to get regulator\n");
> +
> +	data = iio_priv(idev);
> +
> +	ret = regmap_read(regmap, BU27034_REG_SYSTEM_CONTROL, &part_id);

As it's not all of the register I'd rename the temporary variable to
val or reg or something along those lines.

> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to access sensor\n");
> +
> +	part_id &= BU27034_MASK_PART_ID;

FIELD_GET() even when it's lower bits as then there is no need for
a reviewer to confirm that it is the lower bits.
Then you can just do

	if (FIELD_GET(BU27034_MASK_PART_ID, reg) != BU27034_ID)

> +
> +	if (part_id != BU27034_ID)
> +		dev_warn(dev, "unsupported device 0x%x\n", part_id);

I'd adjust that to "unknown device" or "unrecognised device" as it might
well be supported just fine based on the compatible fallback, we just have
no way of knowing if it is.

> +
> +	ret = iio_init_iio_gts(BU27034_SCALE_1X, 0, bu27034_gains,
> +			       ARRAY_SIZE(bu27034_gains), bu27034_itimes,
> +			       ARRAY_SIZE(bu27034_itimes), &data->gts);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_iio_gts_build_avail_tables(dev, &data->gts);
> +	if (ret)
> +		return ret;
> +
> +	mutex_init(&data->mutex);
> +	data->regmap = regmap;
> +	data->dev = dev;
> +
> +	idev->channels = bu27034_channels;
> +	idev->num_channels = ARRAY_SIZE(bu27034_channels);
> +	idev->name = "bu27034";
> +	idev->info = &bu27034_info;
> +
> +	idev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
> +	idev->available_scan_masks = bu27034_scan_masks;
> +
> +	ret = bu27034_chip_init(data);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_iio_kfifo_buffer_setup(dev, idev, &bu27034_buffer_ops);

check ret and error out on failure.

> +
> +	ret = devm_iio_device_register(dev, idev);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret,
> +				     "Unable to register iio device\n");
> +
> +	return ret;
> +}
> +
> +static const struct of_device_id bu27034_of_match[] = {
> +	{ .compatible = "rohm,bu27034", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, bu27034_of_match);
> +
> +static struct i2c_driver bu27034_i2c_driver = {
> +	.driver = {
> +		.name  = "bu27034-als",
> +		.of_match_table = bu27034_of_match,
> +	  },
> +	.probe_new    = bu27034_probe,

I'd just use a single space in all cases before the =
Attempts to align the values in this structure don't really help readability
and often end up messier over time.

> +};
> +module_i2c_driver(bu27034_i2c_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
> +MODULE_DESCRIPTION("ROHM BU27034 ambient light sensor driver");


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-04 19:02       ` Jonathan Cameron
@ 2023-03-04 20:28         ` Matti Vaittinen
  0 siblings, 0 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-04 20:28 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Andy Shevchenko, Matti Vaittinen, Lars-Peter Clausen,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On 3/4/23 21:02, Jonathan Cameron wrote:

>>> ...
>>>    
>>>> +/*
>>>> + * Available scales with gain 1x - 4096x, timings 55, 100, 200, 400 mS
>>>> + * Time impacts to gain: 1x, 2x, 4x, 8x.
>>>> + *
>>>> + * => Max total gain is HWGAIN * gain by integration time (8 * 4096) = 32768
>>>> + *
>>>> + * Using NANO precision for scale we must use scale 64x corresponding gain 1x
>>>> + * to avoid precision loss. (32x would result scale 976 562.5(nanos).
>>>> + */
>>>> +#define BU27034_SCALE_1X	64
>>>> +
>>>> +#define BU27034_GSEL_1X		0x00
>>>> +#define BU27034_GSEL_4X		0x08
>>>> +#define BU27034_GSEL_16X	0x0a
>>>> +#define BU27034_GSEL_32X	0x0b
>>>> +#define BU27034_GSEL_64X	0x0c
>>>> +#define BU27034_GSEL_256X	0x18
>>>> +#define BU27034_GSEL_512X	0x19
>>>> +#define BU27034_GSEL_1024X	0x1a
>>>> +#define BU27034_GSEL_2048X	0x1b
>>>> +#define BU27034_GSEL_4096X	0x1c
>>>
>>> Shouldn't the values be in plain decimal?
>>
>> Why?
> 
> Normally go with datasheet on this as it makes reviewer simpler.
> But datasheet is in binary so meh.
> 
>>
>>> Otherwise I would like to understand bit mapping inside these hex values.
>>
>> I like having register values in hex. It makes it obvious they don't
>> necessarily directly match any 'real world' human-readable values.
> 
> Perhaps a cross reference to the table in the spec is appropriate here?

I think adding a reference to the table in the data-sheet is good. 
Although - I gave some feedback about the data-sheet inside the company. 
It may be we will eventually see another version of it...

> whilst there are some patterns they aren't terribly consistent so probably
> best to just point at the table in the spec.
> 
> 
>>>> +		if (helper64 < 0xFFFFFFFFFFFFFLLU) {
>>>
>>> Perhaps this needs a definition.
>>
>> I like seeing the value here. It makes this less obfuscating. Comment
>> makes the purpose obvious so adding a define would not really give any
>> extra advantage.
> 
> It's not immediately obvious why it is that many f's.  Perhaps change
> to refer to number of bits (which is what matters really I think)
> and then use GENMASK() to fill this in?  I think it's 52 bits?

I am personally not used to the GENMASK(). I am always wondering whether 
the parameters were start + end bits, or star bit + number of set bits 
or ... It's somehow easier for me to understand the hex values - 
(especially when they are composed of all fff's or 1, 3, 7 or 2, 4, 8 ... ).

Well, I understand that is not universally true though so GENMASK() can 
for sure be simpler for most others - and it does not hide the value as 
define would do. So yes, GENMASK() could make sense here.

>>>> +			helper64 *= gain0;
>>>> +			do_div(helper64, ch0);
>>>> +		} else {
>>>> +			do_div(helper64, ch0);
>>>> +			helper64 *= gain0;
>>>> +		}
> 
>>>
>>> ...> >> +	if (!res[0])
>>>
>>> Positive conditional?
>>
>> No. Again, we check for the very specific case where res has all bits
>> zeroed. Inverse condition is counter intuitive.
>>
>>>    
>>>> +		ch0 = 1;
>>>> +	else
>>>> +		ch0 = le16_to_cpu(res[0]);
>>>> +
>>>> +	if (!res[1])
>>>> +		ch1 = 1;
>>>
>>> Ditto.
>>>    
>>>> +	else
>>>> +		ch1 = le16_to_cpu(res[1]);
>>>
>>> But why not to read and convert first and then check.
>>
>> Because conversion is not needed if channel data is zero.
> 
> Conversion is trivial. I agree with Andy here that the logic would look
> a bit simpler as (taking it a little further)
> 
> 	ch0 = max(1, le16_to_cpu(res[0]));

It's strange how differently we read the code. For me:

if (!val)
	ch = 1;
else
	ch =  le16_to_cpu(val);

tells what is happening at a glance whereas the:
ch0 = max(1, le16_to_cpu(res[0]));

really requires some focusing to see what is going on. For me it is both 
less clear and less efficient :(

But alas, if this is what is preferred by both of you, then I guess 
that's what it will look like.

>>>
>>>> +	switch (mask) {
>>>> +	case IIO_CHAN_INFO_INT_TIME:
>>>> +		return iio_gts_avail_times(&data->gts, vals, type, length);
>>>> +	case IIO_CHAN_INFO_SCALE:
>>>> +		return iio_gts_all_avail_scales(&data->gts, vals, type, length);
>>>> +	default:
>>>> +		break;
>>>> +	}
>>>> +
>>>> +	return -EINVAL;
>>>
>>> You may do it from default case.
>>>    
>>
>> I think we have discussed this one in the past too. I like having return
>> at the end of a non void function.
> 
> Given all the earlier returns and the fact that the compiler will shout at
> you if it you can get here and it is missing, I'd also suggest just putting
> it in the switch statement.

Ok. IIO is your territory. If the roles were switched I would definitely 
ask for having the returns at the end of the function - and at the end 
of the function only (when possible w/o lot of extra complication).

So perhaps I just have to accept that you want to have it your way with 
IIO :)

Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-04 20:17   ` Jonathan Cameron
@ 2023-03-05 12:22     ` Matti Vaittinen
  2023-03-12 15:36       ` Jonathan Cameron
  2023-03-05 13:10     ` Matti Vaittinen
  1 sibling, 1 reply; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-05 12:22 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On 3/4/23 22:17, Jonathan Cameron wrote:
> On Thu, 2 Mar 2023 12:58:59 +0200
> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> 
>> +/*
>> + * The BU27034 does not have interrupt or any other mechanism of triggering
>> + * the data read when measurement has finished. Hence we poll the VALID bit in
>> + * a thread. We will try to wake the thread BU27034_MEAS_WAIT_PREMATURE_MS
>> + * milliseconds before the expected sampling time to prevent the drifting. Eg,
>> + * If we constantly wake up a bit too late we would eventually skip a sample.
> 
> Lazier approach would be to just sent the sampling frequency at twice the
> expected frequency and you'll never miss a sample unless you the wake up is
> delayed massively for some reason.  Particularly 'fresh' data might not matter
> enough that half a cycle late is a problem.

Hmm. Do I read this right - You suggest we drop the polling loop for 
valid bit and just always sleep for int_time / 2 if data was not valid?

I don't know. That would probably make the time-stamps for buffered 
results to be jumping quite a bit - especially with the longer 
integration times.

>> + * And because the sleep can't wake up _exactly_ at given time this would be
>> + * inevitable even if the sensor clock would be perfectly phase-locked to CPU
>> + * clock - which we can't say is the case.
>> + *
>> + * This is still fragile. No matter how big advance do we have, we will still
>> + * risk of losing a sample because things can in a rainy-day skenario be
>> + * delayed a lot. Yet, more we reserve the time for polling, more we also lose
>> + * the performance by spending cycles polling the register. So, selecting this
>> + * value is a balancing dance between severity of wasting CPU time and severity
>> + * of losing samples.
>> + *
>> + * In most cases losing the samples is not _that_ crucial because light levels
>> + * tend to change slowly.
>> + */
>> +#define BU27034_MEAS_WAIT_PREMATURE_MS	5
>> +#define BU27034_DATA_WAIT_TIME_US	1000
>> +#define BU27034_TOTAL_DATA_WAIT_TIME_US (BU27034_MEAS_WAIT_PREMATURE_MS * 1000)
> 
>> +static const struct iio_chan_spec bu27034_channels[] = {
>> +	{
>> +		.type = IIO_LIGHT,
>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
>> +				      BIT(IIO_CHAN_INFO_SCALE),
> 
> What is this scale for?

The scale is to inform users that we return data using milli lux.

> Given the channel is computed from various different inputs, is there a
> clear definition of how it is scaled?  What does a write to it mean?

Nothing. writing anything else but milli lux scale fails with -EINVAL.

I guess I am doing something in an unusual way here :) Do you have a 
suggestion for me?


>> +			/*
>> +			 * As Jonathan put it, if caller requests for
> 
> Probably don't reference me directly in a driver.  Keep the 'blame' for the
> email threads :)  Comment is fine otherwise.

Okay, okay :) I just liked the expression and didn't want to take the 
credit for it ;)

>> +
>> +	/*
>> +	 * The new integration time can be supported while keeping the scale of
>> +	 * channels intact by tuning the gains.
> 
> This comment is in a path that is hit event if we go through the warnings
> above that say this isn't true.

Oh! Valid point! This changed when I allowed gain to be changed - need 
to drop the comment. Thanks!

>> +
>> +static int bu27034_calc_lux(struct bu27034_data *data, __le16 *res, int *val)
> 
> As you are going to put it in the buffer, make val a fixed size integer.
> The current approach of calculate in an int and copy to a u32 is a bit nasty.
> Of course if there is a chance of a large enough value you'll have to be careful
> for the unsigned to signed conversion on 32 bit platforms. I doubt there is, but
> a comment saying why not would be great in the code that is hit from read_raw()

This same ..._calc_lux() is used also from the read_raw. I'd rather keep 
this using ints so there would be no need for involving u32 in 
read_raw() path. However, you are correct in that the current calling 
from buffered read where we pass pointer to u32 here - is nasty. I'll 
add temporary int in the calling function there, and add casting with a 
comment when storing value to scan.lux. Thanks for pointing this out! 
There was also missing an error return problem - see below.

>> +
>> +	if (d1_d0_ratio_scaled < 87)
>> +		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 0);
>> +	else if (d1_d0_ratio_scaled < 100)
>> +		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 1);
>> +	else
>> +		*val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 2);
>> +

The bu27034_fixp_calc_lx() might return -EINVAL - which we missed here. 
I'll fix also this one.

>> +
>> +	case IIO_CHAN_INFO_PROCESSED:
>> +		if (chan->type != IIO_LIGHT)
>> +			return -EINVAL;
>> +
>> +		/* Don't mess with measurement enabling while buffering */
>> +		ret = iio_device_claim_direct_mode(idev);
>> +		if (ret)
>> +			return ret;
>> +
>> +		mutex_lock(&data->mutex);
> 
> See below. I would factor out the rest of this so that you can
> unconditionally unlock and then check the return value.

Yes. moving measurement start/stop in bu27034_get_lux() makes this much 
nicer. I wonder how I didn't see that myself - thanks.

>> +
>> +	ret = regmap_read(regmap, BU27034_REG_SYSTEM_CONTROL, &part_id);
> 
> As it's not all of the register I'd rename the temporary variable to
> val or reg or something along those lines.

I still like having the variable named part_id - as it makes the check 
obvious. What I did was adding another temporary variable 'reg' and doing:

part_id = FIELD_GET(BU27034_MASK_PART_ID, reg);

and then using the part_id in if() and dev_warn().

> 
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "Failed to access sensor\n");
>> +
>> +	part_id &= BU27034_MASK_PART_ID;
> 
> FIELD_GET() even when it's lower bits as then there is no need for
> a reviewer to confirm that it is the lower bits.
> Then you can just do
> 
> 	if (FIELD_GET(BU27034_MASK_PART_ID, reg) != BU27034_ID)
> 
>> +
>> +	if (part_id != BU27034_ID)
>> +		dev_warn(dev, "unsupported device 0x%x\n", part_id);
> 
> I'd adjust that to "unknown device" or "unrecognised device" as it might
> well be supported just fine based on the compatible fallback, we just have
> no way of knowing if it is.
> 

Hmm. Won't promise but maybe I have the time to finish v3 tonight. We 
can then continue discussions towards the v4 :)

Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-04 20:17   ` Jonathan Cameron
  2023-03-05 12:22     ` Matti Vaittinen
@ 2023-03-05 13:10     ` Matti Vaittinen
  2023-03-06 11:21       ` Andy Shevchenko
  1 sibling, 1 reply; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-05 13:10 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On 3/4/23 22:17, Jonathan Cameron wrote:
> On Thu, 2 Mar 2023 12:58:59 +0200
> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> 
> As per other branch of the thread.
> 
> 	ch0 = max(1, le16_to_cpu(res[0]);
>  > would be cleaner.

I tried this out. Comparing u16 to literal 1 results comparison of 
values with different sizes:

./include/linux/minmax.h:20:28: warning: comparison of distinct pointer 
types lacks a cast
   (!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
                             ^
./include/linux/minmax.h:26:4: note: in expansion of macro ‘__typecheck’
    (__typecheck(x, y) && __no_side_effects(x, y))
     ^~~~~~~~~~~
./include/linux/minmax.h:36:24: note: in expansion of macro ‘__safe_cmp’
   __builtin_choose_expr(__safe_cmp(x, y), \
                         ^~~~~~~~~~
./include/linux/minmax.h:74:19: note: in expansion of macro ‘__careful_cmp’
  #define max(x, y) __careful_cmp(x, y, >)
                    ^~~~~~~~~~~~~
drivers/iio/light/rohm-bu27034.c:1057:8: note: in expansion of macro ‘max’
   ch0 = max(1, ch0);


I could work around this by doing:

const u16 min_ch_val = 1;

...

ch0 = max(min_ch_val, le16_to_cpu(res[0]));

but I think that would really be obfuscating the meaning. I assume

ch0 = max((u16)1, le16_to_cpu(res[0]));

might work too - but to me it's pretty ugly.

The more I am looking at this, the stronger I feel we should really just 
write this as it was. Check if res[0] contains the only unsafe data 
"!res[0]" - and if yes, set it to 1. The comment above it will clarify 
it to a reader wondering what happens.

I will leave it like it was in v2 for v3. If you still feel strong about 
it then we need to continue rubbing it.

Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-03  7:54     ` Vaittinen, Matti
@ 2023-03-06 11:13       ` Andy Shevchenko
  2023-03-13 11:31         ` Matti Vaittinen
  0 siblings, 1 reply; 36+ messages in thread
From: Andy Shevchenko @ 2023-03-06 11:13 UTC (permalink / raw)
  To: Vaittinen, Matti
  Cc: Matti Vaittinen, Jonathan Cameron, Lars-Peter Clausen,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On Fri, Mar 03, 2023 at 07:54:22AM +0000, Vaittinen, Matti wrote:
> On 3/2/23 17:05, Andy Shevchenko wrote:
> > On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:

...

> >> +{
> >> +	int tmp = 1;
> >> +
> >> +	if (scale > max || !scale)
> >> +		return -EINVAL;
> >> +
> >> +	if (U64_MAX - max < scale) {
> >> +		/* Risk of overflow */
> >> +		if (max - scale < scale)
> >> +			return 1;
> > 
> >> +		while (max - scale > scale * (u64) tmp)
> > 
> > Space is not required after casting.
> > 
> >> +			tmp++;
> >> +
> >> +		return tmp + 1;
> > 
> > Wondering why you can't simplify this to
> > 
> > 		max -= scale;
> > 		tmp++;
> > 
> 
> Sorry, but I don't follow. Can you please show me the complete 
> suggestion? Exactly what should be replaced by the:

I don't understand. Do you see the blank lines I specifically added to show
the piece of the code I'm commenting on?

For your convenience, the while loop and return statement may be replaced with
two simple assignments.

>  > 		max -= scale;
>  > 		tmp++;

> >> +	}
> >> +
> >> +	while (max > scale * (u64) tmp)
> >> +		tmp++;
> >> +
> >> +	return tmp;
> >> +}

...

> >> +EXPORT_SYMBOL_GPL(iio_init_iio_gts);
> > 
> > I haven't noticed if you put these all exports into a proper namespace.
> > If no, please do.
> 
> I haven't. And in many cases I actually see very little to no value in 
> doing so as collisions are unlikely when symbols are appropriately prefixed.

This is a benefit of not polluting (global) namespace with something that is
rarely used. Please, try to utilize namespaces more in your nice kernel
contribution (there is *no* sarcasm).

> Anyways, it seems the IIO uses namespaces so let's play along. Any good 
> suggestions for a name?

IIO_GTS ?

...

> >> +	for (i = gts->num_itime - 2; i >= 0; i--)
> > 
> > Yeah, if you put this into temporary, like
> > 
> > 	i = gts->num_itime - 1;
> > 
> > this becomes
> > 
> > 	while (i--) {
> > 
> 
> I prefer for(). It looks clearer to me...

It has too many characters to parse. That's why I prefer while.
Are we going to have principle disagreement on this one?

>  > Note, you may re-use that i (maybe renamed to something better in the 
> memcpy()
>  > above as well).
> 
> ...but this makes sense so Ok.

...

> > Note, you missed {} for better reading.
> 
> Didn't miss it but left it out intentionally. {} does not help me as 
> indentiation makes it clear. Can add one for you though.

Not for me. For other people who would read 1+ screen long for-loop body (not
everybody has 120+ lines of terminal.

...

> >> +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)
> > 
> > Much easier to read if you move this...
> > 
> >> +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
> >> +					&gts->avail_all_scales_table[i * 2],
> >> +					&gts->avail_all_scales_table[i * 2 + 1]);
> > 
> > ...here as
> > 
> > 		if (ret)
> > 			break;
> 
> I think the !ret in loop condition is obvious. Adding break and brackets 
> would not improve this.

It moves it to the regular pattern. Yours is not so distributed in the kernel.

...

> >> +	kfree(all_gains);
> >> +	if (ret && gts->avail_all_scales_table)
> >> +		kfree(gts->avail_all_scales_table);
> >> +
> >> +	return ret;
> > 
> > But Wouldn't be better to use goto labels?
> 
> I don't see benefit in this case. Handling return value and goto would 
> require brackets. The current one is much simpler for me. I am just 
> considering dropping that 'else' which is not needed.

At least it would be consistent with the very same file style in another
function. So, why there do you goto, and not here where it makes sense
to me?

...

> >> +	while (i) {
> > 
> > Instead of doing standard
> > 
> > 	while (i--) {
> > 
> >> +		/*
> >> +		 * It does not matter if i'th alloc was not succesfull as
> >> +		 * kfree(NULL) is safe.
> >> +		 */
> > 
> > You add this comment, ...
> > 
> >> +		kfree(per_time_gains[i]);
> >> +		kfree(per_time_scales[i]);
> > 
> > ...an additional loop, ...
> 
> The comment is there to explain what I think you missed. We have two 
> arrays there. We don't know whether the allocation of first one was 
> successful so we try freeing both.
> 
> > 
> >> +
> >> +		i--;
> > 
> > ...and a line of code.
> > 
> >> +	}
> > 
> > Why?
> 
> Because, as the comment says, it does not matter if allocation was 
> succesfull. kfree(NULL) is safe.

Wouldn't be better to avoid no-op kfree() by explicitly putting the call before
hands?

	kfree(...[i]);
	while (i--) {
		...
	}

?

Much better to understand than what you wrote. I have to read comment on top of
the code, with my proposal comment won't be needed.

...

> >> +	for (i = gts->num_itime - 1; i >= 0; i--) {
> > 
> > 	i = gts->num_itime;
> > 
> > 	while (i--) {
> 
> I prefer for() when initializing a variable is needed. Furthermore, 
> having var-- or --var in a condition is less clear.

	while (x--)

_is_ a pattern for cleaning up something. Your for-loop has a lot of additional
(unnecessary) code lines that makes it harder to understand (by the people who
are not familiar with the pattern).

> >> +	}

...

> >> +void iio_gts_purge_avail_time_table(struct iio_gts *gts)
> >> +{
> >> +	if (gts->num_avail_time_tables) {
> > 
> > 	if (!...)
> > 		return;
> 
> Does not improve this as we only have just one small conditional block 
> of code which we either execute or not. It is clear at a glance. Would 
> make sense if we had longer function.

I believe it makes sense to have like this even for this long function(s).

> >> +		kfree(gts->avail_time_tables);
> >> +		gts->avail_time_tables = NULL;
> >> +		gts->num_avail_time_tables = 0;
> >> +	}
> >> +}

...

> >> +			if (!diff) {
> > 
> > Why not positive conditional?
> 
> Because !diff is a special condition and we check explicitly for it.

And how my suggestion makes it different?

(Note, it's easy to miss the ! in the conditionals, that's why positive ones
 are preferable.)

> > 
> > 			if (diff) {
> > 				...
> > 			} else {
> > 				...
> > 			}
> > 

> >> +			} else {

> >> +			}

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-04 19:42     ` Matti Vaittinen
@ 2023-03-06 11:15       ` Andy Shevchenko
  0 siblings, 0 replies; 36+ messages in thread
From: Andy Shevchenko @ 2023-03-06 11:15 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Jonathan Cameron, Matti Vaittinen, Lars-Peter Clausen,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On Sat, Mar 04, 2023 at 09:42:22PM +0200, Matti Vaittinen wrote:
> On 3/4/23 20:35, Jonathan Cameron wrote:
> > On Thu, 2 Mar 2023 12:57:54 +0200

...

> > I've been meaning to finish namespacing the rest of IIO but not
> > gotten around to it yet.
> > As this is a separate library probably makes sense to have a unique
> > namespace for it that the users opt in on.
> > Perhaps IIO_GTS makes sense?
> 
> Thanks. I think I'll use that. Although, as all of the symbols are prefixed
> with iio_gts - if I really saw a risk of symbol clash it would probably make
> more sense to use just about anything else ;) This because if someone else
> were prefixing symbols with iio_gts - he would likely be using the exactly
> same namespace.

There is another aspect, i.e. global namespace pollution. This saves memory
if not used.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-05 13:10     ` Matti Vaittinen
@ 2023-03-06 11:21       ` Andy Shevchenko
  2023-03-13  8:54         ` Matti Vaittinen
  0 siblings, 1 reply; 36+ messages in thread
From: Andy Shevchenko @ 2023-03-06 11:21 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Jonathan Cameron, Matti Vaittinen, Lars-Peter Clausen,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On Sun, Mar 05, 2023 at 03:10:38PM +0200, Matti Vaittinen wrote:
> On 3/4/23 22:17, Jonathan Cameron wrote:
> > On Thu, 2 Mar 2023 12:58:59 +0200
> > Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> > 
> > As per other branch of the thread.
> > 
> > 	ch0 = max(1, le16_to_cpu(res[0]);
> >  > would be cleaner.
> 
> I tried this out. Comparing u16 to literal 1 results comparison of values
> with different sizes:
> 
> ./include/linux/minmax.h:20:28: warning: comparison of distinct pointer
> types lacks a cast
>   (!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
>                             ^
> ./include/linux/minmax.h:26:4: note: in expansion of macro ‘__typecheck’
>    (__typecheck(x, y) && __no_side_effects(x, y))
>     ^~~~~~~~~~~
> ./include/linux/minmax.h:36:24: note: in expansion of macro ‘__safe_cmp’
>   __builtin_choose_expr(__safe_cmp(x, y), \
>                         ^~~~~~~~~~
> ./include/linux/minmax.h:74:19: note: in expansion of macro ‘__careful_cmp’
>  #define max(x, y) __careful_cmp(x, y, >)
>                    ^~~~~~~~~~~~~
> drivers/iio/light/rohm-bu27034.c:1057:8: note: in expansion of macro ‘max’
>   ch0 = max(1, ch0);
> 
> 
> I could work around this by doing:
> 
> const u16 min_ch_val = 1;
> 
> ...
> 
> ch0 = max(min_ch_val, le16_to_cpu(res[0]));
> 
> but I think that would really be obfuscating the meaning. I assume
> 
> ch0 = max((u16)1, le16_to_cpu(res[0]));
> 
> might work too - but to me it's pretty ugly.

That's why we have max_t() and clamp_val().
And you know that.

> 
> The more I am looking at this, the stronger I feel we should really just
> write this as it was. Check if res[0] contains the only unsafe data
> "!res[0]" - and if yes, set it to 1. The comment above it will clarify it to
> a reader wondering what happens.
> 
> I will leave it like it was in v2 for v3. If you still feel strong about it
> then we need to continue rubbing it.

You need to convert bit ordering first, then check for 0. It would at least
make more sense. (Today is 0 you are comparing with, tomorrow it might be
0xfffe, which is different to 0x7fff).

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-05 12:22     ` Matti Vaittinen
@ 2023-03-12 15:36       ` Jonathan Cameron
  2023-03-13  9:39         ` Matti Vaittinen
  2023-03-14  9:39         ` Matti Vaittinen
  0 siblings, 2 replies; 36+ messages in thread
From: Jonathan Cameron @ 2023-03-12 15:36 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On Sun, 5 Mar 2023 14:22:51 +0200
Matti Vaittinen <mazziesaccount@gmail.com> wrote:

> On 3/4/23 22:17, Jonathan Cameron wrote:
> > On Thu, 2 Mar 2023 12:58:59 +0200
> > Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> >   
> >> +/*
> >> + * The BU27034 does not have interrupt or any other mechanism of triggering
> >> + * the data read when measurement has finished. Hence we poll the VALID bit in
> >> + * a thread. We will try to wake the thread BU27034_MEAS_WAIT_PREMATURE_MS
> >> + * milliseconds before the expected sampling time to prevent the drifting. Eg,
> >> + * If we constantly wake up a bit too late we would eventually skip a sample.  
> > 
> > Lazier approach would be to just sent the sampling frequency at twice the
> > expected frequency and you'll never miss a sample unless you the wake up is
> > delayed massively for some reason.  Particularly 'fresh' data might not matter
> > enough that half a cycle late is a problem.  
> 
> Hmm. Do I read this right - You suggest we drop the polling loop for 
> valid bit and just always sleep for int_time / 2 if data was not valid?

Yes.  There are costs to both methods, but the advantage here is that the chance
of being so late you miss becomes much less.

> 
> I don't know. That would probably make the time-stamps for buffered 
> results to be jumping quite a bit - especially with the longer 
> integration times.

True enough.  They are probably fairly noisy either way but this would make them
worse.

> 
> >> + * And because the sleep can't wake up _exactly_ at given time this would be
> >> + * inevitable even if the sensor clock would be perfectly phase-locked to CPU
> >> + * clock - which we can't say is the case.
> >> + *
> >> + * This is still fragile. No matter how big advance do we have, we will still
> >> + * risk of losing a sample because things can in a rainy-day skenario be
> >> + * delayed a lot. Yet, more we reserve the time for polling, more we also lose
> >> + * the performance by spending cycles polling the register. So, selecting this
> >> + * value is a balancing dance between severity of wasting CPU time and severity
> >> + * of losing samples.
> >> + *
> >> + * In most cases losing the samples is not _that_ crucial because light levels
> >> + * tend to change slowly.
> >> + */
> >> +#define BU27034_MEAS_WAIT_PREMATURE_MS	5
> >> +#define BU27034_DATA_WAIT_TIME_US	1000
> >> +#define BU27034_TOTAL_DATA_WAIT_TIME_US (BU27034_MEAS_WAIT_PREMATURE_MS * 1000)  
> >   
> >> +static const struct iio_chan_spec bu27034_channels[] = {
> >> +	{
> >> +		.type = IIO_LIGHT,
> >> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
> >> +				      BIT(IIO_CHAN_INFO_SCALE),  
> > 
> > What is this scale for?  
> 
> The scale is to inform users that we return data using milli lux.
> 
> > Given the channel is computed from various different inputs, is there a
> > clear definition of how it is scaled?  What does a write to it mean?  
> 
> Nothing. writing anything else but milli lux scale fails with -EINVAL.
> 
> I guess I am doing something in an unusual way here :) Do you have a 
> suggestion for me?

Return data in lux?  Or return it as INFO_RAW - thus making it clear
that the reading is not in expected units and a conversion must be
applied by userspace.  SCALE is not applied to PROCESSED by userspace.

In the rare case where you do get SCALE and PROCESSED it's there to allow
for changes in the underlying signal measurement that are eaten up in the
computation needed to get to PROCESSED - that is they have no visible
affect (beyond range changes etc).


...

> >> +
> >> +	ret = regmap_read(regmap, BU27034_REG_SYSTEM_CONTROL, &part_id);  
> > 
> > As it's not all of the register I'd rename the temporary variable to
> > val or reg or something along those lines.  
> 
> I still like having the variable named part_id - as it makes the check 
> obvious. What I did was adding another temporary variable 'reg' and doing:
> 
> part_id = FIELD_GET(BU27034_MASK_PART_ID, reg);
> 
> and then using the part_id in if() and dev_warn().

Looks good.


Jonathan


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-06 11:21       ` Andy Shevchenko
@ 2023-03-13  8:54         ` Matti Vaittinen
  0 siblings, 0 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-13  8:54 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Jonathan Cameron, Matti Vaittinen, Lars-Peter Clausen,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On 3/6/23 13:21, Andy Shevchenko wrote:
> On Sun, Mar 05, 2023 at 03:10:38PM +0200, Matti Vaittinen wrote:
>> On 3/4/23 22:17, Jonathan Cameron wrote:
>>> On Thu, 2 Mar 2023 12:58:59 +0200
>>> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
>>>
>>> As per other branch of the thread.
>>>
>>> 	ch0 = max(1, le16_to_cpu(res[0]);
>>>   > would be cleaner.
>>
>> I tried this out. Comparing u16 to literal 1 results comparison of values
>> with different sizes:
>>
>> ./include/linux/minmax.h:20:28: warning: comparison of distinct pointer
>> types lacks a cast
>>    (!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
>>                              ^
>> ./include/linux/minmax.h:26:4: note: in expansion of macro ‘__typecheck’
>>     (__typecheck(x, y) && __no_side_effects(x, y))
>>      ^~~~~~~~~~~
>> ./include/linux/minmax.h:36:24: note: in expansion of macro ‘__safe_cmp’
>>    __builtin_choose_expr(__safe_cmp(x, y), \
>>                          ^~~~~~~~~~
>> ./include/linux/minmax.h:74:19: note: in expansion of macro ‘__careful_cmp’
>>   #define max(x, y) __careful_cmp(x, y, >)
>>                     ^~~~~~~~~~~~~
>> drivers/iio/light/rohm-bu27034.c:1057:8: note: in expansion of macro ‘max’
>>    ch0 = max(1, ch0);
>>
>>
>> I could work around this by doing:
>>
>> const u16 min_ch_val = 1;
>>
>> ...
>>
>> ch0 = max(min_ch_val, le16_to_cpu(res[0]));
>>
>> but I think that would really be obfuscating the meaning. I assume
>>
>> ch0 = max((u16)1, le16_to_cpu(res[0]));
>>
>> might work too - but to me it's pretty ugly.
> 
> That's why we have max_t() and clamp_val().
> And you know that.

Thanks for pointing out the max_t(). I really missed that. I didn't 
think of the clamp_val() either. clamp_val() would "explain" better what 
we do here - but as we don't have the upper limit using that would (IMO) 
be slightly more confusing.

>> The more I am looking at this, the stronger I feel we should really just
>> write this as it was. Check if res[0] contains the only unsafe data
>> "!res[0]" - and if yes, set it to 1. The comment above it will clarify it to
>> a reader wondering what happens.
>>
>> I will leave it like it was in v2 for v3. If you still feel strong about it
>> then we need to continue rubbing it.
> 
> You need to convert bit ordering first, then check for 0. It would at least
> make more sense. (Today is 0 you are comparing with, tomorrow it might be
> 0xfffe, which is different to 0x7fff).

I don't think being prepared for any other check but the check for 0 is 
meaningful here. Every other value except 0 is valid for this sensor and 
can be accepted as such. Preparing for something that may never realize 
(new sensor with less than 16bits of data) is likely to just make things 
more complicated.

Well, as both You and Jonathan do think we should not just do 
if(!res[0]) - I'll try using the max_t() as you suggested. I still 
really dislike it because it makes it harder (for me) to capture what 
happens here. Well, I try to survive with this and hopefully just read 
the comment above if puzzled when I need to get back to this code.

Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-12 15:36       ` Jonathan Cameron
@ 2023-03-13  9:39         ` Matti Vaittinen
  2023-03-18 16:54           ` Jonathan Cameron
  2023-03-14  9:39         ` Matti Vaittinen
  1 sibling, 1 reply; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-13  9:39 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On 3/12/23 17:36, Jonathan Cameron wrote:
> On Sun, 5 Mar 2023 14:22:51 +0200
> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> 
>> On 3/4/23 22:17, Jonathan Cameron wrote:
>>> On Thu, 2 Mar 2023 12:58:59 +0200
>>> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
>>>    

// snip

>>>> +static const struct iio_chan_spec bu27034_channels[] = {
>>>> +	{
>>>> +		.type = IIO_LIGHT,
>>>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
>>>> +				      BIT(IIO_CHAN_INFO_SCALE),
>>>
>>> What is this scale for?
>>
>> The scale is to inform users that we return data using milli lux.
>>
>>> Given the channel is computed from various different inputs, is there a
>>> clear definition of how it is scaled?  What does a write to it mean?
>>
>> Nothing. writing anything else but milli lux scale fails with -EINVAL.
>>
>> I guess I am doing something in an unusual way here :) Do you have a
>> suggestion for me?
> 
> Return data in lux?

That's what I did originally have. But then I noticed we can get 
slightly better accuracy than that. Hence I switched to mLux and added 
the scale.

>  Or return it as INFO_RAW - thus making it clear
> that the reading is not in expected units and a conversion must be
> applied by userspace.  SCALE is not applied to PROCESSED by userspace.

Ah. This makes sense then. Maybe it would be worth adding a warning to 
IIO-core if drivers set both the SCALE and PROCESSED info bits?

So, I need to select between the simplicity or better accuracy here? :/ 
I really hate ending up making choices like this without knowing all the 
real use-cases :( And it happens a lot for me. Well, I guess I'll drop 
the scale, use luxes and go with the PROCESSED data. My understanding is 
that the "thing" with the sensor is a wide-range for wavelengths, not 
the accuracy. So, maybe luxes are just good enough - and again, users 
needing something more accurate can utilize the raw intensity channels.

> 
> In the rare case where you do get SCALE and PROCESSED it's there to allow
> for changes in the underlying signal measurement that are eaten up in the
> computation needed to get to PROCESSED - that is they have no visible
> affect (beyond range changes etc).

Oh, Ok. So there is a valid case for setting both SCALE and PROCESSED. 
Then we can't add teh warning I assume :(


Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-06 11:13       ` Andy Shevchenko
@ 2023-03-13 11:31         ` Matti Vaittinen
  2023-03-13 14:39           ` Andy Shevchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-13 11:31 UTC (permalink / raw)
  To: Andy Shevchenko, Vaittinen, Matti
  Cc: Jonathan Cameron, Lars-Peter Clausen, Paul Gazzillo,
	Dmitry Osipenko, Shreeya Patel, Zhigang Shi, linux-kernel,
	linux-iio

On 3/6/23 13:13, Andy Shevchenko wrote:
> On Fri, Mar 03, 2023 at 07:54:22AM +0000, Vaittinen, Matti wrote:
>> On 3/2/23 17:05, Andy Shevchenko wrote:
>>> On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:
> 
> ...
> 
>>>> +{
>>>> +	int tmp = 1;
>>>> +
>>>> +	if (scale > max || !scale)
>>>> +		return -EINVAL;
>>>> +
>>>> +	if (U64_MAX - max < scale) {
>>>> +		/* Risk of overflow */
>>>> +		if (max - scale < scale)
>>>> +			return 1;
>>>
>>>> +		while (max - scale > scale * (u64) tmp)
>>>
>>> Space is not required after casting.
>>>
>>>> +			tmp++;
>>>> +
>>>> +		return tmp + 1;
>>>
>>> Wondering why you can't simplify this to
>>>
>>> 		max -= scale;
>>> 		tmp++;
>>>
>>
>> Sorry, but I don't follow. Can you please show me the complete
>> suggestion? Exactly what should be replaced by the:
> 
> I don't understand. Do you see the blank lines I specifically added to show
> the piece of the code I'm commenting on?

I saw. I, however, didn't see the point so I was thinking I 
misunderstood what you wanted to replace.

> For your convenience, the while loop and return statement may be replaced with
> two simple assignments.
> 
>>   > 		max -= scale;
>>   > 		tmp++;
> 

Ah, thanks. Yes. I think that would yield the same result. Not sure if 
it makes the logic more or less obvious, but it would kill one return 
path which indeed (in my opinion) could make it look prettier.

That would require a local variable or dropping the const from max 
though. Not sure it's worth that though.

> ...
> 
>>>> +	for (i = gts->num_itime - 2; i >= 0; i--)
>>>
>>> Yeah, if you put this into temporary, like
>>>
>>> 	i = gts->num_itime - 1;
>>>
>>> this becomes
>>>
>>> 	while (i--) {
>>>
>>
>> I prefer for(). It looks clearer to me...
> 
> It has too many characters to parse. That's why I prefer while.
> Are we going to have principle disagreement on this one?
> 

I think we have disagreed whether to prefer while() or for() also in the 
past. I am reluctant to switch for()s to while()s unless there are other 
obvious benefits. Especially I dislike the change if it involves -- or 
++ in the condition. They tend to make it much more difficult (for me) 
to see what the value is when comparing and what the value is after 
exiting the loop. Yet, in this particular case I saw the value of 
re-using the temporary value in memcpy() as you suggest below.

>>   > Note, you may re-use that i (maybe renamed to something better in the
>> memcpy()
>>   > above as well).
>>
>> ...but this makes sense so Ok.
> 
> ...
> 
>>>> +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)
>>>
>>> Much easier to read if you move this...
>>>
>>>> +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
>>>> +					&gts->avail_all_scales_table[i * 2],
>>>> +					&gts->avail_all_scales_table[i * 2 + 1]);
>>>
>>> ...here as
>>>
>>> 		if (ret)
>>> 			break;
>>
>> I think the !ret in loop condition is obvious. Adding break and brackets
>> would not improve this.
> 
> It moves it to the regular pattern. Yours is not so distributed in the kernel.

I believe we can find examples of both patterns in kernel. I don't think 
the "many people use different pattern" is a great reason to add break + 
brackets which (in my eyes) give no additional value to code I am 
planning to keep reading also in the future...

> ...
> 
>>>> +	kfree(all_gains);
>>>> +	if (ret && gts->avail_all_scales_table)
>>>> +		kfree(gts->avail_all_scales_table);
>>>> +
>>>> +	return ret;
>>>
>>> But Wouldn't be better to use goto labels?
>>
>> I don't see benefit in this case. Handling return value and goto would
>> require brackets. The current one is much simpler for me. I am just
>> considering dropping that 'else' which is not needed.
> 
> At least it would be consistent with the very same file style in another
> function. So, why there do you goto, and not here where it makes sense
> to me?
> 

Talking about the iio_gts_build_avail_scale_table()?

Difference is that the iio_gts_build_avail_scale_table() has multiple 
checks for "if this happens - error out". I think using goto has valid 
use-cases there.

To tell the truth - in the past I preferred "arrow code" pattern where I 
initialized return values to default errors and only entered deeper to 
nested code when checks were successful - changing error to succes only 
at the deepest nested level. That however seems to rub the wrong way 
most of the kernel developers. Hence I am adapted to use gotos for error 
handling when we have more than only a few failure points.

In any case, here we can just do with single if-else (and for) without 
even having the brackets. This is not a deeply nesting complex construct.

> ...
> 
>>>> +	while (i) {
>>>
>>> Instead of doing standard
>>>
>>> 	while (i--) {
>>>
>>>> +		/*
>>>> +		 * It does not matter if i'th alloc was not succesfull as
>>>> +		 * kfree(NULL) is safe.
>>>> +		 */
>>>
>>> You add this comment, ...
>>>
>>>> +		kfree(per_time_gains[i]);
>>>> +		kfree(per_time_scales[i]);
>>>
>>> ...an additional loop, ...
>>
>> The comment is there to explain what I think you missed. We have two
>> arrays there. We don't know whether the allocation of first one was
>> successful so we try freeing both.
>>
>>>
>>>> +
>>>> +		i--;
>>>
>>> ...and a line of code.
>>>
>>>> +	}
>>>
>>> Why?
>>
>> Because, as the comment says, it does not matter if allocation was
>> succesfull. kfree(NULL) is safe.
> 
> Wouldn't be better to avoid no-op kfree() by explicitly putting the call before
> hands?
> 
> 	kfree(...[i]);
> 	while (i--) {
> 		...
> 	}
> 
> ?
> 
> Much better to understand than what you wrote. I have to read comment on top of
> the code, with my proposal comment won't be needed.

True, I can add one kfree() before the loop. It still does not change 
the fact that we don't know if allocating the per_time_scales[i] was 
succesful - unless we also add another label there. Well, it is a few 
more LOC but avoids the kfree() and the comment so not really unbearable 
trade :)

> ...
> 
>>>> +	for (i = gts->num_itime - 1; i >= 0; i--) {
>>>
>>> 	i = gts->num_itime;
>>>
>>> 	while (i--) {
>>
>> I prefer for() when initializing a variable is needed. Furthermore,
>> having var-- or --var in a condition is less clear.
> 
> 	while (x--)
> 
> _is_ a pattern for cleaning up something. Your for-loop has a lot of additional
> (unnecessary) code lines that makes it harder to understand (by the people who
> are not familiar with the pattern).
> 

I do strongly disagree with you regarding

while (x--)

being simpler to understand than respective for. -- in a condition is 
terrible - and I rarely use it.

I know I used it in v3 for the

while (time_idx--) {

and I am regretting that already.

>>>> +	}
> 
> ...
> 
>>>> +void iio_gts_purge_avail_time_table(struct iio_gts *gts)
>>>> +{
>>>> +	if (gts->num_avail_time_tables) {
>>>
>>> 	if (!...)
>>> 		return;
>>
>> Does not improve this as we only have just one small conditional block
>> of code which we either execute or not. It is clear at a glance. Would
>> make sense if we had longer function.
> 
> I believe it makes sense to have like this even for this long function(s).

So, it seems like we do disagree with this too then. To me it is just 
obvious here to check if we have tables to free and free them.

> 
>>>> +		kfree(gts->avail_time_tables);
>>>> +		gts->avail_time_tables = NULL;
>>>> +		gts->num_avail_time_tables = 0;
>>>> +	}
>>>> +}
> 
> ...
> 
>>>> +			if (!diff) {
>>>
>>> Why not positive conditional?
>>
>> Because !diff is a special condition and we check explicitly for it.
> 
> And how my suggestion makes it different?

In example you gave we would be checking if the value is anything else 
but the specific value we are checking for. It is counter intuitive.

> (Note, it's easy to miss the ! in the conditionals, that's why positive ones
>   are preferable.)

Thank you for explaining me the rationale behind the "positive checks". 
I didn't know missing '!' was seen as a thing.

I still don't think being afraid of missing '!' is a good reason to 
switch to counter intuitive checks. A check "if (!foo)" is a pattern 
in-kernel if anything and in my opinion people really should be aware of it.

(I would much more say that having a constant value on left side of a 
"equality" check is beneficial as people do really occasionally miss one 
'=' when meaning '=='. Still, this is not strong enough reason to make 
counter-intuitive checks. In my books 'avoiding negative checks' is much 
less of a reason as people (in my experience) do not really miss the '!'.)

Best Regards
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-13 11:31         ` Matti Vaittinen
@ 2023-03-13 14:39           ` Andy Shevchenko
  2023-03-14 10:28             ` Matti Vaittinen
  0 siblings, 1 reply; 36+ messages in thread
From: Andy Shevchenko @ 2023-03-13 14:39 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Vaittinen, Matti, Jonathan Cameron, Lars-Peter Clausen,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On Mon, Mar 13, 2023 at 01:31:42PM +0200, Matti Vaittinen wrote:
> On 3/6/23 13:13, Andy Shevchenko wrote:
> > On Fri, Mar 03, 2023 at 07:54:22AM +0000, Vaittinen, Matti wrote:
> > > On 3/2/23 17:05, Andy Shevchenko wrote:
> > > > On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:

...

> > > > > +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)
> > > > 
> > > > Much easier to read if you move this...
> > > > 
> > > > > +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
> > > > > +					&gts->avail_all_scales_table[i * 2],
> > > > > +					&gts->avail_all_scales_table[i * 2 + 1]);
> > > > 
> > > > ...here as
> > > > 
> > > > 		if (ret)
> > > > 			break;
> > > 
> > > I think the !ret in loop condition is obvious. Adding break and brackets
> > > would not improve this.
> > 
> > It moves it to the regular pattern. Yours is not so distributed in the kernel.
> 
> I believe we can find examples of both patterns in kernel. I don't think the
> "many people use different pattern" is a great reason to add break +
> brackets which (in my eyes) give no additional value to code I am planning
> to keep reading also in the future...

The problem is that your pattern is not so standard (distributed) and hence
less maintainable.

...

> > > > > +			if (!diff) {
> > > > 
> > > > Why not positive conditional?
> > > 
> > > Because !diff is a special condition and we check explicitly for it.
> > 
> > And how my suggestion makes it different?
> 
> In example you gave we would be checking if the value is anything else but
> the specific value we are checking for. It is counter intuitive.
> 
> > (Note, it's easy to miss the ! in the conditionals, that's why positive ones
> >   are preferable.)
> 
> Thank you for explaining me the rationale behind the "positive checks". I
> didn't know missing '!' was seen as a thing.
> 
> I still don't think being afraid of missing '!' is a good reason to switch
> to counter intuitive checks. A check "if (!foo)" is a pattern in-kernel if
> anything and in my opinion people really should be aware of it.
> 
> (I would much more say that having a constant value on left side of a
> "equality" check is beneficial as people do really occasionally miss one '='
> when meaning '=='. Still, this is not strong enough reason to make
> counter-intuitive checks. In my books 'avoiding negative checks' is much
> less of a reason as people (in my experience) do not really miss the '!'.)

It's not a problem when it's a common pattern (like you mentioned
if (!foo) return -ENOMEM; or alike), but in your case it's not.
I would rather see if (diff == 0) which definitely shows the intention
and I wouldn't tell a word against it.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-12 15:36       ` Jonathan Cameron
  2023-03-13  9:39         ` Matti Vaittinen
@ 2023-03-14  9:39         ` Matti Vaittinen
  2023-03-18 16:57           ` Jonathan Cameron
  1 sibling, 1 reply; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-14  9:39 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On 3/12/23 17:36, Jonathan Cameron wrote:
> On Sun, 5 Mar 2023 14:22:51 +0200
> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> 
>> On 3/4/23 22:17, Jonathan Cameron wrote:
>>> On Thu, 2 Mar 2023 12:58:59 +0200
>>> Matti Vaittinen <mazziesaccount@gmail.com> wrote:

//snip

>>>    
>>>> +static const struct iio_chan_spec bu27034_channels[] = {
>>>> +	{
>>>> +		.type = IIO_LIGHT,
>>>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
>>>> +				      BIT(IIO_CHAN_INFO_SCALE),
>>>
>>> What is this scale for?
>>
>> The scale is to inform users that we return data using milli lux.
>>
>>> Given the channel is computed from various different inputs, is there a
>>> clear definition of how it is scaled?  What does a write to it mean?
>>
>> Nothing. writing anything else but milli lux scale fails with -EINVAL.
>>
>> I guess I am doing something in an unusual way here :) Do you have a
>> suggestion for me?
> 
> Return data in lux?  Or return it as INFO_RAW - thus making it clear
> that the reading is not in expected units and a conversion must be
> applied by userspace.  SCALE is not applied to PROCESSED by userspace.
> 

I just noticed a thing. I used the iio_generic_buffer to test the 
changes - and it got the channel values scaled to luxes even for the 
PROCESSED channel. So, it seems to me the iio_generic_buffer does apply 
the scale for PROCESSED channels too. I think that is slightly 
misleading. Oh, and this is not intended to be a complaint - just a 
report that there might be some room for an improvement :)

Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-13 14:39           ` Andy Shevchenko
@ 2023-03-14 10:28             ` Matti Vaittinen
  2023-03-14 11:31               ` Andy Shevchenko
  2023-03-18 16:49               ` Jonathan Cameron
  0 siblings, 2 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-14 10:28 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Vaittinen, Matti, Jonathan Cameron, Lars-Peter Clausen,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On 3/13/23 16:39, Andy Shevchenko wrote:
> On Mon, Mar 13, 2023 at 01:31:42PM +0200, Matti Vaittinen wrote:
>> On 3/6/23 13:13, Andy Shevchenko wrote:
>>> On Fri, Mar 03, 2023 at 07:54:22AM +0000, Vaittinen, Matti wrote:
>>>> On 3/2/23 17:05, Andy Shevchenko wrote:
>>>>> On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:
> 
> ...
> 
>>>>>> +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)
>>>>>
>>>>> Much easier to read if you move this...
>>>>>
>>>>>> +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
>>>>>> +					&gts->avail_all_scales_table[i * 2],
>>>>>> +					&gts->avail_all_scales_table[i * 2 + 1]);
>>>>>
>>>>> ...here as
>>>>>
>>>>> 		if (ret)
>>>>> 			break;
>>>>
>>>> I think the !ret in loop condition is obvious. Adding break and brackets
>>>> would not improve this.
>>>
>>> It moves it to the regular pattern. Yours is not so distributed in the kernel.
>>
>> I believe we can find examples of both patterns in kernel. I don't think the
>> "many people use different pattern" is a great reason to add break +
>> brackets which (in my eyes) give no additional value to code I am planning
>> to keep reading also in the future...
> 
> The problem is that your pattern is not so standard (distributed) and hence
> less maintainable.

I am sorry but I can't really agree with you on this one. For me adding 
the break and brackets would just complicate the flow and thus decrease 
the maintainability.

> ...
> 
>>>>>> +			if (!diff) {
>>>>>
>>>>> Why not positive conditional?
>>>>
>>>> Because !diff is a special condition and we check explicitly for it.
>>>
>>> And how my suggestion makes it different?
>>
>> In example you gave we would be checking if the value is anything else but
>> the specific value we are checking for. It is counter intuitive.
>>
>>> (Note, it's easy to miss the ! in the conditionals, that's why positive ones
>>>    are preferable.)
>>
>> Thank you for explaining me the rationale behind the "positive checks". I
>> didn't know missing '!' was seen as a thing.
>>
>> I still don't think being afraid of missing '!' is a good reason to switch
>> to counter intuitive checks. A check "if (!foo)" is a pattern in-kernel if
>> anything and in my opinion people really should be aware of it.
>>
>> (I would much more say that having a constant value on left side of a
>> "equality" check is beneficial as people do really occasionally miss one '='
>> when meaning '=='. Still, this is not strong enough reason to make
>> counter-intuitive checks. In my books 'avoiding negative checks' is much
>> less of a reason as people (in my experience) do not really miss the '!'.)
> 
> It's not a problem when it's a common pattern (like you mentioned
> if (!foo) return -ENOMEM; or alike), but in your case it's not.

I think we can find plenty of cases where the if (!foo) is used also for 
other type of checks. To me the argument about people easily missing the 
! in if () just do not sound reasonable.

> I would rather see if (diff == 0) which definitely shows the intention
> and I wouldn't tell a word against it.

I think this depends much of the corner of the kernel you have been 
working with. As far as I remember, in some parts the kernel the check
(foo == 0) was actually discouraged, and check (!foo) was preferred.

Personally I like !foo much more - but I can tolerate the (foo == 0) in 
cases where the purpose is to really see if some measure equals to zero.

Other uses where I definitely don't want to use "== 0" are for example 
checking if a flag is clear, pointer is NULL or "magic value" is zero.

In this case we are checking for a magic value. Having this check 
written as: (diff == 0), would actually falsely suggest me we are 
checking for the difference of gains being zero. That would really be a 
clever obfuscation and I am certain the code readers would fall on that 
trap quite easily.

Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-14 10:28             ` Matti Vaittinen
@ 2023-03-14 11:31               ` Andy Shevchenko
  2023-03-14 11:36                 ` Andy Shevchenko
  2023-03-15  7:20                 ` Vaittinen, Matti
  2023-03-18 16:49               ` Jonathan Cameron
  1 sibling, 2 replies; 36+ messages in thread
From: Andy Shevchenko @ 2023-03-14 11:31 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Vaittinen, Matti, Jonathan Cameron, Lars-Peter Clausen,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On Tue, Mar 14, 2023 at 12:28:43PM +0200, Matti Vaittinen wrote:
> On 3/13/23 16:39, Andy Shevchenko wrote:
> > On Mon, Mar 13, 2023 at 01:31:42PM +0200, Matti Vaittinen wrote:
> > > On 3/6/23 13:13, Andy Shevchenko wrote:
> > > > On Fri, Mar 03, 2023 at 07:54:22AM +0000, Vaittinen, Matti wrote:
> > > > > On 3/2/23 17:05, Andy Shevchenko wrote:
> > > > > > On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:

...

> > > > > > > +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)
> > > > > > 
> > > > > > Much easier to read if you move this...
> > > > > > 
> > > > > > > +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
> > > > > > > +					&gts->avail_all_scales_table[i * 2],
> > > > > > > +					&gts->avail_all_scales_table[i * 2 + 1]);
> > > > > > 
> > > > > > ...here as
> > > > > > 
> > > > > > 		if (ret)
> > > > > > 			break;
> > > > > 
> > > > > I think the !ret in loop condition is obvious. Adding break and brackets
> > > > > would not improve this.
> > > > 
> > > > It moves it to the regular pattern. Yours is not so distributed in the kernel.
> > > 
> > > I believe we can find examples of both patterns in kernel. I don't think the
> > > "many people use different pattern" is a great reason to add break +
> > > brackets which (in my eyes) give no additional value to code I am planning
> > > to keep reading also in the future...
> > 
> > The problem is that your pattern is not so standard (distributed) and hence
> > less maintainable.
> 
> I am sorry but I can't really agree with you on this one. For me adding the
> break and brackets would just complicate the flow and thus decrease the
> maintainability.

So, we may start to have a "fundamental disagreements between Matti and Andy on
the code style in the Linux kernel" document that we won't clash on this again.
At least the amount of these disagreements seems not decreasing in time.

...

> > > > > > > +			if (!diff) {
> > > > > > 
> > > > > > Why not positive conditional?
> > > > > 
> > > > > Because !diff is a special condition and we check explicitly for it.
> > > > 
> > > > And how my suggestion makes it different?
> > > 
> > > In example you gave we would be checking if the value is anything else but
> > > the specific value we are checking for. It is counter intuitive.
> > > 
> > > > (Note, it's easy to miss the ! in the conditionals, that's why positive ones
> > > >    are preferable.)
> > > 
> > > Thank you for explaining me the rationale behind the "positive checks". I
> > > didn't know missing '!' was seen as a thing.
> > > I still don't think being afraid of missing '!' is a good reason to switch
> > > to counter intuitive checks. A check "if (!foo)" is a pattern in-kernel if
> > > anything and in my opinion people really should be aware of it.
> > > 
> > > (I would much more say that having a constant value on left side of a
> > > "equality" check is beneficial as people do really occasionally miss one '='
> > > when meaning '=='. Still, this is not strong enough reason to make
> > > counter-intuitive checks. In my books 'avoiding negative checks' is much
> > > less of a reason as people (in my experience) do not really miss the '!'.)
> > 
> > It's not a problem when it's a common pattern (like you mentioned
> > if (!foo) return -ENOMEM; or alike), but in your case it's not.
> 
> I think we can find plenty of cases where the if (!foo) is used also for

Pleading to the quantity and not quality is not an argument, right?

> other type of checks. To me the argument about people easily missing the !
> in if () just do not sound reasonable.

You may theoretically discuss this, I'm telling from my review background
and real cases.

> > I would rather see if (diff == 0) which definitely shows the intention
> > and I wouldn't tell a word against it.
> 
> I think this depends much of the corner of the kernel you have been working
> with. As far as I remember, in some parts the kernel the check
> (foo == 0) was actually discouraged, and check (!foo) was preferred.

Don't you use your common sense?

> Personally I like !foo much more - but I can tolerate the (foo == 0) in
> cases where the purpose is to really see if some measure equals to zero.
> 
> Other uses where I definitely don't want to use "== 0" are for example
> checking if a flag is clear, pointer is NULL or "magic value" is zero.
> 
> In this case we are checking for a magic value. Having this check written
> as: (diff == 0), would actually falsely suggest me we are checking for the
> difference of gains being zero. That would really be a clever obfuscation
> and I am certain the code readers would fall on that trap quite easily.

Testing with !diff sounds like it's a boolean kind and makes a false
impression that all other values are almost the same meaning which is
not the case. Am I right? That's why diff == 0 shows the exact intention
here "I would like to check if diff is 0 because this is *special case*".

Making !diff creates less visibility on this.

Result: Fundamental disagreement between us.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-14 11:31               ` Andy Shevchenko
@ 2023-03-14 11:36                 ` Andy Shevchenko
  2023-03-15  7:20                 ` Vaittinen, Matti
  1 sibling, 0 replies; 36+ messages in thread
From: Andy Shevchenko @ 2023-03-14 11:36 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Vaittinen, Matti, Jonathan Cameron, Lars-Peter Clausen,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On Tue, Mar 14, 2023 at 01:31:06PM +0200, Andy Shevchenko wrote:
> On Tue, Mar 14, 2023 at 12:28:43PM +0200, Matti Vaittinen wrote:
> > On 3/13/23 16:39, Andy Shevchenko wrote:
> > > On Mon, Mar 13, 2023 at 01:31:42PM +0200, Matti Vaittinen wrote:
> > > > On 3/6/23 13:13, Andy Shevchenko wrote:
> > > > > On Fri, Mar 03, 2023 at 07:54:22AM +0000, Vaittinen, Matti wrote:
> > > > > > On 3/2/23 17:05, Andy Shevchenko wrote:
> > > > > > > On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:

...

> > > > > > > > +			if (!diff) {
> > > > > > > 
> > > > > > > Why not positive conditional?
> > > > > > 
> > > > > > Because !diff is a special condition and we check explicitly for it.
> > > > > 
> > > > > And how my suggestion makes it different?
> > > > 
> > > > In example you gave we would be checking if the value is anything else but
> > > > the specific value we are checking for. It is counter intuitive.
> > > > 
> > > > > (Note, it's easy to miss the ! in the conditionals, that's why positive ones
> > > > >    are preferable.)
> > > > 
> > > > Thank you for explaining me the rationale behind the "positive checks". I
> > > > didn't know missing '!' was seen as a thing.
> > > > I still don't think being afraid of missing '!' is a good reason to switch
> > > > to counter intuitive checks. A check "if (!foo)" is a pattern in-kernel if
> > > > anything and in my opinion people really should be aware of it.
> > > > 
> > > > (I would much more say that having a constant value on left side of a
> > > > "equality" check is beneficial as people do really occasionally miss one '='
> > > > when meaning '=='. Still, this is not strong enough reason to make
> > > > counter-intuitive checks. In my books 'avoiding negative checks' is much
> > > > less of a reason as people (in my experience) do not really miss the '!'.)
> > > 
> > > It's not a problem when it's a common pattern (like you mentioned
> > > if (!foo) return -ENOMEM; or alike), but in your case it's not.
> > 
> > I think we can find plenty of cases where the if (!foo) is used also for
> 
> Pleading to the quantity and not quality is not an argument, right?
> 
> > other type of checks. To me the argument about people easily missing the !
> > in if () just do not sound reasonable.
> 
> You may theoretically discuss this, I'm telling from my review background
> and real cases.
> 
> > > I would rather see if (diff == 0) which definitely shows the intention
> > > and I wouldn't tell a word against it.
> > 
> > I think this depends much of the corner of the kernel you have been working
> > with. As far as I remember, in some parts the kernel the check
> > (foo == 0) was actually discouraged, and check (!foo) was preferred.
> 
> Don't you use your common sense?
> 
> > Personally I like !foo much more - but I can tolerate the (foo == 0) in
> > cases where the purpose is to really see if some measure equals to zero.
> > 
> > Other uses where I definitely don't want to use "== 0" are for example
> > checking if a flag is clear, pointer is NULL or "magic value" is zero.
> > 
> > In this case we are checking for a magic value. Having this check written
> > as: (diff == 0), would actually falsely suggest me we are checking for the
> > difference of gains being zero. That would really be a clever obfuscation
> > and I am certain the code readers would fall on that trap quite easily.
> 
> Testing with !diff sounds like it's a boolean kind and makes a false
> impression that all other values are almost the same meaning which is
> not the case. Am I right? That's why diff == 0 shows the exact intention
> here "I would like to check if diff is 0 because this is *special case*".
> 
> Making !diff creates less visibility on this.
> 
> Result: Fundamental disagreement between us.

JFYI:
$ git grep -n 'diff.* == 0[^0-9]' -- drivers/ | wc -l
45

(It happens to have same variable name, but you can imagine that there are
 much more cases with different variable names in use)

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-14 11:31               ` Andy Shevchenko
  2023-03-14 11:36                 ` Andy Shevchenko
@ 2023-03-15  7:20                 ` Vaittinen, Matti
  1 sibling, 0 replies; 36+ messages in thread
From: Vaittinen, Matti @ 2023-03-15  7:20 UTC (permalink / raw)
  To: Andy Shevchenko, Matti Vaittinen
  Cc: Jonathan Cameron, Lars-Peter Clausen, Paul Gazzillo,
	Dmitry Osipenko, Shreeya Patel, Zhigang Shi, linux-kernel,
	linux-iio

On 3/14/23 13:31, Andy Shevchenko wrote:
> On Tue, Mar 14, 2023 at 12:28:43PM +0200, Matti Vaittinen wrote:
>> On 3/13/23 16:39, Andy Shevchenko wrote:
>>> On Mon, Mar 13, 2023 at 01:31:42PM +0200, Matti Vaittinen wrote:
>>>> On 3/6/23 13:13, Andy Shevchenko wrote:
>>>>> On Fri, Mar 03, 2023 at 07:54:22AM +0000, Vaittinen, Matti wrote:
>>>>>> On 3/2/23 17:05, Andy Shevchenko wrote:
>>>>>>> On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:
> 
> ...
> 
>>>>>>>> +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)
>>>>>>>
>>>>>>> Much easier to read if you move this...
>>>>>>>
>>>>>>>> +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
>>>>>>>> +					&gts->avail_all_scales_table[i * 2],
>>>>>>>> +					&gts->avail_all_scales_table[i * 2 + 1]);
>>>>>>>
>>>>>>> ...here as
>>>>>>>
>>>>>>> 		if (ret)
>>>>>>> 			break;
>>>>>>
>>>>>> I think the !ret in loop condition is obvious. Adding break and brackets
>>>>>> would not improve this.
>>>>>
>>>>> It moves it to the regular pattern. Yours is not so distributed in the kernel.
>>>>
>>>> I believe we can find examples of both patterns in kernel. I don't think the
>>>> "many people use different pattern" is a great reason to add break +
>>>> brackets which (in my eyes) give no additional value to code I am planning
>>>> to keep reading also in the future...
>>>
>>> The problem is that your pattern is not so standard (distributed) and hence
>>> less maintainable.
>>
>> I am sorry but I can't really agree with you on this one. For me adding the
>> break and brackets would just complicate the flow and thus decrease the
>> maintainability.
> 
> So, we may start to have a "fundamental disagreements between Matti and Andy on
> the code style in the Linux kernel" document that we won't clash on this again.
> At least the amount of these disagreements seems not decreasing in time.
> 

:)

> ...
> 
>>>>>>>> +			if (!diff) {
>>>>>>>
>>>>>>> Why not positive conditional?
>>>>>>
>>>>>> Because !diff is a special condition and we check explicitly for it.
>>>>>
>>>>> And how my suggestion makes it different?
>>>>
>>>> In example you gave we would be checking if the value is anything else but
>>>> the specific value we are checking for. It is counter intuitive.
>>>>
>>>>> (Note, it's easy to miss the ! in the conditionals, that's why positive ones
>>>>>     are preferable.)
>>>>
>>>> Thank you for explaining me the rationale behind the "positive checks". I
>>>> didn't know missing '!' was seen as a thing.
>>>> I still don't think being afraid of missing '!' is a good reason to switch
>>>> to counter intuitive checks. A check "if (!foo)" is a pattern in-kernel if
>>>> anything and in my opinion people really should be aware of it.
>>>>
>>>> (I would much more say that having a constant value on left side of a
>>>> "equality" check is beneficial as people do really occasionally miss one '='
>>>> when meaning '=='. Still, this is not strong enough reason to make
>>>> counter-intuitive checks. In my books 'avoiding negative checks' is much
>>>> less of a reason as people (in my experience) do not really miss the '!'.)
>>>
>>> It's not a problem when it's a common pattern (like you mentioned
>>> if (!foo) return -ENOMEM; or alike), but in your case it's not.
>>
>> I think we can find plenty of cases where the if (!foo) is used also for
> 
> Pleading to the quantity and not quality is not an argument, right?

Eh. I think it has been you who has quite often used that as an 
argument, right?

>> other type of checks. To me the argument about people easily missing the !
>> in if () just do not sound reasonable.
> 
> You may theoretically discuss this, I'm telling from my review background
> and real cases.

I think we all have been reading the code for quite a few years. Thus, 
also my statements are done based on real cases, or lack of them 
thereof. Hence a "Shut up, I have the reviewing experience" is not such 
a strong argument in my books ;)

>>> I would rather see if (diff == 0) which definitely shows the intention
>>> and I wouldn't tell a word against it.
>>
>> I think this depends much of the corner of the kernel you have been working
>> with. As far as I remember, in some parts the kernel the check
>> (foo == 0) was actually discouraged, and check (!foo) was preferred.
> 
> Don't you use your common sense?

I try. I believe this is why I so often clash with people who are just 
more or less blindly following their preferred idioms.

>> Personally I like !foo much more - but I can tolerate the (foo == 0) in
>> cases where the purpose is to really see if some measure equals to zero.
>>
>> Other uses where I definitely don't want to use "== 0" are for example
>> checking if a flag is clear, pointer is NULL or "magic value" is zero.
>>
>> In this case we are checking for a magic value. Having this check written
>> as: (diff == 0), would actually falsely suggest me we are checking for the
>> difference of gains being zero. That would really be a clever obfuscation
>> and I am certain the code readers would fall on that trap quite easily.
> 
> Testing with !diff sounds like it's a boolean kind and makes a false
> impression that all other values are almost the same meaning which is
> not the case. Am I right? That's why diff == 0 shows the exact intention
> here "I would like to check if diff is 0 because this is *special case*".

To me the diff == 0 would definitely read "difference of gains is zero". 
And this is NOT what this check is here for. And no, using !var is _not_ 
a sign of a boolean for me. It might be if this pattern was not used 
throughout the kernel for checking if something is _not set_, 
successful, NULL and yes, often also to see it something is non zero.

> Making !diff creates less visibility on this.
> 
> Result: Fundamental disagreement between us.

We agree on that :)

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

* Re: [PATCH v2 2/6] iio: light: Add gain-time-scale helpers
  2023-03-14 10:28             ` Matti Vaittinen
  2023-03-14 11:31               ` Andy Shevchenko
@ 2023-03-18 16:49               ` Jonathan Cameron
  1 sibling, 0 replies; 36+ messages in thread
From: Jonathan Cameron @ 2023-03-18 16:49 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Andy Shevchenko, Vaittinen, Matti, Lars-Peter Clausen,
	Paul Gazzillo, Dmitry Osipenko, Shreeya Patel, Zhigang Shi,
	linux-kernel, linux-iio

On Tue, 14 Mar 2023 12:28:43 +0200
Matti Vaittinen <mazziesaccount@gmail.com> wrote:

> On 3/13/23 16:39, Andy Shevchenko wrote:
> > On Mon, Mar 13, 2023 at 01:31:42PM +0200, Matti Vaittinen wrote:  
> >> On 3/6/23 13:13, Andy Shevchenko wrote:  
> >>> On Fri, Mar 03, 2023 at 07:54:22AM +0000, Vaittinen, Matti wrote:  
> >>>> On 3/2/23 17:05, Andy Shevchenko wrote:  
> >>>>> On Thu, Mar 02, 2023 at 12:57:54PM +0200, Matti Vaittinen wrote:  
> > 
> > ...
> >   
> >>>>>> +		for (i = 0; !ret && i < gts->num_avail_all_scales; i++)  
> >>>>>
> >>>>> Much easier to read if you move this...
> >>>>>  
> >>>>>> +			ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
> >>>>>> +					&gts->avail_all_scales_table[i * 2],
> >>>>>> +					&gts->avail_all_scales_table[i * 2 + 1]);  
> >>>>>
> >>>>> ...here as
> >>>>>
> >>>>> 		if (ret)
> >>>>> 			break;  
> >>>>
> >>>> I think the !ret in loop condition is obvious. Adding break and brackets
> >>>> would not improve this.  
> >>>
> >>> It moves it to the regular pattern. Yours is not so distributed in the kernel.  
> >>
> >> I believe we can find examples of both patterns in kernel. I don't think the
> >> "many people use different pattern" is a great reason to add break +
> >> brackets which (in my eyes) give no additional value to code I am planning
> >> to keep reading also in the future...  
> > 
> > The problem is that your pattern is not so standard (distributed) and hence
> > less maintainable.  
> 
> I am sorry but I can't really agree with you on this one. For me adding 
> the break and brackets would just complicate the flow and thus decrease 
> the maintainability.

I'm with the if (ret) break;
school of thought on this one.  Never like for loops with complex conditions,
I guess because I've trained my eyes to ignore them ;)



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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-13  9:39         ` Matti Vaittinen
@ 2023-03-18 16:54           ` Jonathan Cameron
  2023-03-19 15:59             ` Matti Vaittinen
  0 siblings, 1 reply; 36+ messages in thread
From: Jonathan Cameron @ 2023-03-18 16:54 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On Mon, 13 Mar 2023 11:39:06 +0200
Matti Vaittinen <mazziesaccount@gmail.com> wrote:

> On 3/12/23 17:36, Jonathan Cameron wrote:
> > On Sun, 5 Mar 2023 14:22:51 +0200
> > Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> >   
> >> On 3/4/23 22:17, Jonathan Cameron wrote:  
> >>> On Thu, 2 Mar 2023 12:58:59 +0200
> >>> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> >>>      
> 
> // snip
> 
> >>>> +static const struct iio_chan_spec bu27034_channels[] = {
> >>>> +	{
> >>>> +		.type = IIO_LIGHT,
> >>>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
> >>>> +				      BIT(IIO_CHAN_INFO_SCALE),  
> >>>
> >>> What is this scale for?  
> >>
> >> The scale is to inform users that we return data using milli lux.
> >>  
> >>> Given the channel is computed from various different inputs, is there a
> >>> clear definition of how it is scaled?  What does a write to it mean?  
> >>
> >> Nothing. writing anything else but milli lux scale fails with -EINVAL.
> >>
> >> I guess I am doing something in an unusual way here :) Do you have a
> >> suggestion for me?  
> > 
> > Return data in lux?  
> 
> That's what I did originally have. But then I noticed we can get 
> slightly better accuracy than that. Hence I switched to mLux and added 
> the scale.
> 
> >  Or return it as INFO_RAW - thus making it clear
> > that the reading is not in expected units and a conversion must be
> > applied by userspace.  SCALE is not applied to PROCESSED by userspace.  
> 
> Ah. This makes sense then. Maybe it would be worth adding a warning to 
> IIO-core if drivers set both the SCALE and PROCESSED info bits?

Hmm. I'm not sure that we don't have valid users of it even if they
are unusual.  We also have some historical messes that do RAW + SCALE +
PROCESSED so we can't really have a warning on it.

Warning generally is that the test tools that come with the kernel
will give you the wrong reading. :)

> 
> So, I need to select between the simplicity or better accuracy here? :/ 
> I really hate ending up making choices like this without knowing all the 
> real use-cases :( And it happens a lot for me. Well, I guess I'll drop 
> the scale, use luxes and go with the PROCESSED data. My understanding is 
> that the "thing" with the sensor is a wide-range for wavelengths, not 
> the accuracy. So, maybe luxes are just good enough - and again, users 
> needing something more accurate can utilize the raw intensity channels.

Hmm. For the sysfs case you could use VAL_INT_PLUS_MICRO but that doesn't
then work well with the buffered path.

It is perfectly valid to just have this as _RAW and keep your _SCALE so
that's probably the best option
_RAW doesn't have to mean totally raw, it just means userspace is expected
to applying a linear conversion to get a reading in the 'base' units for the channel.


> 
> > 
> > In the rare case where you do get SCALE and PROCESSED it's there to allow
> > for changes in the underlying signal measurement that are eaten up in the
> > computation needed to get to PROCESSED - that is they have no visible
> > affect (beyond range changes etc).  
> 
> Oh, Ok. So there is a valid case for setting both SCALE and PROCESSED. 
> Then we can't add teh warning I assume :(

They are obscure but IIRC there are some.

Jonathan

> 
> 
> Yours,
> 	-- Matti
> 


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-14  9:39         ` Matti Vaittinen
@ 2023-03-18 16:57           ` Jonathan Cameron
  0 siblings, 0 replies; 36+ messages in thread
From: Jonathan Cameron @ 2023-03-18 16:57 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On Tue, 14 Mar 2023 11:39:22 +0200
Matti Vaittinen <mazziesaccount@gmail.com> wrote:

> On 3/12/23 17:36, Jonathan Cameron wrote:
> > On Sun, 5 Mar 2023 14:22:51 +0200
> > Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> >   
> >> On 3/4/23 22:17, Jonathan Cameron wrote:  
> >>> On Thu, 2 Mar 2023 12:58:59 +0200
> >>> Matti Vaittinen <mazziesaccount@gmail.com> wrote:  
> 
> //snip
> 
> >>>      
> >>>> +static const struct iio_chan_spec bu27034_channels[] = {
> >>>> +	{
> >>>> +		.type = IIO_LIGHT,
> >>>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
> >>>> +				      BIT(IIO_CHAN_INFO_SCALE),  
> >>>
> >>> What is this scale for?  
> >>
> >> The scale is to inform users that we return data using milli lux.
> >>  
> >>> Given the channel is computed from various different inputs, is there a
> >>> clear definition of how it is scaled?  What does a write to it mean?  
> >>
> >> Nothing. writing anything else but milli lux scale fails with -EINVAL.
> >>
> >> I guess I am doing something in an unusual way here :) Do you have a
> >> suggestion for me?  
> > 
> > Return data in lux?  Or return it as INFO_RAW - thus making it clear
> > that the reading is not in expected units and a conversion must be
> > applied by userspace.  SCALE is not applied to PROCESSED by userspace.
> >   
> 
> I just noticed a thing. I used the iio_generic_buffer to test the 
> changes - and it got the channel values scaled to luxes even for the 
> PROCESSED channel. So, it seems to me the iio_generic_buffer does apply 
> the scale for PROCESSED channels too. I think that is slightly 
> misleading. Oh, and this is not intended to be a complaint - just a 
> report that there might be some room for an improvement :)

Ah. Looks like the code doesn't have any check on whether the sysfs
read is _raw or _processed which is kind of understandable.

This may be the first case where those have both applied on a channel
that is available via buffered route.

Given processed channels are rarely IIO_VAL_INT which is kind of necessary
to poke it in the buffered route, that may well be true.

Jonathan

> 
> Yours,
> 	-- Matti
> 


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

* Re: [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor
  2023-03-18 16:54           ` Jonathan Cameron
@ 2023-03-19 15:59             ` Matti Vaittinen
  0 siblings, 0 replies; 36+ messages in thread
From: Matti Vaittinen @ 2023-03-19 15:59 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Matti Vaittinen, Lars-Peter Clausen, Andy Shevchenko,
	Paul Gazzillo, Zhigang Shi, Shreeya Patel, Dmitry Osipenko,
	linux-kernel, linux-iio

On 3/18/23 18:54, Jonathan Cameron wrote:
> On Mon, 13 Mar 2023 11:39:06 +0200
> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
> 
>> On 3/12/23 17:36, Jonathan Cameron wrote:
>>> On Sun, 5 Mar 2023 14:22:51 +0200
>>> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
>>>    
>>>> On 3/4/23 22:17, Jonathan Cameron wrote:
>>>>> On Thu, 2 Mar 2023 12:58:59 +0200
>>>>> Matti Vaittinen <mazziesaccount@gmail.com> wrote:
>>>>>       
>>
>> // snip
>>
>>>>>> +static const struct iio_chan_spec bu27034_channels[] = {
>>>>>> +	{
>>>>>> +		.type = IIO_LIGHT,
>>>>>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
>>>>>> +				      BIT(IIO_CHAN_INFO_SCALE),
>>>>>
>>>>> What is this scale for?
>>>>
>>>> The scale is to inform users that we return data using milli lux.
>>>>   
>>>>> Given the channel is computed from various different inputs, is there a
>>>>> clear definition of how it is scaled?  What does a write to it mean?
>>>>
>>>> Nothing. writing anything else but milli lux scale fails with -EINVAL.
>>>>
>>>> I guess I am doing something in an unusual way here :) Do you have a
>>>> suggestion for me?
>>>
>>> Return data in lux?
>>
>> That's what I did originally have. But then I noticed we can get
>> slightly better accuracy than that. Hence I switched to mLux and added
>> the scale.
>>
>>>   Or return it as INFO_RAW - thus making it clear
>>> that the reading is not in expected units and a conversion must be
>>> applied by userspace.  SCALE is not applied to PROCESSED by userspace.
>>
>> Ah. This makes sense then. Maybe it would be worth adding a warning to
>> IIO-core if drivers set both the SCALE and PROCESSED info bits?
> 
> Hmm. I'm not sure that we don't have valid users of it even if they
> are unusual.  We also have some historical messes that do RAW + SCALE +
> PROCESSED so we can't really have a warning on it.
> 
> Warning generally is that the test tools that come with the kernel
> will give you the wrong reading. :)
> 
>>
>> So, I need to select between the simplicity or better accuracy here? :/
>> I really hate ending up making choices like this without knowing all the
>> real use-cases :( And it happens a lot for me. Well, I guess I'll drop
>> the scale, use luxes and go with the PROCESSED data. My understanding is
>> that the "thing" with the sensor is a wide-range for wavelengths, not
>> the accuracy. So, maybe luxes are just good enough - and again, users
>> needing something more accurate can utilize the raw intensity channels.
> 
> Hmm. For the sysfs case you could use VAL_INT_PLUS_MICRO but that doesn't
> then work well with the buffered path.
> 
> It is perfectly valid to just have this as _RAW and keep your _SCALE so
> that's probably the best option
> _RAW doesn't have to mean totally raw, it just means userspace is expected
> to applying a linear conversion to get a reading in the 'base' units for the channel.

Thanks for the insight!

I'll return the scale and switch to RAW for v5 :)

Yours,
	-- Matti

-- 
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


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

end of thread, other threads:[~2023-03-19 15:59 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-02 10:57 [PATCH v2 0/6] Support ROHM BU27034 ALS sensor Matti Vaittinen
2023-03-02 10:57 ` [PATCH v2 1/6] dt-bindings: iio: light: Support ROHM BU27034 Matti Vaittinen
2023-03-02 10:57 ` [PATCH v2 2/6] iio: light: Add gain-time-scale helpers Matti Vaittinen
2023-03-02 15:05   ` Andy Shevchenko
2023-03-03  7:54     ` Vaittinen, Matti
2023-03-06 11:13       ` Andy Shevchenko
2023-03-13 11:31         ` Matti Vaittinen
2023-03-13 14:39           ` Andy Shevchenko
2023-03-14 10:28             ` Matti Vaittinen
2023-03-14 11:31               ` Andy Shevchenko
2023-03-14 11:36                 ` Andy Shevchenko
2023-03-15  7:20                 ` Vaittinen, Matti
2023-03-18 16:49               ` Jonathan Cameron
2023-03-04 18:35   ` Jonathan Cameron
2023-03-04 19:42     ` Matti Vaittinen
2023-03-06 11:15       ` Andy Shevchenko
2023-03-02 10:58 ` [PATCH v2 3/6] iio: test: test " Matti Vaittinen
2023-03-02 10:58 ` [PATCH v2 4/6] MAINTAINERS: Add IIO " Matti Vaittinen
2023-03-02 10:58 ` [PATCH v2 5/6] iio: light: ROHM BU27034 Ambient Light Sensor Matti Vaittinen
2023-03-02 14:17   ` Matti Vaittinen
2023-03-02 15:34   ` Andy Shevchenko
2023-03-03  9:17     ` Matti Vaittinen
2023-03-04 19:02       ` Jonathan Cameron
2023-03-04 20:28         ` Matti Vaittinen
2023-03-04 20:17   ` Jonathan Cameron
2023-03-05 12:22     ` Matti Vaittinen
2023-03-12 15:36       ` Jonathan Cameron
2023-03-13  9:39         ` Matti Vaittinen
2023-03-18 16:54           ` Jonathan Cameron
2023-03-19 15:59             ` Matti Vaittinen
2023-03-14  9:39         ` Matti Vaittinen
2023-03-18 16:57           ` Jonathan Cameron
2023-03-05 13:10     ` Matti Vaittinen
2023-03-06 11:21       ` Andy Shevchenko
2023-03-13  8:54         ` Matti Vaittinen
2023-03-02 10:59 ` [PATCH v2 6/6] MAINTAINERS: Add ROHM BU27034 Matti Vaittinen

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