[PATCH 3/3] iio: light: opt4001: add support for opt4041

From: Jesper X Nilsson

Date: Fri Oct 23 2026 - 03:50:04 EST


Extend the opt4001 driver to also support OPT4041. The devices are very
similar, with the difference being OPT4041 having two channels, one
visible and one wide band channel ranging from 380nm to 1000nm.

The register map is almost the same. OPT4001 has registers to read out
the last four values sampled. OPT4041 maps to the same registers, but
has two channels, saving the last two samples for each channel.

Datasheet: https://www.ti.com/lit/gpn/opt4041
Assisted-by: LLM
Signed-off-by: Jesper X Nilsson <jesper.x.nilsson@xxxxxxxx>
---
drivers/iio/light/Kconfig | 2 +-
drivers/iio/light/opt4001.c | 67 ++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 462fff1ddef2..625ebb10ad1b 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -508,7 +508,7 @@ config OPT4001
select REGMAP_I2C
help
If you say Y or M here, you get support for Texas Instruments
- OPT4001 Ambient Light Sensor.
+ OPT4001 and OPT4041 Ambient Light Sensors.

If built as a dynamically linked module, it will be called
opt4001.
diff --git a/drivers/iio/light/opt4001.c b/drivers/iio/light/opt4001.c
index bf5dc1c8514c..ff142591fcba 100644
--- a/drivers/iio/light/opt4001.c
+++ b/drivers/iio/light/opt4001.c
@@ -2,9 +2,11 @@
/*
* Copyright (C) 2023 Axis Communications AB
*
- * Datasheet: https://www.ti.com/lit/gpn/opt4001
+ * Datasheets:
+ * https://www.ti.com/lit/gpn/opt4001
+ * https://www.ti.com/lit/gpn/opt4041
*
- * Device driver for the Texas Instruments OPT4001.
+ * Device driver for the Texas Instruments OPT4001 and OPT4041.
*/

#include <linux/bitfield.h>
@@ -19,6 +21,8 @@
/* OPT4001 register set */
#define OPT4001_LIGHT1_MSB 0x00
#define OPT4001_LIGHT1_LSB 0x01
+#define OPT4041_LIGHT2_MSB 0x02
+#define OPT4041_LIGHT2_LSB 0x03
#define OPT4001_CTRL 0x0A
#define OPT4001_DEVICE_ID 0x11

@@ -43,6 +47,7 @@

/* OPT4001 constants */
#define OPT4001_DEVICE_ID_VAL 0x121
+#define OPT4041_DEVICE_ID_VAL 0x221

/* OPT4001 operating modes */
#define OPT4001_CTRL_OPER_MODE_OFF 0x0
@@ -72,7 +77,7 @@

/*
* The different packaging of OPT4001 has different constants used when calculating
- * lux values.
+ * lux values and OPT4041 has one additional wide band channel.
*/
struct opt4001_chip_info {
int mul;
@@ -100,6 +105,25 @@ static const struct iio_chan_spec opt4001_channels[] = {
},
};

+static const struct iio_chan_spec opt4041_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .address = OPT4001_LIGHT1_MSB,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME)
+ },
+ {
+ .type = IIO_INTENSITY,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_BOTH,
+ .address = OPT4041_LIGHT2_MSB,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME)
+ },
+};
+
static const struct opt4001_chip_info opt4001_sot_5x3_info = {
.mul = 4375,
.div = 10000000,
@@ -118,6 +142,15 @@ static const struct opt4001_chip_info opt4001_picostar_info = {
.device_id = OPT4001_DEVICE_ID_VAL,
};

+static const struct opt4001_chip_info opt4041_sot_5x3_info = {
+ .mul = 5850,
+ .div = 10000000,
+ .name = "opt4041-sot-5x3",
+ .channels = opt4041_channels,
+ .num_channels = ARRAY_SIZE(opt4041_channels),
+ .device_id = OPT4041_DEVICE_ID_VAL,
+};
+
static const int opt4001_int_time_available[][2] = {
{ 0, 600 },
{ 0, 1000 },
@@ -270,10 +303,30 @@ static int opt4001_read_raw(struct iio_dev *indio_dev,
int *val, int *val2, long mask)
{
struct opt4001_chip *chip = iio_priv(indio_dev);
+ u64 raw_value;
+ u32 rem;
+ int ret;

switch (mask) {
case IIO_CHAN_INFO_PROCESSED:
return opt4001_read_lux_value(chip, chan->address, val, val2);
+ case IIO_CHAN_INFO_RAW:
+ ret = opt4001_read_raw_value(chip, chan->address, &raw_value);
+ if (ret)
+ return ret;
+
+ *val = div_u64_rem(raw_value, 100, &rem);
+ *val2 = rem * 10000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ /*
+ * OPT4041 CH1: datasheet specifies 0.1923 nW/cm^2 per ADC code
+ * with a 520 nm stimulus. Convert to nW/m^2 (x10000) and
+ * compensate for dividing the reported raw value by 100 (x100).
+ * The resulting scale is 192300 nW/m^2 per reported unit.
+ */
+ *val = 192300;
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_INT_TIME:
*val = 0;
*val2 = opt4001_int_time_reg[chip->int_time][0];
@@ -340,6 +393,8 @@ static bool opt4001_readable_reg(struct device *dev, unsigned int reg)
switch (reg) {
case OPT4001_LIGHT1_MSB:
case OPT4001_LIGHT1_LSB:
+ case OPT4041_LIGHT2_MSB:
+ case OPT4041_LIGHT2_LSB:
case OPT4001_CTRL:
case OPT4001_DEVICE_ID:
return true;
@@ -363,6 +418,8 @@ static bool opt4001_volatile_reg(struct device *dev, unsigned int reg)
switch (reg) {
case OPT4001_LIGHT1_MSB:
case OPT4001_LIGHT1_LSB:
+ case OPT4041_LIGHT2_MSB:
+ case OPT4041_LIGHT2_LSB:
return true;
default:
return false;
@@ -448,6 +505,7 @@ static int opt4001_probe(struct i2c_client *client)
static const struct i2c_device_id opt4001_id[] = {
{ .name = "opt4001-sot-5x3", .driver_data = (kernel_ulong_t)&opt4001_sot_5x3_info },
{ .name = "opt4001-picostar", .driver_data = (kernel_ulong_t)&opt4001_picostar_info },
+ { .name = "opt4041-sot-5x3", .driver_data = (kernel_ulong_t)&opt4041_sot_5x3_info },
{ }
};
MODULE_DEVICE_TABLE(i2c, opt4001_id);
@@ -455,6 +513,7 @@ MODULE_DEVICE_TABLE(i2c, opt4001_id);
static const struct of_device_id opt4001_of_match[] = {
{ .compatible = "ti,opt4001-sot-5x3", .data = &opt4001_sot_5x3_info},
{ .compatible = "ti,opt4001-picostar", .data = &opt4001_picostar_info},
+ { .compatible = "ti,opt4041-sot-5x3", .data = &opt4041_sot_5x3_info},
{ }
};
MODULE_DEVICE_TABLE(of, opt4001_of_match);
@@ -470,5 +529,5 @@ static struct i2c_driver opt4001_driver = {
module_i2c_driver(opt4001_driver);

MODULE_AUTHOR("Stefan Windfeldt-Prytz <stefan.windfeldt-prytz@xxxxxxxx>");
-MODULE_DESCRIPTION("Texas Instruments opt4001 ambient light sensor driver");
+MODULE_DESCRIPTION("Texas Instruments opt4001 and opt4041 ambient light sensor driver");
MODULE_LICENSE("GPL");

--
2.43.0