Linux IIO development
 help / color / mirror / Atom feed
* [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999
@ 2026-05-13  8:09 Jose A. Perez de Azpillaga
  2026-05-13  8:10 ` [RFC PATCH v2 1/2] dt-bindings: iio: light: add " Jose A. Perez de Azpillaga
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Jose A. Perez de Azpillaga @ 2026-05-13  8:09 UTC (permalink / raw)
  To: linux-iio; +Cc: Jonathan Cameron, David Lechner, Nuno Sá

Add IIO driver and DT binding for the Broadcom APDS9999 ambient light
and proximity sensor. The green channel uses optical coating technology
to approximate the human eye spectral response for ALS/lux measurements.
IIO_INTENSITY channels are also provided for red, green, blue, and
clear/IR so userspace can compute its own weighted lux.

Questions for reviewers:
  1. Is the green channel as an ALS/lux approximation acceptable given
     the datasheet's claim of optical coating to emulate V(lambda)?
  2. Is the IIO_INTENSITY channel layout correct? (red, green, blue,
     clear with modifiers, no scale)
  3. Should the binding be merged with avago,apds9300.yaml or kept
     separate given the extra vled-supply?

v2:
  - Added IIO_INTENSITY channels for R, G, B, clear
  - Switched to guard(mutex)(), devm_mutex_init(), dev_err_probe()
  - Replaced remove() with devm_add_action_or_reset()
  - Don't fail probe on PART_ID mismatch (fallback compatibles)
  - Replaced manual shifts with FIELD_PREP() / GENMASK
  - Used get_unaligned_le24(), sizeof(buf), NSEC_PER_SEC, fsleep()
  - Named gain register values, explicit resolution enum values
  - Removed section comments, grouped bit defines under registers
  - Squashed Kconfig/Makefile into driver patch
  - Split MAINTAINERS entry across patches
  - DT binding: added vled-supply, interrupts, required vdd-supply
  - DT binding: fixed title, added blank lines, full description
  - Dropped "DT binding" duplication from binding commit subject

Link to v1: https://lore.kernel.org/linux-iio/cover.1778491503.git.azpijr@gmail.com/

Jose A. Perez de Azpillaga (2):
  dt-bindings: iio: light: add Broadcom APDS9999
  iio: light: add support for APDS9999 sensor

 .../bindings/iio/light/brcm,apds9999.yaml     |  53 +++
 MAINTAINERS                                   |   7 +
 drivers/iio/light/Kconfig                     |  10 +
 drivers/iio/light/Makefile                    |   1 +
 drivers/iio/light/apds9999.c                  | 344 ++++++++++++++++++
 5 files changed, 415 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
 create mode 100644 drivers/iio/light/apds9999.c


-- 
jose a. p-a

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

* [RFC PATCH v2 1/2] dt-bindings: iio: light: add Broadcom APDS9999
  2026-05-13  8:09 [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999 Jose A. Perez de Azpillaga
@ 2026-05-13  8:10 ` Jose A. Perez de Azpillaga
  2026-05-16 14:47   ` Jonathan Cameron
  2026-05-13  8:10 ` [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor Jose A. Perez de Azpillaga
  2026-05-16 14:44 ` [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999 Jonathan Cameron
  2 siblings, 1 reply; 10+ messages in thread
From: Jose A. Perez de Azpillaga @ 2026-05-13  8:10 UTC (permalink / raw)
  To: linux-iio; +Cc: Jonathan Cameron, David Lechner, Nuno Sá, devicetree

Add Device Tree binding for the Broadcom APDS9999 ambient light
and proximity sensor.

The APDS9999 features individual R, G, B, and IR channels with
a green channel that uses optical coating to approximate the
human eye spectral response for ALS/lux measurements. Proximity
and RGB functionality are not yet implemented in the driver.

Signed-off-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
---
 .../bindings/iio/light/brcm,apds9999.yaml     | 53 +++++++++++++++++++
 MAINTAINERS                                   |  6 +++
 2 files changed, 59 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml

diff --git a/Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml b/Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
new file mode 100644
index 000000000000..759fe0151549
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/light/brcm,apds9999.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Broadcom APDS-9999 Digital Proximity and RGB Sensor
+
+maintainers:
+  - Jose A. Perez de Azpillaga <azpijr@gmail.com>
+
+description: |
+  Broadcom APDS-9999 is a digital proximity and RGB sensor with
+  ambient light sensing (ALS) capability. The device uses individual
+  R, G, B, and IR channels plus a VCSEL for proximity detection.
+
+  Datasheet: https://docs.broadcom.com/docs/APDS-9999-DS
+
+properties:
+  compatible:
+    enum:
+      - brcm,apds9999
+
+  reg:
+    maxItems: 1
+
+  vdd-supply: true
+
+  vled-supply:
+    description: VCSEL power supply
+
+  interrupts:
+    maxItems: 1
+
+additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        light-sensor@52 {
+            compatible = "brcm,apds9999";
+            reg = <0x52>;
+            vdd-supply = <&vdd_reg>;
+            vled-supply = <&vled_reg>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 1aa9c989973f..64c8cf2601e8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4302,6 +4302,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml
 F:	drivers/iio/light/apds9306.c

+BROADCOM APDS9999 AMBIENT LIGHT SENSOR DRIVER
+M:	Jose A. Perez de Azpillaga <azpijr@gmail.com>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
+
 AVIA HX711 ANALOG DIGITAL CONVERTER IIO DRIVER
 M:	Andreas Klinger <ak@it-klinger.de>
 L:	linux-iio@vger.kernel.org



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

* [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor
  2026-05-13  8:09 [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999 Jose A. Perez de Azpillaga
  2026-05-13  8:10 ` [RFC PATCH v2 1/2] dt-bindings: iio: light: add " Jose A. Perez de Azpillaga
@ 2026-05-13  8:10 ` Jose A. Perez de Azpillaga
  2026-05-16 14:58   ` Jonathan Cameron
  2026-05-16 15:28   ` David Lechner
  2026-05-16 14:44 ` [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999 Jonathan Cameron
  2 siblings, 2 replies; 10+ messages in thread
From: Jose A. Perez de Azpillaga @ 2026-05-13  8:10 UTC (permalink / raw)
  To: linux-iio; +Cc: Jonathan Cameron, David Lechner, Nuno Sá

Add IIO driver for Broadcom APDS9999 ambient light sensor.

The APDS9999 is a digital proximity and RGB sensor with ALS
capability. This driver implements the ALS/Lux functionality using
the green channel, which uses optical coating technology to
approximate the human eye spectral response.

Proximity (PS) and RGB color features are not yet implemented.

Signed-off-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
---
 MAINTAINERS                  |   1 +
 drivers/iio/light/Kconfig    |  10 +
 drivers/iio/light/Makefile   |   1 +
 drivers/iio/light/apds9999.c | 344 +++++++++++++++++++++++++++++++++++
 4 files changed, 356 insertions(+)
 create mode 100644 drivers/iio/light/apds9999.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 64c8cf2601e8..cdb703f37f06 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4307,6 +4307,7 @@ M:	Jose A. Perez de Azpillaga <azpijr@gmail.com>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
+F:	drivers/iio/light/apds9999.c

 AVIA HX711 ANALOG DIGITAL CONVERTER IIO DRIVER
 M:	Andreas Klinger <ak@it-klinger.de>
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index eff33e456c70..c7256ffb972d 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -119,6 +119,16 @@ config APDS9960
 	  To compile this driver as a module, choose M here: the
 	  module will be called apds9960

+config APDS9999
+	tristate "APDS9999 ambient light sensor"
+	depends on I2C
+	help
+	  Say Y here if you want to build support for the Broadcom APDS9999
+	  ambient light sensor (ALS/Lux).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called apds9999.
+
 config AS73211
 	tristate "AMS AS73211 XYZ color sensor and AMS AS7331 UV sensor"
 	depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index c0048e0d5ca8..39e62dfc10c7 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_APDS9160)		+= apds9160.o
 obj-$(CONFIG_APDS9300)		+= apds9300.o
 obj-$(CONFIG_APDS9306)		+= apds9306.o
 obj-$(CONFIG_APDS9960)		+= apds9960.o
+obj-$(CONFIG_APDS9999)		+= apds9999.o
 obj-$(CONFIG_AS73211)		+= as73211.o
 obj-$(CONFIG_BH1745)		+= bh1745.o
 obj-$(CONFIG_BH1750)		+= bh1750.o
diff --git a/drivers/iio/light/apds9999.c b/drivers/iio/light/apds9999.c
new file mode 100644
index 000000000000..577b93f61d31
--- /dev/null
+++ b/drivers/iio/light/apds9999.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IIO driver for Broadcom APDS9999 Lux Light Sensor
+ *
+ * Copyright (C) 2026
+ * Author: Jose A. Perez de Azpillaga <azpijr@gmail.com>
+ *
+ * TODO: proximity and color sensor
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/unaligned.h>
+
+#define APDS9999_REG_MAIN_CTRL		0x00
+#define   APDS9999_MAIN_CTRL_LS_EN	BIT(1)
+#define APDS9999_REG_LS_MEAS_RATE	0x04
+#define   APDS9999_LS_RES_MASK		GENMASK(6, 4)
+#define   APDS9999_LS_RATE_MASK	GENMASK(2, 0)
+#define APDS9999_REG_LS_GAIN		0x05
+#define APDS9999_REG_PART_ID		0x06
+#define APDS9999_REG_MAIN_STATUS	0x07
+#define   APDS9999_MAIN_STATUS_LS_DATA	BIT(3)
+#define APDS9999_REG_LS_DATA_IR_0	0x0A
+#define APDS9999_REG_LS_DATA_GREEN_0	0x0D
+#define APDS9999_REG_LS_DATA_BLUE_0	0x10
+#define APDS9999_REG_LS_DATA_RED_0	0x13
+
+#define APDS9999_PART_ID		0xC2
+
+enum apds9999_gain {
+	APDS9999_GAIN_1X = 0,
+	APDS9999_GAIN_3X = 1,
+	APDS9999_GAIN_6X = 2,
+	APDS9999_GAIN_9X = 3,
+	APDS9999_GAIN_18X = 4,
+};
+
+static const int apds9999_gains[] = {
+	[APDS9999_GAIN_1X]  = 1,
+	[APDS9999_GAIN_3X]  = 3,
+	[APDS9999_GAIN_6X]  = 6,
+	[APDS9999_GAIN_9X]  = 9,
+	[APDS9999_GAIN_18X] = 18,
+};
+
+enum apds9999_resolution {
+	APDS9999_RES_20BIT = 0,	/* 400 ms */
+	APDS9999_RES_19BIT = 1,	/* 200 ms */
+	APDS9999_RES_18BIT = 2,	/* 100 ms (default) */
+	APDS9999_RES_17BIT = 3,	/*  50 ms */
+	APDS9999_RES_16BIT = 4,	/*  25 ms */
+	APDS9999_RES_13BIT = 5,	/*  3.125 ms */
+	APDS9999_RES_NUM
+};
+
+static const int apds9999_itimes_us[APDS9999_RES_NUM] = {
+	[APDS9999_RES_20BIT] = 400000,
+	[APDS9999_RES_19BIT] = 200000,
+	[APDS9999_RES_18BIT] = 100000,
+	[APDS9999_RES_17BIT] =  50000,
+	[APDS9999_RES_16BIT] =  25000,
+	[APDS9999_RES_13BIT] =   3125,
+};
+
+enum apds9999_rate {
+	APDS9999_RATE_25_MS = 0,
+	APDS9999_RATE_50_MS = 1,
+	APDS9999_RATE_100_MS = 2,
+	APDS9999_RATE_200_MS = 3,
+	APDS9999_RATE_500_MS = 4,
+	APDS9999_RATE_1000_MS = 5,
+	APDS9999_RATE_2000_MS = 6,
+};
+
+struct apds9999_data {
+	struct i2c_client *client;
+	/* lock: protects als_gain_idx, als_res, als_rate */
+	struct mutex lock;
+	int als_gain_idx;
+	int als_res;
+	int als_rate;
+};
+
+static void apds9999_standby(void *client)
+{
+	i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, 0);
+}
+
+static int apds9999_init(struct apds9999_data *data)
+{
+	struct device *dev = &data->client->dev;
+	struct i2c_client *client = data->client;
+	u8 reg;
+	int ret;
+
+	ret = devm_add_action_or_reset(dev, apds9999_standby, client);
+	if (ret)
+		return ret;
+
+	guard(mutex)(&data->lock);
+
+	reg = FIELD_PREP(APDS9999_LS_RES_MASK, APDS9999_RES_18BIT) |
+	      FIELD_PREP(APDS9999_LS_RATE_MASK, APDS9999_RATE_100_MS);
+	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_MEAS_RATE, reg);
+	if (ret)
+		return ret;
+	data->als_res = APDS9999_RES_18BIT;
+	data->als_rate = APDS9999_RATE_100_MS;
+
+	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_GAIN,
+					APDS9999_GAIN_3X);
+	if (ret)
+		return ret;
+	data->als_gain_idx = APDS9999_GAIN_3X;
+
+	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL,
+					APDS9999_MAIN_CTRL_LS_EN);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int apds9999_read_channel(struct apds9999_data *data, u8 reg, u32 *counts)
+{
+	struct i2c_client *client = data->client;
+	u8 buf[3];
+	int ret, tries;
+
+	guard(mutex)(&data->lock);
+
+	/*
+	 * Poll MAIN_STATUS for new data.  Timeout: ~2 integration periods
+	 * plus margin.  Each try sleeps 20 ms.
+	 */
+	tries = max(2, (apds9999_itimes_us[data->als_res] * 2) / 20000);
+
+	while (tries--) {
+		ret = i2c_smbus_read_byte_data(client,
+						APDS9999_REG_MAIN_STATUS);
+		if (ret < 0)
+			return ret;
+		if (ret & APDS9999_MAIN_STATUS_LS_DATA)
+			break;
+		fsleep(20000);
+	}
+
+	if (tries < 0)
+		return -ETIMEDOUT;
+
+	ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
+	if (ret < 0)
+		return ret;
+	if (ret != sizeof(buf))
+		return -EIO;
+
+	*counts = get_unaligned_le24(buf) & GENMASK(19, 0);
+	return 0;
+}
+
+static int apds9999_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct apds9999_data *data = iio_priv(indio_dev);
+	int gain, itime_us;
+	u64 scale_nano;
+	u32 counts;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = apds9999_read_channel(data,
+						    APDS9999_REG_LS_DATA_GREEN_0,
+						    &counts);
+			break;
+		case IIO_INTENSITY:
+			ret = apds9999_read_channel(data, chan->address,
+						    &counts);
+			break;
+		default:
+			return -EINVAL;
+		}
+		if (ret)
+			return ret;
+		*val = (int)counts;
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		/*
+		 * Scale (lux per count) = 54 / (gain * integration_time_ms)
+		 *
+		 * The constant 54 is derived from the datasheet table:
+		 *   at gain = 3x, itime = 100 ms -> 0.180 lux/count
+		 *   -> C = 0.180 * 3 * 100 = 54
+		 *
+		 * Expressed as IIO_VAL_INT_PLUS_NANO.
+		 */
+		gain = apds9999_gains[data->als_gain_idx];
+		itime_us = apds9999_itimes_us[data->als_res];
+
+		/* scale_nano = 54e12 / (gain * itime_us) nano-lux/count */
+		scale_nano = div_u64(54000000000000ULL, (u32)(gain * itime_us));
+		*val = (int)(scale_nano / NSEC_PER_SEC);
+		*val2 = (int)(scale_nano % NSEC_PER_SEC);
+		return IIO_VAL_INT_PLUS_NANO;
+
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = 0;
+		*val2 = apds9999_itimes_us[data->als_res];
+		return IIO_VAL_INT_PLUS_MICRO;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_chan_spec apds9999_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+	},
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_RED,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+		.address = APDS9999_REG_LS_DATA_RED_0,
+	},
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_GREEN,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+		.address = APDS9999_REG_LS_DATA_GREEN_0,
+	},
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_BLUE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+		.address = APDS9999_REG_LS_DATA_BLUE_0,
+	},
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_CLEAR,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+		.address = APDS9999_REG_LS_DATA_IR_0,
+	},
+};
+
+static const struct iio_info apds9999_info = {
+	.read_raw = apds9999_read_raw,
+};
+
+static int apds9999_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct apds9999_data *data;
+	struct iio_dev *indio_dev;
+	int ret, part_id;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	data->client = client;
+
+	ret = devm_mutex_init(dev, &data->lock);
+	if (ret)
+		return ret;
+
+	part_id = i2c_smbus_read_byte_data(client, APDS9999_REG_PART_ID);
+	if (part_id < 0)
+		return dev_err_probe(dev, part_id,
+				     "failed to read PART_ID\n");
+	if (part_id != APDS9999_PART_ID)
+		dev_info(dev, "unexpected PART_ID 0x%02x (expected 0x%02x)\n",
+			 part_id, APDS9999_PART_ID);
+
+	ret = apds9999_init(data);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to initialize device\n");
+
+	indio_dev->name = "apds9999";
+	indio_dev->info = &apds9999_info;
+	indio_dev->channels = apds9999_channels;
+	indio_dev->num_channels = ARRAY_SIZE(apds9999_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to register IIO device\n");
+
+	return 0;
+}
+
+static const struct i2c_device_id apds9999_id[] = {
+	{ "apds9999" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, apds9999_id);
+
+static const struct of_device_id apds9999_of_match[] = {
+	{ .compatible = "brcm,apds9999" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, apds9999_of_match);
+
+static struct i2c_driver apds9999_driver = {
+	.driver = {
+		.name = "apds9999",
+		.of_match_table = apds9999_of_match,
+	},
+	.probe = apds9999_probe,
+	.id_table = apds9999_id,
+};
+module_i2c_driver(apds9999_driver);
+
+MODULE_AUTHOR("Jose A. Perez de Azpillaga <azpijr@gmail.com>");
+MODULE_DESCRIPTION("APDS-9999 Lux Light Sensor IIO Driver");
+MODULE_LICENSE("GPL");

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

* Re: [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999
  2026-05-13  8:09 [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999 Jose A. Perez de Azpillaga
  2026-05-13  8:10 ` [RFC PATCH v2 1/2] dt-bindings: iio: light: add " Jose A. Perez de Azpillaga
  2026-05-13  8:10 ` [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor Jose A. Perez de Azpillaga
@ 2026-05-16 14:44 ` Jonathan Cameron
  2 siblings, 0 replies; 10+ messages in thread
From: Jonathan Cameron @ 2026-05-16 14:44 UTC (permalink / raw)
  To: Jose A. Perez de Azpillaga; +Cc: linux-iio, David Lechner, Nuno Sá

On Wed, 13 May 2026 10:09:02 +0200
"Jose A. Perez de Azpillaga" <azpijr@gmail.com> wrote:

> Add IIO driver and DT binding for the Broadcom APDS9999 ambient light
> and proximity sensor. The green channel uses optical coating technology
> to approximate the human eye spectral response for ALS/lux measurements.
> IIO_INTENSITY channels are also provided for red, green, blue, and
> clear/IR so userspace can compute its own weighted lux.
> 
> Questions for reviewers:
>   1. Is the green channel as an ALS/lux approximation acceptable given
>      the datasheet's claim of optical coating to emulate V(lambda)?

It's the best we can do and the datasheet does say to do this. So whilst
I'll express some doubts, it's fine. I think you shout about it enough that
we shouldn't get that copied into other places.

>   2. Is the IIO_INTENSITY channel layout correct? (red, green, blue,
>      clear with modifiers, no scale)

They sometimes still have scale if there is a variable gain amplifier
we need to control / express the value of.  Looks like that might apply here.

However, it's the one channel where the ABI docs make it clear there is not
meaningful unit from _raw * _scale.  Just that if raw doubles and scale
halves we should get the same value.

>   3. Should the binding be merged with avago,apds9300.yaml or kept
>      separate given the extra vled-supply?

I'm fine with this either way. Marginal case.

With those answered, you can drop the RFC for v3 ;)

Jonathan

> 
> v2:
>   - Added IIO_INTENSITY channels for R, G, B, clear
>   - Switched to guard(mutex)(), devm_mutex_init(), dev_err_probe()
>   - Replaced remove() with devm_add_action_or_reset()
>   - Don't fail probe on PART_ID mismatch (fallback compatibles)
>   - Replaced manual shifts with FIELD_PREP() / GENMASK
>   - Used get_unaligned_le24(), sizeof(buf), NSEC_PER_SEC, fsleep()
>   - Named gain register values, explicit resolution enum values
>   - Removed section comments, grouped bit defines under registers
>   - Squashed Kconfig/Makefile into driver patch
>   - Split MAINTAINERS entry across patches
>   - DT binding: added vled-supply, interrupts, required vdd-supply
>   - DT binding: fixed title, added blank lines, full description
>   - Dropped "DT binding" duplication from binding commit subject
> 
> Link to v1: https://lore.kernel.org/linux-iio/cover.1778491503.git.azpijr@gmail.com/
> 
> Jose A. Perez de Azpillaga (2):
>   dt-bindings: iio: light: add Broadcom APDS9999
>   iio: light: add support for APDS9999 sensor
> 
>  .../bindings/iio/light/brcm,apds9999.yaml     |  53 +++
>  MAINTAINERS                                   |   7 +
>  drivers/iio/light/Kconfig                     |  10 +
>  drivers/iio/light/Makefile                    |   1 +
>  drivers/iio/light/apds9999.c                  | 344 ++++++++++++++++++
>  5 files changed, 415 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
>  create mode 100644 drivers/iio/light/apds9999.c
> 
> 


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

* Re: [RFC PATCH v2 1/2] dt-bindings: iio: light: add Broadcom APDS9999
  2026-05-13  8:10 ` [RFC PATCH v2 1/2] dt-bindings: iio: light: add " Jose A. Perez de Azpillaga
@ 2026-05-16 14:47   ` Jonathan Cameron
  0 siblings, 0 replies; 10+ messages in thread
From: Jonathan Cameron @ 2026-05-16 14:47 UTC (permalink / raw)
  To: Jose A. Perez de Azpillaga
  Cc: linux-iio, David Lechner, Nuno Sá, devicetree

On Wed, 13 May 2026 10:10:03 +0200
"Jose A. Perez de Azpillaga" <azpijr@gmail.com> wrote:

> Add Device Tree binding for the Broadcom APDS9999 ambient light
> and proximity sensor.
> 
> The APDS9999 features individual R, G, B, and IR channels with
> a green channel that uses optical coating to approximate the
> human eye spectral response for ALS/lux measurements. Proximity
> and RGB functionality are not yet implemented in the driver.

State in this description a little bit of why you didn't merge it
with similar bindings.


Trivial stuff inline.
> 
> Signed-off-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
> ---
>  .../bindings/iio/light/brcm,apds9999.yaml     | 53 +++++++++++++++++++
>  MAINTAINERS                                   |  6 +++
>  2 files changed, 59 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
> 
> diff --git a/Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml b/Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
> new file mode 100644
> index 000000000000..759fe0151549
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
> @@ -0,0 +1,53 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/light/brcm,apds9999.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +title: Broadcom APDS-9999 Digital Proximity and RGB Sensor
> +
> +maintainers:
> +  - Jose A. Perez de Azpillaga <azpijr@gmail.com>
> +
> +description: |
> +  Broadcom APDS-9999 is a digital proximity and RGB sensor with
> +  ambient light sensing (ALS) capability. The device uses individual
> +  R, G, B, and IR channels plus a VCSEL for proximity detection.

What's a VCSEL?  Spell it out.

Otherwise 
> +
> +  Datasheet: https://docs.broadcom.com/docs/APDS-9999-DS
> +
> +properties:
> +  compatible:
> +    enum:
> +      - brcm,apds9999
> +
> +  reg:
> +    maxItems: 1
> +
> +  vdd-supply: true
> +
> +  vled-supply:
> +    description: VCSEL power supply
> +
> +  interrupts:
> +    maxItems: 1
> +
> +additionalProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - vdd-supply
> +
> +examples:
> +  - |
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        light-sensor@52 {
> +            compatible = "brcm,apds9999";
> +            reg = <0x52>;
> +            vdd-supply = <&vdd_reg>;
> +            vled-supply = <&vled_reg>;
> +        };
> +    };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1aa9c989973f..64c8cf2601e8 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4302,6 +4302,12 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml
>  F:	drivers/iio/light/apds9306.c
> 
> +BROADCOM APDS9999 AMBIENT LIGHT SENSOR DRIVER
> +M:	Jose A. Perez de Azpillaga <azpijr@gmail.com>
> +L:	linux-iio@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml

Sashiko pointed out this should be in alphabetical order.
Bit annoying in this particular case but we'll just find someone
else moves it if we don't do it from the start.

> +
>  AVIA HX711 ANALOG DIGITAL CONVERTER IIO DRIVER
>  M:	Andreas Klinger <ak@it-klinger.de>
>  L:	linux-iio@vger.kernel.org
> 
> 
> 


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

* Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor
  2026-05-13  8:10 ` [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor Jose A. Perez de Azpillaga
@ 2026-05-16 14:58   ` Jonathan Cameron
  2026-05-16 15:28   ` David Lechner
  1 sibling, 0 replies; 10+ messages in thread
From: Jonathan Cameron @ 2026-05-16 14:58 UTC (permalink / raw)
  To: Jose A. Perez de Azpillaga; +Cc: linux-iio, David Lechner, Nuno Sá

On Wed, 13 May 2026 10:10:35 +0200
"Jose A. Perez de Azpillaga" <azpijr@gmail.com> wrote:

> Add IIO driver for Broadcom APDS9999 ambient light sensor.
> 
> The APDS9999 is a digital proximity and RGB sensor with ALS
> capability. This driver implements the ALS/Lux functionality using
> the green channel, which uses optical coating technology to
> approximate the human eye spectral response.

Can wrap commit messages to 75 chars.

> 
> Proximity (PS) and RGB color features are not yet implemented.
Needs an update (sashiko pointed this out)
https://sashiko.dev/#/patchset/cover.1778659152.git.azpijr%40gmail.com

> 
> Signed-off-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>

A few minor things inline. 

Thanks,

Jonathan

> diff --git a/drivers/iio/light/apds9999.c b/drivers/iio/light/apds9999.c
> new file mode 100644
> index 000000000000..577b93f61d31
> --- /dev/null
> +++ b/drivers/iio/light/apds9999.c
> @@ -0,0 +1,344 @@

> +static int apds9999_init(struct apds9999_data *data)
> +{
> +	struct device *dev = &data->client->dev;
> +	struct i2c_client *client = data->client;
> +	u8 reg;

I think regval makes it more obvious this isn't a register address.

> +	int ret;
> +
> +	ret = devm_add_action_or_reset(dev, apds9999_standby, client);
> +	if (ret)
> +		return ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	reg = FIELD_PREP(APDS9999_LS_RES_MASK, APDS9999_RES_18BIT) |
> +	      FIELD_PREP(APDS9999_LS_RATE_MASK, APDS9999_RATE_100_MS);
> +	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_MEAS_RATE, reg);
> +	if (ret)
> +		return ret;
> +	data->als_res = APDS9999_RES_18BIT;
> +	data->als_rate = APDS9999_RATE_100_MS;
> +
> +	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_GAIN,
> +					APDS9999_GAIN_3X);
> +	if (ret)
> +		return ret;
> +	data->als_gain_idx = APDS9999_GAIN_3X;
> +
> +	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL,
> +					APDS9999_MAIN_CTRL_LS_EN);
> +	if (ret)
> +		return ret;
> +
> +	return 0;

	return i2c_smbus_write_byte_data()

> +}
> +
> +static int apds9999_read_channel(struct apds9999_data *data, u8 reg, u32 *counts)
> +{
> +	struct i2c_client *client = data->client;
> +	u8 buf[3];
> +	int ret, tries;
> +
> +	guard(mutex)(&data->lock);
> +
> +	/*
> +	 * Poll MAIN_STATUS for new data.  Timeout: ~2 integration periods
> +	 * plus margin.  Each try sleeps 20 ms.
> +	 */
> +	tries = max(2, (apds9999_itimes_us[data->als_res] * 2) / 20000);
> +
> +	while (tries--) {
> +		ret = i2c_smbus_read_byte_data(client,
> +						APDS9999_REG_MAIN_STATUS);
Off by one space and it's under 80 chars anyway (would have been fine if a little
over a well).

		ret = i2c_smbus_read_byte_data(client, APDS9999_REG_MAIN_STATUS);


> +		if (ret < 0)
> +			return ret;
> +		if (ret & APDS9999_MAIN_STATUS_LS_DATA)
> +			break;
> +		fsleep(20000);
> +	}
> +
> +	if (tries < 0)
> +		return -ETIMEDOUT;
> +
> +	ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
> +	if (ret < 0)
> +		return ret;
> +	if (ret != sizeof(buf))
> +		return -EIO;
> +
> +	*counts = get_unaligned_le24(buf) & GENMASK(19, 0);
> +	return 0;
> +}
> +
> +static int apds9999_read_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int *val, int *val2, long mask)
> +{
> +	struct apds9999_data *data = iio_priv(indio_dev);
> +	int gain, itime_us;
> +	u64 scale_nano;
> +	u32 counts;
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		switch (chan->type) {
> +		case IIO_LIGHT:
> +			ret = apds9999_read_channel(data,
> +						    APDS9999_REG_LS_DATA_GREEN_0,
> +						    &counts);
			if (ret)
				return ret;

			*val = (int)counts;
	
			return IIO_VAL_COUNT;
(See below)

> +			break;
> +		case IIO_INTENSITY:
> +			ret = apds9999_read_channel(data, chan->address,
> +						    &counts);
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		if (ret)
> +			return ret;
I'd shift this up so all errors are dealt with at source.  Means we don't need
to read on to see where they are going... 
> +		*val = (int)counts;
> +		return IIO_VAL_INT;
Not worth sharing this either given only save a tiny bit.

> +
> +	case IIO_CHAN_INFO_SCALE:
> +		/*
> +		 * Scale (lux per count) = 54 / (gain * integration_time_ms)
> +		 *
> +		 * The constant 54 is derived from the datasheet table:
> +		 *   at gain = 3x, itime = 100 ms -> 0.180 lux/count
> +		 *   -> C = 0.180 * 3 * 100 = 54
> +		 *
> +		 * Expressed as IIO_VAL_INT_PLUS_NANO.
> +		 */
> +		gain = apds9999_gains[data->als_gain_idx];
> +		itime_us = apds9999_itimes_us[data->als_res];
> +
> +		/* scale_nano = 54e12 / (gain * itime_us) nano-lux/count */
> +		scale_nano = div_u64(54000000000000ULL, (u32)(gain * itime_us));
> +		*val = (int)(scale_nano / NSEC_PER_SEC);
> +		*val2 = (int)(scale_nano % NSEC_PER_SEC);
> +		return IIO_VAL_INT_PLUS_NANO;
> +
> +	case IIO_CHAN_INFO_INT_TIME:
> +		*val = 0;
> +		*val2 = apds9999_itimes_us[data->als_res];
> +		return IIO_VAL_INT_PLUS_MICRO;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct iio_chan_spec apds9999_channels[] = {
> +	{
> +		.type = IIO_LIGHT,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_RED,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +		.address = APDS9999_REG_LS_DATA_RED_0,
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_GREEN,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +		.address = APDS9999_REG_LS_DATA_GREEN_0,
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_BLUE,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +		.address = APDS9999_REG_LS_DATA_BLUE_0,
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_CLEAR,

Why clear rather than IR? Sashiko even noticed this. 

> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +		.address = APDS9999_REG_LS_DATA_IR_0,
> +	},
> +};

> +
> +static const struct i2c_device_id apds9999_id[] = {
> +	{ "apds9999" },

Whilst not traditionally done, we are moving towards using c99 for this.
	{ .name = "apds9999" }
preferred.  Doesn't actually matter if only a name, but later fields
are likely to soon become a union.  For consistency and ease of explanation
I think always using named structure fields is a good direction to move in.

> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, apds9999_id);
> +
> +static const struct of_device_id apds9999_of_match[] = {
> +	{ .compatible = "brcm,apds9999" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, apds9999_of_match);
> +
> +static struct i2c_driver apds9999_driver = {
> +	.driver = {
> +		.name = "apds9999",
> +		.of_match_table = apds9999_of_match,
> +	},
> +	.probe = apds9999_probe,
> +	.id_table = apds9999_id,
> +};
> +module_i2c_driver(apds9999_driver);
> +
> +MODULE_AUTHOR("Jose A. Perez de Azpillaga <azpijr@gmail.com>");
> +MODULE_DESCRIPTION("APDS-9999 Lux Light Sensor IIO Driver");
> +MODULE_LICENSE("GPL");
> 


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

* Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor
  2026-05-13  8:10 ` [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor Jose A. Perez de Azpillaga
  2026-05-16 14:58   ` Jonathan Cameron
@ 2026-05-16 15:28   ` David Lechner
  2026-05-16 16:39     ` Jonathan Cameron
  1 sibling, 1 reply; 10+ messages in thread
From: David Lechner @ 2026-05-16 15:28 UTC (permalink / raw)
  To: Jose A. Perez de Azpillaga, linux-iio; +Cc: Jonathan Cameron, Nuno Sá

On 5/13/26 3:10 AM, Jose A. Perez de Azpillaga wrote:
> Add IIO driver for Broadcom APDS9999 ambient light sensor.
> 
> The APDS9999 is a digital proximity and RGB sensor with ALS
> capability. This driver implements the ALS/Lux functionality using
> the green channel, which uses optical coating technology to
> approximate the human eye spectral response.
> 
> Proximity (PS) and RGB color features are not yet implemented.
> 



> +enum apds9999_gain {
> +	APDS9999_GAIN_1X = 0,
> +	APDS9999_GAIN_3X = 1,
> +	APDS9999_GAIN_6X = 2,
> +	APDS9999_GAIN_9X = 3,
> +	APDS9999_GAIN_18X = 4,
> +};

Giving the natural values of enum explicitly just seems like noise
IMHO. I wasted time looking for an anomaly.

> +
> +static const int apds9999_gains[] = {
> +	[APDS9999_GAIN_1X]  = 1,
> +	[APDS9999_GAIN_3X]  = 3,
> +	[APDS9999_GAIN_6X]  = 6,
> +	[APDS9999_GAIN_9X]  = 9,
> +	[APDS9999_GAIN_18X] = 18,
> +};
> +
> +enum apds9999_resolution {
> +	APDS9999_RES_20BIT = 0,	/* 400 ms */
> +	APDS9999_RES_19BIT = 1,	/* 200 ms */
> +	APDS9999_RES_18BIT = 2,	/* 100 ms (default) */
> +	APDS9999_RES_17BIT = 3,	/*  50 ms */
> +	APDS9999_RES_16BIT = 4,	/*  25 ms */
> +	APDS9999_RES_13BIT = 5,	/*  3.125 ms */
> +	APDS9999_RES_NUM
> +};

Comments seems redundant since we have the table below.

> +
> +static const int apds9999_itimes_us[APDS9999_RES_NUM] = {
> +	[APDS9999_RES_20BIT] = 400000,

Can be written as 400 * USEC_PER_MSEC to make it easier to understand.

> +	[APDS9999_RES_19BIT] = 200000,
> +	[APDS9999_RES_18BIT] = 100000,
> +	[APDS9999_RES_17BIT] =  50000,
> +	[APDS9999_RES_16BIT] =  25000,
> +	[APDS9999_RES_13BIT] =   3125,
> +};
> +
> +enum apds9999_rate {
> +	APDS9999_RATE_25_MS = 0,
> +	APDS9999_RATE_50_MS = 1,
> +	APDS9999_RATE_100_MS = 2,
> +	APDS9999_RATE_200_MS = 3,
> +	APDS9999_RATE_500_MS = 4,
> +	APDS9999_RATE_1000_MS = 5,
> +	APDS9999_RATE_2000_MS = 6,

again, values are distracting.

> +};
> +
> +struct apds9999_data {
> +	struct i2c_client *client;
> +	/* lock: protects als_gain_idx, als_res, als_rate */
> +	struct mutex lock;
> +	int als_gain_idx;
> +	int als_res;
> +	int als_rate;
> +};
> +
> +static void apds9999_standby(void *client)
> +{
> +	i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, 0);
> +}
> +
> +static int apds9999_init(struct apds9999_data *data)
> +{
> +	struct device *dev = &data->client->dev;
> +	struct i2c_client *client = data->client;
> +	u8 reg;
> +	int ret;
> +
> +	ret = devm_add_action_or_reset(dev, apds9999_standby, client);
> +	if (ret)
> +		return ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	reg = FIELD_PREP(APDS9999_LS_RES_MASK, APDS9999_RES_18BIT) |
> +	      FIELD_PREP(APDS9999_LS_RATE_MASK, APDS9999_RATE_100_MS);
> +	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_MEAS_RATE, reg);
> +	if (ret)
> +		return ret;


> +	data->als_res = APDS9999_RES_18BIT;
> +	data->als_rate = APDS9999_RATE_100_MS;

Can we get a comment explaining why these are the default?

> +
> +	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_GAIN,
> +					APDS9999_GAIN_3X);
> +	if (ret)
> +		return ret;
> +	data->als_gain_idx = APDS9999_GAIN_3X;
> +
> +	ret = i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL,
> +					APDS9999_MAIN_CTRL_LS_EN);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int apds9999_read_channel(struct apds9999_data *data, u8 reg, u32 *counts)
> +{
> +	struct i2c_client *client = data->client;
> +	u8 buf[3];
> +	int ret, tries;
> +
> +	guard(mutex)(&data->lock);
> +
> +	/*
> +	 * Poll MAIN_STATUS for new data.  Timeout: ~2 integration periods
> +	 * plus margin.  Each try sleeps 20 ms.
> +	 */
> +	tries = max(2, (apds9999_itimes_us[data->als_res] * 2) / 20000);
> +
> +	while (tries--) {
> +		ret = i2c_smbus_read_byte_data(client,
> +						APDS9999_REG_MAIN_STATUS);
> +		if (ret < 0)
> +			return ret;
> +		if (ret & APDS9999_MAIN_STATUS_LS_DATA)
> +			break;
> +		fsleep(20000);
> +	}
> +
> +	if (tries < 0)
> +		return -ETIMEDOUT;
> +
> +	ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
> +	if (ret < 0)
> +		return ret;
> +	if (ret != sizeof(buf))
> +		return -EIO;
> +
> +	*counts = get_unaligned_le24(buf) & GENMASK(19, 0);
> +	return 0;
> +}
> +
> +static int apds9999_read_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int *val, int *val2, long mask)
> +{
> +	struct apds9999_data *data = iio_priv(indio_dev);
> +	int gain, itime_us;
> +	u64 scale_nano;
> +	u32 counts;
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		switch (chan->type) {
> +		case IIO_LIGHT:
> +			ret = apds9999_read_channel(data,
> +						    APDS9999_REG_LS_DATA_GREEN_0,
> +						    &counts);
> +			break;
> +		case IIO_INTENSITY:
> +			ret = apds9999_read_channel(data, chan->address,
> +						    &counts);
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		if (ret)
> +			return ret;
> +		*val = (int)counts;
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_SCALE:
> +		/*
> +		 * Scale (lux per count) = 54 / (gain * integration_time_ms)
> +		 *
> +		 * The constant 54 is derived from the datasheet table:
> +		 *   at gain = 3x, itime = 100 ms -> 0.180 lux/count
> +		 *   -> C = 0.180 * 3 * 100 = 54
> +		 *
> +		 * Expressed as IIO_VAL_INT_PLUS_NANO.
> +		 */
> +		gain = apds9999_gains[data->als_gain_idx];
> +		itime_us = apds9999_itimes_us[data->als_res];
> +
> +		/* scale_nano = 54e12 / (gain * itime_us) nano-lux/count */
> +		scale_nano = div_u64(54000000000000ULL, (u32)(gain * itime_us));
> +		*val = (int)(scale_nano / NSEC_PER_SEC);
> +		*val2 = (int)(scale_nano % NSEC_PER_SEC);
> +		return IIO_VAL_INT_PLUS_NANO;
> +
> +	case IIO_CHAN_INFO_INT_TIME:
> +		*val = 0;
> +		*val2 = apds9999_itimes_us[data->als_res];
> +		return IIO_VAL_INT_PLUS_MICRO;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct iio_chan_spec apds9999_channels[] = {
> +	{
> +		.type = IIO_LIGHT,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),

It looks like if we put a .address here, we could simplify the raw read.
(Could also use a comment explaining what was discussed about the green
channel in the cover letter.)

> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_RED,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),

No scale on these?

> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +		.address = APDS9999_REG_LS_DATA_RED_0,
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_GREEN,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +		.address = APDS9999_REG_LS_DATA_GREEN_0,
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_BLUE,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +		.address = APDS9999_REG_LS_DATA_BLUE_0,
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_CLEAR,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
> +		.address = APDS9999_REG_LS_DATA_IR_0,
> +	},
> +};
> +
> +static const struct iio_info apds9999_info = {
> +	.read_raw = apds9999_read_raw,
> +};

Would be more logical to move this struct right after the function it
references.

> +
> +static int apds9999_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct apds9999_data *data;
> +	struct iio_dev *indio_dev;
> +	int ret, part_id;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	data = iio_priv(indio_dev);
> +	data->client = client;
> +
> +	ret = devm_mutex_init(dev, &data->lock);
> +	if (ret)
> +		return ret;
> +
> +	part_id = i2c_smbus_read_byte_data(client, APDS9999_REG_PART_ID);
> +	if (part_id < 0)
> +		return dev_err_probe(dev, part_id,
> +				     "failed to read PART_ID\n");
> +	if (part_id != APDS9999_PART_ID)
> +		dev_info(dev, "unexpected PART_ID 0x%02x (expected 0x%02x)\n",
> +			 part_id, APDS9999_PART_ID);
> +
> +	ret = apds9999_init(data);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "failed to initialize device\n");
> +
> +	indio_dev->name = "apds9999";
> +	indio_dev->info = &apds9999_info;
> +	indio_dev->channels = apds9999_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(apds9999_channels);
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	ret = devm_iio_device_register(dev, indio_dev);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "failed to register IIO device\n");
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id apds9999_id[] = {
> +	{ "apds9999" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, apds9999_id);
> +
> +static const struct of_device_id apds9999_of_match[] = {
> +	{ .compatible = "brcm,apds9999" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, apds9999_of_match);
> +
> +static struct i2c_driver apds9999_driver = {
> +	.driver = {
> +		.name = "apds9999",
> +		.of_match_table = apds9999_of_match,
> +	},
> +	.probe = apds9999_probe,
> +	.id_table = apds9999_id,
> +};
> +module_i2c_driver(apds9999_driver);
> +
> +MODULE_AUTHOR("Jose A. Perez de Azpillaga <azpijr@gmail.com>");
> +MODULE_DESCRIPTION("APDS-9999 Lux Light Sensor IIO Driver");
> +MODULE_LICENSE("GPL");


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

* Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor
  2026-05-16 15:28   ` David Lechner
@ 2026-05-16 16:39     ` Jonathan Cameron
  2026-05-16 18:19       ` David Lechner
  0 siblings, 1 reply; 10+ messages in thread
From: Jonathan Cameron @ 2026-05-16 16:39 UTC (permalink / raw)
  To: David Lechner; +Cc: Jose A. Perez de Azpillaga, linux-iio, Nuno Sá

On Sat, 16 May 2026 10:28:47 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 5/13/26 3:10 AM, Jose A. Perez de Azpillaga wrote:
> > Add IIO driver for Broadcom APDS9999 ambient light sensor.
> > 
> > The APDS9999 is a digital proximity and RGB sensor with ALS
> > capability. This driver implements the ALS/Lux functionality using
> > the green channel, which uses optical coating technology to
> > approximate the human eye spectral response.
> > 
> > Proximity (PS) and RGB color features are not yet implemented.
> >   
> 
> 
> 
> > +enum apds9999_gain {
> > +	APDS9999_GAIN_1X = 0,
> > +	APDS9999_GAIN_3X = 1,
> > +	APDS9999_GAIN_6X = 2,
> > +	APDS9999_GAIN_9X = 3,
> > +	APDS9999_GAIN_18X = 4,
> > +};  
> 
> Giving the natural values of enum explicitly just seems like noise
> IMHO. I wasted time looking for an anomaly.

I tend to ask for this when they are hardware field values to make
the point the values matter rather than just being an enum.
I'm not that fussed though.

Jonathan

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

* Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor
  2026-05-16 16:39     ` Jonathan Cameron
@ 2026-05-16 18:19       ` David Lechner
  2026-05-17 11:40         ` Jonathan Cameron
  0 siblings, 1 reply; 10+ messages in thread
From: David Lechner @ 2026-05-16 18:19 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Jose A. Perez de Azpillaga, linux-iio, Nuno Sá

On 5/16/26 11:39 AM, Jonathan Cameron wrote:
> On Sat, 16 May 2026 10:28:47 -0500
> David Lechner <dlechner@baylibre.com> wrote:
> 
>> On 5/13/26 3:10 AM, Jose A. Perez de Azpillaga wrote:
>>> Add IIO driver for Broadcom APDS9999 ambient light sensor.
>>>
>>> The APDS9999 is a digital proximity and RGB sensor with ALS
>>> capability. This driver implements the ALS/Lux functionality using
>>> the green channel, which uses optical coating technology to
>>> approximate the human eye spectral response.
>>>
>>> Proximity (PS) and RGB color features are not yet implemented.
>>>   
>>
>>
>>
>>> +enum apds9999_gain {
>>> +	APDS9999_GAIN_1X = 0,
>>> +	APDS9999_GAIN_3X = 1,
>>> +	APDS9999_GAIN_6X = 2,
>>> +	APDS9999_GAIN_9X = 3,
>>> +	APDS9999_GAIN_18X = 4,
>>> +};  
>>
>> Giving the natural values of enum explicitly just seems like noise
>> IMHO. I wasted time looking for an anomaly.
> 
> I tend to ask for this when they are hardware field values to make
> the point the values matter rather than just being an enum.
> I'm not that fussed though.
> 
> Jonathan

In that case, I would just use #define instead of enum.

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

* Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor
  2026-05-16 18:19       ` David Lechner
@ 2026-05-17 11:40         ` Jonathan Cameron
  0 siblings, 0 replies; 10+ messages in thread
From: Jonathan Cameron @ 2026-05-17 11:40 UTC (permalink / raw)
  To: David Lechner; +Cc: Jose A. Perez de Azpillaga, linux-iio, Nuno Sá

On Sat, 16 May 2026 13:19:28 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 5/16/26 11:39 AM, Jonathan Cameron wrote:
> > On Sat, 16 May 2026 10:28:47 -0500
> > David Lechner <dlechner@baylibre.com> wrote:
> >   
> >> On 5/13/26 3:10 AM, Jose A. Perez de Azpillaga wrote:  
> >>> Add IIO driver for Broadcom APDS9999 ambient light sensor.
> >>>
> >>> The APDS9999 is a digital proximity and RGB sensor with ALS
> >>> capability. This driver implements the ALS/Lux functionality using
> >>> the green channel, which uses optical coating technology to
> >>> approximate the human eye spectral response.
> >>>
> >>> Proximity (PS) and RGB color features are not yet implemented.
> >>>     
> >>
> >>
> >>  
> >>> +enum apds9999_gain {
> >>> +	APDS9999_GAIN_1X = 0,
> >>> +	APDS9999_GAIN_3X = 1,
> >>> +	APDS9999_GAIN_6X = 2,
> >>> +	APDS9999_GAIN_9X = 3,
> >>> +	APDS9999_GAIN_18X = 4,
> >>> +};    
> >>
> >> Giving the natural values of enum explicitly just seems like noise
> >> IMHO. I wasted time looking for an anomaly.  
> > 
> > I tend to ask for this when they are hardware field values to make
> > the point the values matter rather than just being an enum.
> > I'm not that fussed though.
> > 
> > Jonathan  
> 
> In that case, I would just use #define instead of enum.
Likewise unless the type is actually used.

For that matter if type is not used, should perhaps be an anon enum
but that tends to also indicate defines are more appropriate.

Jonathan


> 


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

end of thread, other threads:[~2026-05-17 11:40 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-13  8:09 [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999 Jose A. Perez de Azpillaga
2026-05-13  8:10 ` [RFC PATCH v2 1/2] dt-bindings: iio: light: add " Jose A. Perez de Azpillaga
2026-05-16 14:47   ` Jonathan Cameron
2026-05-13  8:10 ` [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor Jose A. Perez de Azpillaga
2026-05-16 14:58   ` Jonathan Cameron
2026-05-16 15:28   ` David Lechner
2026-05-16 16:39     ` Jonathan Cameron
2026-05-16 18:19       ` David Lechner
2026-05-17 11:40         ` Jonathan Cameron
2026-05-16 14:44 ` [RFC PATCH v2 0/2] iio: light: add support for Broadcom APDS9999 Jonathan Cameron

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox