Skip to contentSkip to conversation

iio: light: add support for Broadcom APDS9999

RFC PATCH Superseded Jose A. Perez de Azpillaga 4 participants 11 messages May 13 – May 17

Subsystems (3)

v2 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

Jose A. Perez de Azpillaga

author maintainer
#0 lore raw

v2 cover iio: light: add support for Broadcom APDS9999

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/[email protected]/

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
Signature · 1 line
jose a. p-a
author maintainer
#1 lore raw

RFC PATCH v2 1/2 dt-bindings: iio: light: add Broadcom APDS9999

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.

---
 .../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
Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml new file +53 -0
new file mode 100644
index 000000000000..759fe0151549
@@ -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 <[email protected]>
+
+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>;
+        };
+    };
MAINTAINERS +6 -0
index 1aa9c989973f..64c8cf2601e8 100644
@@ -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 <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
+
 AVIA HX711 ANALOG DIGITAL CONVERTER IIO DRIVER
 M:	Andreas Klinger <[email protected]>
 L:	[email protected]
#2 lore raw

Re: [RFC PATCH v2 1/2] dt-bindings: iio: light: add Broadcom APDS9999

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Incorrect alphabetical placement of the new entry in the MAINTAINERS file. --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Incorrect alphabetical placement of the new entry in the MAINTAINERS file. --

commit 9c652efb6ad9a7c4e83bfb1ad645000b5ee9d6a4
Author: Jose A. Perez de Azpillaga <[email protected]>

dt-bindings: iio: light: add Broadcom APDS9999

This commit adds Device Tree bindings for the Broadcom APDS9999 ambient light and proximity sensor, and updates the MAINTAINERS file.

[ ... ]

Quoted 7 lines
MAINTAINERS +0 -0
index a92290fffa163..289130aeb4192 100644
@@ -3976,6 +3976,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml
 F:	drivers/iio/light/apds9306.c

MAINTAINERS S: Maintained · line 3987

Quoted 10 lines
MAINTAINERS +6 -0
 
+BROADCOM APDS9999 AMBIENT LIGHT SENSOR DRIVER
+M:	Jose A. Perez de Azpillaga <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml
+
 AVIA HX711 ANALOG DIGITAL CONVERTER IIO DRIVER
 M:	Andreas Klinger <[email protected]>
 L:	[email protected]

This isn't a bug, but should this new entry be placed further down in the file to maintain alphabetical order?

It looks like the BROADCOM entry is currently inserted between the AVAGO and AVIA entries, rather than in the B section.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
maintainer
#3 lore raw

Re: [RFC PATCH v2 1/2] dt-bindings: iio: light: add Broadcom APDS9999

In reply to Jose A. Perez de Azpillaga
Quoted 7 lines · On Wed, 13 May 2026 10:10:03 +0200 "Jose A. Perez de Azpillaga"…

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.

Quoted 18 lines
---
 .../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
Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml new file +4 -0
new file mode 100644
index 000000000000..759fe0151549
@@ -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#

Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml line 14

Quoted 10 lines
Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml new file +10 -0
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Broadcom APDS-9999 Digital Proximity and RGB Sensor
+
+maintainers:
+  - Jose A. Perez de Azpillaga <[email protected]>
+
+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

Quoted 44 lines
Documentation/devicetree/bindings/iio/light/brcm,apds9999.yaml new file +39 -0
+
+  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>;
+        };
+    };
MAINTAINERS +0 -0
index 1aa9c989973f..64c8cf2601e8 100644

MAINTAINERS S: Maintained · line 4309

Quoted 8 lines
MAINTAINERS +5 -0
@@ -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 <[email protected]>
+L:	[email protected]
+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.

Quoted 7 lines
MAINTAINERS +1 -0
+
 AVIA HX711 ANALOG DIGITAL CONVERTER IIO DRIVER
 M:	Andreas Klinger <[email protected]>
 L:	[email protected]
author maintainer
#4 lore raw

RFC PATCH v2 2/2 iio: light: add support for APDS9999 sensor

In reply to Jose A. Perez de Azpillaga

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.

---
 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
MAINTAINERS +1 -0
index 64c8cf2601e8..cdb703f37f06 100644
@@ -4307,6 +4307,7 @@ M:	Jose A. Perez de Azpillaga <[email protected]>
 L:	[email protected]
 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 <[email protected]>
drivers/iio/light/Kconfig +10 -0
index eff33e456c70..c7256ffb972d 100644
@@ -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
drivers/iio/light/Makefile +1 -0
index c0048e0d5ca8..39e62dfc10c7 100644
@@ -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
drivers/iio/light/apds9999.c new file +344 -0

344 lines not on this page · raw message

maintainer
#5 lore raw

Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor

Quoted 6 lines · On Wed, 13 May 2026 10:10:35 +0200 "Jose A. Perez de Azpillaga"…

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.

Quoted 2 lines

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

Quoted 2 lines

A few minor things inline.

Thanks,

Jonathan

drivers/iio/light/apds9999.c line 103

Quoted 12 lines
drivers/iio/light/apds9999.c new file +5 -0
new file mode 100644
index 000000000000..577b93f61d31
@@ -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.

Quoted 18 lines
drivers/iio/light/apds9999.c new file +18 -0
+	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);

drivers/iio/light/apds9999.c line 131

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+	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()

Quoted 9 lines
drivers/iio/light/apds9999.c new file +9 -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);

drivers/iio/light/apds9999.c line 150

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+
+	/*
+	 * 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);
Quoted 27 lines
drivers/iio/light/apds9999.c new file +27 -0
+		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;

drivers/iio/light/apds9999.c line 187

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+	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)

drivers/iio/light/apds9999.c line 197

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+			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...

drivers/iio/light/apds9999.c line 199

Quoted 2 lines
drivers/iio/light/apds9999.c new file +2 -0
+		*val = (int)counts;
+		return IIO_VAL_INT;

Not worth sharing this either given only save a tiny bit.

Quoted 55 lines
drivers/iio/light/apds9999.c new file +55 -0
+
+	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,

drivers/iio/light/apds9999.c line 264

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+		.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.

drivers/iio/light/apds9999.c line 321

Quoted 9 lines
drivers/iio/light/apds9999.c new file +8 -0
+		.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.

Quoted 24 lines
drivers/iio/light/apds9999.c new file +23 -0
+	{ }
+};
+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 <[email protected]>");
+MODULE_DESCRIPTION("APDS-9999 Lux Light Sensor IIO Driver");
+MODULE_LICENSE("GPL");
reviewer
#6 lore raw

Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor

In reply to Jose A. Perez de Azpillaga
Quoted 12 lines · Jose A. Perez de Azpillaga

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.

drivers/iio/light/apds9999.c line 46

Quoted 7 lines
drivers/iio/light/apds9999.c new file +7 -0
+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.

Quoted 8 lines
drivers/iio/light/apds9999.c new file +8 -0
+
+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,
+};

drivers/iio/light/apds9999.c line 64

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+
+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.

drivers/iio/light/apds9999.c line 67

Quoted 3 lines
drivers/iio/light/apds9999.c new file +3 -0
+
+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.

Quoted 5 lines
drivers/iio/light/apds9999.c new file +5 -0
+	[APDS9999_RES_19BIT] = 200000,
+	[APDS9999_RES_18BIT] = 100000,
+	[APDS9999_RES_17BIT] =  50000,
+	[APDS9999_RES_16BIT] =  25000,
+	[APDS9999_RES_13BIT] =   3125,

drivers/iio/light/apds9999.c line 82

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+};
+
+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.

Quoted 28 lines
drivers/iio/light/apds9999.c new file +28 -0
+};
+
+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);

drivers/iio/light/apds9999.c line 118

Quoted 10 lines
drivers/iio/light/apds9999.c new file +8 -0
+
+	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?

Quoted 107 lines
drivers/iio/light/apds9999.c new file +107 -0
+
+	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:

drivers/iio/light/apds9999.c line 235

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+		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.)

drivers/iio/light/apds9999.c line 241

Quoted 6 lines
drivers/iio/light/apds9999.c new file +6 -0
+	},
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_RED,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),

No scale on these?

Quoted 22 lines
drivers/iio/light/apds9999.c new file +22 -0
+		.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,

drivers/iio/light/apds9999.c line 273

Quoted 10 lines
drivers/iio/light/apds9999.c new file +10 -0
+		.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.

Quoted 71 lines
drivers/iio/light/apds9999.c new file +71 -0
+
+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 <[email protected]>");
+MODULE_DESCRIPTION("APDS-9999 Lux Light Sensor IIO Driver");
+MODULE_LICENSE("GPL");
maintainer
#7 lore raw

Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor

Quoted 13 lines · On Sat, 16 May 2026 10:28:47 -0500 David Lechner <[email protected]> wrote:
Quoted 9 lines · Jose A. Perez de Azpillaga

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.

Quoted 10 lines

drivers/iio/light/apds9999.c line 46

Quoted 7 lines
drivers/iio/light/apds9999.c new file +7 -0
+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

reviewer
#8 lore raw

Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor

Quoted 27 lines · Jonathan Cameron
Quoted 13 lines · On Sat, 16 May 2026 10:28:47 -0500 David Lechner <[email protected]> wrote:
Quoted 9 lines · Jose A. Perez de Azpillaga

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.

Quoted 10 lines

drivers/iio/light/apds9999.c line 46

Quoted 7 lines
drivers/iio/light/apds9999.c new file +7 -0
+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.

Quoted 5 lines

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.

maintainer
#9 lore raw

Re: [RFC PATCH v2 2/2] iio: light: add support for APDS9999 sensor

Quoted 28 lines · On Sat, 16 May 2026 13:19:28 -0500 David Lechner <[email protected]> wrote:
Quoted 27 lines · Jonathan Cameron
Quoted 23 lines · On Sat, 16 May 2026 10:28:47 -0500 David Lechner <[email protected]> wrote:
Quoted 12 lines · Jose A. Perez de Azpillaga

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.

drivers/iio/light/apds9999.c line 46

Quoted 7 lines
drivers/iio/light/apds9999.c new file +7 -0
+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.

Quoted 7 lines
Quoted 5 lines

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

Quoted 1 line
maintainer
#10 lore raw
In reply to Jose A. Perez de Azpillaga
Quoted 9 lines · On Wed, 13 May 2026 10:09:02 +0200 "Jose A. Perez de Azpillaga"…

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.

Quoted 2 lines

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.

Quoted 2 lines

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

Quoted 32 lines

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/[email protected]/

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