diff mbox series

[v2,2/2] iio: light: add support for veml3235

Message ID 20241020-veml3235-v2-2-4bc7cfad7e0b@gmail.com (mailing list archive)
State Changes Requested
Headers show
Series iio: light: add support for veml3235 | expand

Commit Message

Javier Carrasco Oct. 20, 2024, 7:12 p.m. UTC
The Vishay veml3235 is a low-power ambient light sensor with I2C
interface. It provides a minimum detectable intensity of
0.0021 lx/cnt, configurable integration time and gain, and an additional
white channel to distinguish between different light sources.

Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
---
 MAINTAINERS                  |   6 +
 drivers/iio/light/Kconfig    |  11 +
 drivers/iio/light/Makefile   |   1 +
 drivers/iio/light/veml3235.c | 540 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 558 insertions(+)

Comments

Jonathan Cameron Oct. 21, 2024, 6:39 p.m. UTC | #1
On Sun, 20 Oct 2024 21:12:17 +0200
Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:

> The Vishay veml3235 is a low-power ambient light sensor with I2C
> interface. It provides a minimum detectable intensity of
> 0.0021 lx/cnt, configurable integration time and gain, and an additional
> white channel to distinguish between different light sources.
> 
> Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
Hi Javier,

I missed one thing on previous review...
There is no obvious reason this driver needs to provide raw and processed
values.  Unless I'm missing something, just provide raw and let userspace
do the maths for us.

Jonathan

> diff --git a/drivers/iio/light/veml3235.c b/drivers/iio/light/veml3235.c
> new file mode 100644
> index 000000000000..fa2141cced8f
> --- /dev/null
> +++ b/drivers/iio/light/veml3235.c

> +static const struct iio_chan_spec veml3235_channels[] = {
> +	{
> +		.type = IIO_LIGHT,
> +		.channel = CH_ALS,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_PROCESSED),
Why both raw + scale and processed?

We normally only provide raw and processed for light sensors if:
1) The conversion is non linear and hard to reverse.
2) There are events that are thresholds on the raw value.

Here it is linear so just provide _RAW.

> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
> +					   BIT(IIO_CHAN_INFO_SCALE),
> +		.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
> +						     BIT(IIO_CHAN_INFO_SCALE),
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.channel = CH_WHITE,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_BOTH,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
> +					   BIT(IIO_CHAN_INFO_SCALE),
> +		.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
> +						     BIT(IIO_CHAN_INFO_SCALE),
> +	},
> +};
Javier Carrasco Oct. 21, 2024, 8:21 p.m. UTC | #2
On 21/10/2024 20:39, Jonathan Cameron wrote:
> On Sun, 20 Oct 2024 21:12:17 +0200
> Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:
> 
>> The Vishay veml3235 is a low-power ambient light sensor with I2C
>> interface. It provides a minimum detectable intensity of
>> 0.0021 lx/cnt, configurable integration time and gain, and an additional
>> white channel to distinguish between different light sources.
>>
>> Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
> Hi Javier,
> 
> I missed one thing on previous review...
> There is no obvious reason this driver needs to provide raw and processed
> values.  Unless I'm missing something, just provide raw and let userspace
> do the maths for us.
> 
> Jonathan
> 
Sure, I will drop that for v3. I added it because this driver took the
veml6030 as a reference, and that driver provides the processed value. I
guess that the veml6030 should have not provided processed values
either, but it's late to remove them after the driver was released.

Now that we are at it, what is the rule (of thumb?) to provide processed
values? Those that can't be obtained from the raw data and simple
operations with the scale/offset/integration time/whatever userspace can
see?

Thank you and best regards,
Javier Carrasco
Jonathan Cameron Oct. 22, 2024, 6:28 p.m. UTC | #3
On Mon, 21 Oct 2024 22:21:22 +0200
Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:

> On 21/10/2024 20:39, Jonathan Cameron wrote:
> > On Sun, 20 Oct 2024 21:12:17 +0200
> > Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:
> >   
> >> The Vishay veml3235 is a low-power ambient light sensor with I2C
> >> interface. It provides a minimum detectable intensity of
> >> 0.0021 lx/cnt, configurable integration time and gain, and an additional
> >> white channel to distinguish between different light sources.
> >>
> >> Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>  
> > Hi Javier,
> > 
> > I missed one thing on previous review...
> > There is no obvious reason this driver needs to provide raw and processed
> > values.  Unless I'm missing something, just provide raw and let userspace
> > do the maths for us.
> > 
> > Jonathan
> >   
> Sure, I will drop that for v3. I added it because this driver took the
> veml6030 as a reference, and that driver provides the processed value. I
> guess that the veml6030 should have not provided processed values
> either, but it's late to remove them after the driver was released.
> 
> Now that we are at it, what is the rule (of thumb?) to provide processed
> values? Those that can't be obtained from the raw data and simple
> operations with the scale/offset/integration time/whatever userspace can
> see?

Yes. If the conversion is linear, then leave it to userspace (with scale
and offset provided). If it's not linear then in kernel because currently
we have no other choice.

There are some historical quirks where a processed only interface got in
then we had to add raw later (typically when we added buffered output
where scale and offset are important because processed values normally
don't pack well).

Jonathan


> 
> Thank you and best regards,
> Javier Carrasco
Javier Carrasco Nov. 28, 2024, 12:26 p.m. UTC | #4
On 22/10/2024 20:28, Jonathan Cameron wrote:
> On Mon, 21 Oct 2024 22:21:22 +0200
> Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:
> 
>> On 21/10/2024 20:39, Jonathan Cameron wrote:
>>> On Sun, 20 Oct 2024 21:12:17 +0200
>>> Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:
>>>   
>>>> The Vishay veml3235 is a low-power ambient light sensor with I2C
>>>> interface. It provides a minimum detectable intensity of
>>>> 0.0021 lx/cnt, configurable integration time and gain, and an additional
>>>> white channel to distinguish between different light sources.
>>>>
>>>> Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>  
>>> Hi Javier,
>>>
>>> I missed one thing on previous review...
>>> There is no obvious reason this driver needs to provide raw and processed
>>> values.  Unless I'm missing something, just provide raw and let userspace
>>> do the maths for us.
>>>
>>> Jonathan
>>>   
>> Sure, I will drop that for v3. I added it because this driver took the
>> veml6030 as a reference, and that driver provides the processed value. I
>> guess that the veml6030 should have not provided processed values
>> either, but it's late to remove them after the driver was released.
>>
>> Now that we are at it, what is the rule (of thumb?) to provide processed
>> values? Those that can't be obtained from the raw data and simple
>> operations with the scale/offset/integration time/whatever userspace can
>> see?
> 
> Yes. If the conversion is linear, then leave it to userspace (with scale
> and offset provided). If it's not linear then in kernel because currently
> we have no other choice.
> 
> There are some historical quirks where a processed only interface got in
> then we had to add raw later (typically when we added buffered output
> where scale and offset are important because processed values normally
> don't pack well).
> 
> Jonathan
> 
> 


Hi Jonathan, I am bringing this back because I am not sure if dropping
the processed values was the right approach here. I would like to
clarify before propagating some approach that might not be accurate.

This sensor is linear, and the processed value can be obtained by simple
multiplications, but not just raw * scale as documented in the ABI.

This driver is based on the veml6030, whose processed value is obtained
as raw * resolution, where the resolution is completely linear and is
obtained as sensor_resolution * integration_time / scale.

That means that the scale is actually a gain, and the user needs to know
the sensor resolution provided in the datasheet (see cur_resolution in
veml6030.c) to get the processed value. There is a sensor resolution for
every pair { gain, integration_time } in the datasheet, so there is no
need to calculate anything, yet the resolution is not provided by the
driver.

Nevertheless, your comment on this matter was the following:

> Why both raw + scale and processed?
>
> We normally only provide raw and processed for light sensors if:
> 1) The conversion is non linear and hard to reverse.
> 2) There are events that are thresholds on the raw value.
>
> Here it is linear so just provide _RAW.

That is still true in this case, because it is a linear, easy to reverse
conversion. Nevertheless, the user needs to look for the sensor
resolution in the datasheet and then use the given integration_time and
scale.

Is that ok and desired for light sensors? I think that a more accurate
approach would have been treating the gain as a HARDWAREGAIN, which
would have been used to calculate the scale i.e. resolution to directly
apply to the raw value. In its current form, the processed value is not
what you get if you do raw * scale. But as you specifically mentioned
light sensors in your comment, that might not apply here. Moreover,
there are only two drivers (si1133.c and vl6180.c) that use HARDWAREGAIN
for IIO_LIGHT, which makes me think I am over-complicating thing here.

By the way, in_illuminance_hardwaregain is not documented in the ABI,
only out_voltageY and in_intensity. But that is another topic.

The veml6030 has been around for some time and there is no way around
without breaking ABI, and the veml3235 has been only applied to your
tree and maybe it could wait to be released.

If everything is ok as it is, then that's the end of the story, but if
the processed = raw * scale operation should apply, the veml3235 could
still be fixed. And when it is too late for that one too, then I could
follow a different approach for the veml6031x00 I recently sent to avoid
propagating the issue.

Thanks and best regards,
Javier Carrasco
Javier Carrasco Dec. 14, 2024, 3:24 p.m. UTC | #5
On Thu Nov 28, 2024 at 1:26 PM CET, Javier Carrasco wrote:
> On 22/10/2024 20:28, Jonathan Cameron wrote:
> > On Mon, 21 Oct 2024 22:21:22 +0200
> > Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:
> >
> >> On 21/10/2024 20:39, Jonathan Cameron wrote:
> >>> On Sun, 20 Oct 2024 21:12:17 +0200
> >>> Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:
> >>>
> >>>> The Vishay veml3235 is a low-power ambient light sensor with I2C
> >>>> interface. It provides a minimum detectable intensity of
> >>>> 0.0021 lx/cnt, configurable integration time and gain, and an additional
> >>>> white channel to distinguish between different light sources.
> >>>>
> >>>> Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
> >>> Hi Javier,
> >>>
> >>> I missed one thing on previous review...
> >>> There is no obvious reason this driver needs to provide raw and processed
> >>> values.  Unless I'm missing something, just provide raw and let userspace
> >>> do the maths for us.
> >>>
> >>> Jonathan
> >>>
> >> Sure, I will drop that for v3. I added it because this driver took the
> >> veml6030 as a reference, and that driver provides the processed value. I
> >> guess that the veml6030 should have not provided processed values
> >> either, but it's late to remove them after the driver was released.
> >>
> >> Now that we are at it, what is the rule (of thumb?) to provide processed
> >> values? Those that can't be obtained from the raw data and simple
> >> operations with the scale/offset/integration time/whatever userspace can
> >> see?
> >
> > Yes. If the conversion is linear, then leave it to userspace (with scale
> > and offset provided). If it's not linear then in kernel because currently
> > we have no other choice.
> >
> > There are some historical quirks where a processed only interface got in
> > then we had to add raw later (typically when we added buffered output
> > where scale and offset are important because processed values normally
> > don't pack well).
> >
> > Jonathan
> >
> >
>
> Hi Jonathan, I am bringing this back because I am not sure if dropping
> the processed values was the right approach here. I would like to
> clarify before propagating some approach that might not be accurate.
>
> This sensor is linear, and the processed value can be obtained by simple
> multiplications, but not just raw * scale as documented in the ABI.
>
> This driver is based on the veml6030, whose processed value is obtained
> as raw * resolution, where the resolution is completely linear and is
> obtained as sensor_resolution * integration_time / scale.
>
> That means that the scale is actually a gain, and the user needs to know
> the sensor resolution provided in the datasheet (see cur_resolution in
> veml6030.c) to get the processed value. There is a sensor resolution for
> every pair { gain, integration_time } in the datasheet, so there is no
> need to calculate anything, yet the resolution is not provided by the
> driver.
>
> Nevertheless, your comment on this matter was the following:
>
> > Why both raw + scale and processed?
> >
> > We normally only provide raw and processed for light sensors if:
> > 1) The conversion is non linear and hard to reverse.
> > 2) There are events that are thresholds on the raw value.
> >
> > Here it is linear so just provide _RAW.
>
> That is still true in this case, because it is a linear, easy to reverse
> conversion. Nevertheless, the user needs to look for the sensor
> resolution in the datasheet and then use the given integration_time and
> scale.
>
> Is that ok and desired for light sensors? I think that a more accurate
> approach would have been treating the gain as a HARDWAREGAIN, which
> would have been used to calculate the scale i.e. resolution to directly
> apply to the raw value. In its current form, the processed value is not
> what you get if you do raw * scale. But as you specifically mentioned
> light sensors in your comment, that might not apply here. Moreover,
> there are only two drivers (si1133.c and vl6180.c) that use HARDWAREGAIN
> for IIO_LIGHT, which makes me think I am over-complicating thing here.
>
> By the way, in_illuminance_hardwaregain is not documented in the ABI,
> only out_voltageY and in_intensity. But that is another topic.
>
> The veml6030 has been around for some time and there is no way around
> without breaking ABI, and the veml3235 has been only applied to your
> tree and maybe it could wait to be released.
>
> If everything is ok as it is, then that's the end of the story, but if
> the processed = raw * scale operation should apply, the veml3235 could
> still be fixed. And when it is too late for that one too, then I could
> follow a different approach for the veml6031x00 I recently sent to avoid
> propagating the issue.
>
> Thanks and best regards,
> Javier Carrasco

Hi Jonathan, this email might have gone unnoticed.

This issue is relevant for the veml6030 and veml3235, and also for the
veml6031x00 under review, as it follows the same pattern. Do you think
they are ok as they are? Probably not, as they don't follow the ABI
documentation, and after reading some other reviews, HARDWAREGAIN is
usually not the fix for something like this.
Should the gts helpers be used instead?

Thanks and best regards,
Javier Carrasco
Jonathan Cameron Dec. 15, 2024, 2:05 p.m. UTC | #6
On Sat, 14 Dec 2024 16:24:34 +0100
"Javier Carrasco" <javier.carrasco.cruz@gmail.com> wrote:

> On Thu Nov 28, 2024 at 1:26 PM CET, Javier Carrasco wrote:
> > On 22/10/2024 20:28, Jonathan Cameron wrote:  
> > > On Mon, 21 Oct 2024 22:21:22 +0200
> > > Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:
> > >  
> > >> On 21/10/2024 20:39, Jonathan Cameron wrote:  
> > >>> On Sun, 20 Oct 2024 21:12:17 +0200
> > >>> Javier Carrasco <javier.carrasco.cruz@gmail.com> wrote:
> > >>>  
> > >>>> The Vishay veml3235 is a low-power ambient light sensor with I2C
> > >>>> interface. It provides a minimum detectable intensity of
> > >>>> 0.0021 lx/cnt, configurable integration time and gain, and an additional
> > >>>> white channel to distinguish between different light sources.
> > >>>>
> > >>>> Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>  
> > >>> Hi Javier,
> > >>>
> > >>> I missed one thing on previous review...
> > >>> There is no obvious reason this driver needs to provide raw and processed
> > >>> values.  Unless I'm missing something, just provide raw and let userspace
> > >>> do the maths for us.
> > >>>
> > >>> Jonathan
> > >>>  
> > >> Sure, I will drop that for v3. I added it because this driver took the
> > >> veml6030 as a reference, and that driver provides the processed value. I
> > >> guess that the veml6030 should have not provided processed values
> > >> either, but it's late to remove them after the driver was released.
> > >>
> > >> Now that we are at it, what is the rule (of thumb?) to provide processed
> > >> values? Those that can't be obtained from the raw data and simple
> > >> operations with the scale/offset/integration time/whatever userspace can
> > >> see?  
> > >
> > > Yes. If the conversion is linear, then leave it to userspace (with scale
> > > and offset provided). If it's not linear then in kernel because currently
> > > we have no other choice.
> > >
> > > There are some historical quirks where a processed only interface got in
> > > then we had to add raw later (typically when we added buffered output
> > > where scale and offset are important because processed values normally
> > > don't pack well).
> > >
> > > Jonathan
> > >
> > >  
> >
> > Hi Jonathan, I am bringing this back because I am not sure if dropping
> > the processed values was the right approach here. I would like to
> > clarify before propagating some approach that might not be accurate.
> >
> > This sensor is linear, and the processed value can be obtained by simple
> > multiplications, but not just raw * scale as documented in the ABI.
> >
> > This driver is based on the veml6030, whose processed value is obtained
> > as raw * resolution, where the resolution is completely linear and is
> > obtained as sensor_resolution * integration_time / scale.
> >
> > That means that the scale is actually a gain, and the user needs to know
> > the sensor resolution provided in the datasheet (see cur_resolution in
> > veml6030.c) to get the processed value. There is a sensor resolution for
> > every pair { gain, integration_time } in the datasheet, so there is no
> > need to calculate anything, yet the resolution is not provided by the
> > driver.

Ah. The ABI is defined such that the _scale should always be the value that
you multiply _raw by to get a the defined base units.   That means it
has to take into account anything that affects that - in this case that
means it incorporates the effect of that resolution table as well.



> >
> > Nevertheless, your comment on this matter was the following:
> >  
> > > Why both raw + scale and processed?
> > >
> > > We normally only provide raw and processed for light sensors if:
> > > 1) The conversion is non linear and hard to reverse.
> > > 2) There are events that are thresholds on the raw value.
> > >
> > > Here it is linear so just provide _RAW.  
> >
> > That is still true in this case, because it is a linear, easy to reverse
> > conversion. Nevertheless, the user needs to look for the sensor
> > resolution in the datasheet and then use the given integration_time and
> > scale.
> >
> > Is that ok and desired for light sensors? 

If we are talking about illuminance or other well defined scaled channels then
no it is not.  They must give a value in Lux when we multiply _raw * _scale
(ignoring offset).  It is messily defined for intensity channels as they
have no base units to target, but the principle should be the same.



> I think that a more accurate
> > approach would have been treating the gain as a HARDWAREGAIN, which
> > would have been used to calculate the scale i.e. resolution to directly
> > apply to the raw value. In its current form, the processed value is not
> > what you get if you do raw * scale.

That sounds like a bug that needs fixing because it is not compliant
with the define ABI.  It is messy as stated for intensity because in that
case PROCESSED in the ABI as it's largely meaningless.

>  But as you specifically mentioned
> > light sensors in your comment, that might not apply here. Moreover,
> > there are only two drivers (si1133.c and vl6180.c) that use HARDWAREGAIN
> > for IIO_LIGHT, which makes me think I am over-complicating thing here.

Hardware gain must not be part of what is necessary to compute a value
in userspace. It is not the ABI specified way of doing it which means
zero software will do it right.  Any change to that is a break in 
backwards compatibility of the ABI. The cardinal rule of Linux is we
never ever do that. Note not breaking the subsystem ABI comes above
not breaking backwards compatibility of a given driver.

We have relaxed things a bit to say that in some cases it can be used
to provide some useful 'debug' info on a channel setup, but _scale * _raw
is still the only way to get to the defined ABI.

> >
> > By the way, in_illuminance_hardwaregain is not documented in the ABI,
> > only out_voltageY and in_intensity. But that is another topic.

We should indeed add it.

> >
> > The veml6030 has been around for some time and there is no way around
> > without breaking ABI, and the veml3235 has been only applied to your
> > tree and maybe it could wait to be released.

If the reporting is wrong wrt to the defined userspace ABI then it's
a bug fix and unfortunately that means we might break users :(  There
is no good way to fix these.

> >
> > If everything is ok as it is, then that's the end of the story, but if
> > the processed = raw * scale operation should apply, the veml3235 could
> > still be fixed. And when it is too late for that one too, then I could
> > follow a different approach for the veml6031x00 I recently sent to avoid
> > propagating the issue.

Definitely good to fix the veml3235 and we can also fix the older driver
as this is simply a bug (one of the few valid reasons for an ABI change).

> >
> > Thanks and best regards,
> > Javier Carrasco  
> 
> Hi Jonathan, this email might have gone unnoticed.

Yup. Sorry. I had an email sanfu and ended up with my whole inbox marked read.
May have missed a few mails as a result.

> 
> This issue is relevant for the veml6030 and veml3235, and also for the
> veml6031x00 under review, as it follows the same pattern. Do you think
> they are ok as they are? Probably not, as they don't follow the ABI
> documentation, and after reading some other reviews, HARDWAREGAIN is
> usually not the fix for something like this.
> Should the gts helpers be used instead?

They need to be fixed so they follow the ABI. GTS may well help with that
as Matti wrote it to address this complexity of too many ways of making
changes that affect the scale.  It is also valid under the ABI to restrict
things to simpler case of integration time etc being controlled directly
and the _scale just updating when you change them as long as you update
scale_avail as well.  That's ugly interface but not 'wrong' which is why
Matti created something more user friendly.

Jonathan

> 
> Thanks and best regards,
> Javier Carrasco
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 5ed860685cd1..ae7cb38fb64e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24798,6 +24798,12 @@  S:	Maintained
 F:	drivers/input/serio/userio.c
 F:	include/uapi/linux/userio.h
 
+VISHAY VEML3235 AMBIENT LIGHT SENSOR DRIVER
+M:	Javier Carrasco <javier.carrasco.cruz@gmail.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml
+F:	drivers/iio/light/veml3235.c
+
 VISHAY VEML6030 AMBIENT LIGHT SENSOR DRIVER
 M:	Javier Carrasco <javier.carrasco.cruz@gmail.com>
 S:	Maintained
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index c38b3c603ab4..c6b7ed0c61a1 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -670,6 +670,17 @@  config VCNL4035
 	  To compile this driver as a module, choose M here: the
 	  module will be called vcnl4035.
 
+config VEML3235
+	tristate "VEML3235 ambient light sensor"
+	select REGMAP_I2C
+	depends on I2C
+	help
+	  Say Y here if you want to build a driver for the Vishay VEML3235
+	  ambient light sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called veml3235.
+
 config VEML6030
 	tristate "VEML6030 and VEML6035 ambient light sensors"
 	select REGMAP_I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 321010fc0b93..f14a37442712 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -62,6 +62,7 @@  obj-$(CONFIG_TSL4531)		+= tsl4531.o
 obj-$(CONFIG_US5182D)		+= us5182d.o
 obj-$(CONFIG_VCNL4000)		+= vcnl4000.o
 obj-$(CONFIG_VCNL4035)		+= vcnl4035.o
+obj-$(CONFIG_VEML3235)		+= veml3235.o
 obj-$(CONFIG_VEML6030)		+= veml6030.o
 obj-$(CONFIG_VEML6040)		+= veml6040.o
 obj-$(CONFIG_VEML6070)		+= veml6070.o
diff --git a/drivers/iio/light/veml3235.c b/drivers/iio/light/veml3235.c
new file mode 100644
index 000000000000..fa2141cced8f
--- /dev/null
+++ b/drivers/iio/light/veml3235.c
@@ -0,0 +1,540 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * VEML3235 Ambient Light Sensor
+ *
+ * Copyright (c) 2024, Javier Carrasco <javier.carrasco.cruz@gmail.com>
+ *
+ * Datasheet: https://www.vishay.com/docs/80131/veml3235.pdf
+ * Appnote-80222: https://www.vishay.com/docs/80222/designingveml3235.pdf
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define VEML3235_REG_CONF       0x00
+#define VEML3235_REG_WH_DATA    0x04
+#define VEML3235_REG_ALS_DATA   0x05
+#define VEML3235_REG_ID         0x09
+
+#define VEML3235_CONF_SD        BIT(0)
+#define VEML3235_CONF_SD0       BIT(8)
+
+struct veml3235_rf {
+	struct regmap_field *it;
+	struct regmap_field *gain;
+	struct regmap_field *id;
+};
+
+struct veml3235_data {
+	struct i2c_client *client;
+	struct device *dev;
+	struct regmap *regmap;
+	struct veml3235_rf rf;
+	int gain;
+	int it;
+	int resolution;
+};
+
+static const int veml3235_it_times[][2] = {
+	{ 0, 50000 },
+	{ 0, 100000 },
+	{ 0, 200000 },
+	{ 0, 400000 },
+	{ 0, 800000 },
+};
+
+static const int veml3235_scale_vals[] = { 1, 2, 4, 8 };
+
+static int veml3235_power_on(struct veml3235_data *data)
+{
+	int ret;
+
+	ret = regmap_clear_bits(data->regmap, VEML3235_REG_CONF,
+				VEML3235_CONF_SD | VEML3235_CONF_SD0);
+	if (ret)
+		return ret;
+
+	/* Wait 4 ms to let processor & oscillator start correctly */
+	fsleep(4000);
+
+	return 0;
+}
+
+static int veml3235_shut_down(struct veml3235_data *data)
+{
+	return regmap_set_bits(data->regmap, VEML3235_REG_CONF,
+			       VEML3235_CONF_SD | VEML3235_CONF_SD0);
+}
+
+static void veml3235_shut_down_action(void *data)
+{
+	veml3235_shut_down(data);
+}
+
+enum veml3235_chan {
+	CH_ALS,
+	CH_WHITE,
+};
+
+static const struct iio_chan_spec veml3235_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.channel = CH_ALS,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_PROCESSED),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+					   BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+						     BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		.type = IIO_INTENSITY,
+		.channel = CH_WHITE,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+					   BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+						     BIT(IIO_CHAN_INFO_SCALE),
+	},
+};
+
+static const struct regmap_config veml3235_regmap_config = {
+	.name = "veml3235_regmap",
+	.reg_bits = 8,
+	.val_bits = 16,
+	.max_register = VEML3235_REG_ID,
+	.val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static int veml3235_get_it(struct veml3235_data *data, int *val, int *val2)
+{
+	int ret, reg;
+
+	ret = regmap_field_read(data->rf.it, &reg);
+	if (ret)
+		return ret;
+
+	switch (reg) {
+	case 0:
+		*val2 = 50000;
+		break;
+	case 1:
+		*val2 = 100000;
+		break;
+	case 2:
+		*val2 = 200000;
+		break;
+	case 3:
+		*val2 = 400000;
+		break;
+	case 4:
+		*val2 = 800000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	*val = 0;
+
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int veml3235_set_it(struct iio_dev *indio_dev, int val, int val2)
+{
+	struct veml3235_data *data = iio_priv(indio_dev);
+	int ret, new_it, it_idx;
+
+	if (val)
+		return -EINVAL;
+
+	switch (val2) {
+	case 50000:
+		new_it = 0x00;
+		it_idx = 4;
+		break;
+	case 100000:
+		new_it = 0x01;
+		it_idx = 3;
+		break;
+	case 200000:
+		new_it = 0x02;
+		it_idx = 2;
+		break;
+	case 400000:
+		new_it = 0x03;
+		it_idx = 1;
+		break;
+	case 800000:
+		new_it = 0x04;
+		it_idx = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = regmap_field_write(data->rf.it, new_it);
+	if (ret) {
+		dev_err(data->dev,
+			"failed to update integration time: %d\n", ret);
+		return ret;
+	}
+
+	if (data->it < it_idx)
+		data->resolution <<= it_idx - data->it;
+	else if (data->it > it_idx)
+		data->resolution >>= data->it - it_idx;
+
+	data->it = it_idx;
+
+	return 0;
+}
+
+static int veml3235_set_gain(struct iio_dev *indio_dev, int val, int val2)
+{
+	struct veml3235_data *data = iio_priv(indio_dev);
+	int ret, new_gain, gain_idx;
+
+	if (val2 != 0)
+		return -EINVAL;
+
+	switch (val) {
+	case 1:
+		new_gain = 0x00;
+		gain_idx = 3;
+		break;
+	case 2:
+		new_gain = 0x01;
+		gain_idx = 2;
+		break;
+	case 4:
+		new_gain = 0x03;
+		gain_idx = 1;
+		break;
+	case 8:
+		new_gain = 0x07;
+		gain_idx = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = regmap_field_write(data->rf.gain, new_gain);
+	if (ret) {
+		dev_err(data->dev, "failed to set gain: %d\n", ret);
+		return ret;
+	}
+
+	if (data->gain < gain_idx)
+		data->resolution <<= gain_idx - data->gain;
+	else if (data->gain > gain_idx)
+		data->resolution >>= data->gain - gain_idx;
+
+	data->gain = gain_idx;
+
+	return 0;
+}
+
+static int veml3235_get_gain(struct veml3235_data *data, int *val)
+{
+	int ret, reg;
+
+	ret = regmap_field_read(data->rf.gain, &reg);
+	if (ret) {
+		dev_err(data->dev, "failed to read gain %d\n", ret);
+		return ret;
+	}
+
+	switch (reg & 0x03) {
+	case 0:
+		*val = 1;
+		break;
+	case 1:
+		*val = 2;
+		break;
+	case 3:
+		*val = 4;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Double gain */
+	if (reg & 0x04)
+		*val *= 2;
+
+	return IIO_VAL_INT;
+}
+
+static int veml3235_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int *val,
+			     int *val2, long mask)
+{
+	struct veml3235_data *data = iio_priv(indio_dev);
+	struct regmap *regmap = data->regmap;
+	int ret, reg;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = regmap_read(regmap, VEML3235_REG_ALS_DATA, &reg);
+			if (ret < 0)
+				return ret;
+
+			if (mask == IIO_CHAN_INFO_PROCESSED) {
+				*val = (reg * data->resolution) / 100000;
+				*val2 = (reg * data->resolution) % 100000 * 10;
+				return IIO_VAL_INT_PLUS_MICRO;
+			}
+			*val = reg;
+			return IIO_VAL_INT;
+		case IIO_INTENSITY:
+			ret = regmap_read(regmap, VEML3235_REG_WH_DATA, &reg);
+			if (ret < 0)
+				return ret;
+
+			*val = reg;
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_INT_TIME:
+		return veml3235_get_it(data, val, val2);
+	case IIO_CHAN_INFO_SCALE:
+		return veml3235_get_gain(data, val);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int veml3235_read_avail(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       const int **vals, int *type, int *length,
+			       long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_INT_TIME:
+		*vals = (int *)&veml3235_it_times;
+		*length = 2 * ARRAY_SIZE(veml3235_it_times);
+		*type = IIO_VAL_INT_PLUS_MICRO;
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_SCALE:
+		*vals = (int *)&veml3235_scale_vals;
+		*length = ARRAY_SIZE(veml3235_scale_vals);
+		*type = IIO_VAL_INT;
+		return IIO_AVAIL_LIST;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int veml3235_write_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int val, int val2, long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_INT_TIME:
+		return veml3235_set_it(indio_dev, val, val2);
+	case IIO_CHAN_INFO_SCALE:
+		return veml3235_set_gain(indio_dev, val, val2);
+	}
+
+	return -EINVAL;
+}
+
+static void veml3235_read_id(struct veml3235_data *data)
+{
+	int ret, reg;
+
+	ret = regmap_field_read(data->rf.id,  &reg);
+	if (ret) {
+		dev_info(data->dev, "failed to read ID\n");
+		return;
+	}
+
+	if (reg != 0x35)
+		dev_info(data->dev, "Unknown ID %d\n", reg);
+}
+
+static const struct reg_field veml3235_rf_it =
+	REG_FIELD(VEML3235_REG_CONF, 4, 6);
+
+static const struct reg_field veml3235_rf_gain =
+	REG_FIELD(VEML3235_REG_CONF, 11, 13);
+
+static const struct reg_field veml3235_rf_id =
+	REG_FIELD(VEML3235_REG_ID, 0, 7);
+
+static int veml3235_regfield_init(struct veml3235_data *data)
+{
+	struct regmap *regmap = data->regmap;
+	struct device *dev = data->dev;
+	struct regmap_field *rm_field;
+	struct veml3235_rf *rf = &data->rf;
+
+	rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_it);
+	if (IS_ERR(rm_field))
+		return PTR_ERR(rm_field);
+	rf->it = rm_field;
+
+	rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_gain);
+	if (IS_ERR(rm_field))
+		return PTR_ERR(rm_field);
+	rf->gain = rm_field;
+
+	rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_id);
+	if (IS_ERR(rm_field))
+		return PTR_ERR(rm_field);
+	rf->id = rm_field;
+
+	return 0;
+}
+
+static int veml3235_hw_init(struct iio_dev *indio_dev)
+{
+	struct veml3235_data *data = iio_priv(indio_dev);
+	struct device *dev = data->dev;
+	int ret;
+
+	ret = regmap_write(data->regmap, VEML3235_REG_CONF, 0x1001);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to set configuration\n");
+
+	/*
+	 * Set gain to 1, integration time to 100 ms, and the resulting
+	 * resolution in (lux/cnt * 100000) according to the typical value
+	 * from the datasheet.
+	 */
+	ret = regmap_field_write(data->rf.gain, 0x00);
+	if (ret)
+		return dev_err_probe(data->dev, ret, "failed to set gain\n");
+
+	ret = regmap_field_write(data->rf.it, 0x01);
+	if (ret)
+		return dev_err_probe(data->dev, ret,
+				     "failed to set integration time\n");
+
+	data->gain = 3;
+	data->it = 3;
+	data->resolution = 13632;
+
+	ret = veml3235_power_on(data);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to power on\n");
+
+	return devm_add_action_or_reset(dev, veml3235_shut_down_action, data);
+}
+
+static const struct iio_info veml3235_info = {
+	.read_raw  = veml3235_read_raw,
+	.read_avail  = veml3235_read_avail,
+	.write_raw = veml3235_write_raw,
+};
+
+static int veml3235_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct veml3235_data *data;
+	struct iio_dev *indio_dev;
+	struct regmap *regmap;
+	int ret;
+
+	regmap = devm_regmap_init_i2c(client, &veml3235_regmap_config);
+	if (IS_ERR(regmap))
+		return dev_err_probe(dev, PTR_ERR(regmap),
+				     "failed to setup regmap\n");
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	data->dev = dev;
+	data->regmap = regmap;
+
+	ret = veml3235_regfield_init(data);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to init regfield\n");
+
+	ret = devm_regulator_get_enable(dev, "vdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable regulator\n");
+
+	indio_dev->name = "veml3235";
+	indio_dev->channels = veml3235_channels;
+	indio_dev->num_channels = ARRAY_SIZE(veml3235_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &veml3235_info;
+
+	veml3235_read_id(data);
+
+	ret = veml3235_hw_init(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static int veml3235_runtime_suspend(struct device *dev)
+{
+	struct veml3235_data *data = iio_priv(dev_get_drvdata(dev));
+	int ret;
+
+	ret = veml3235_shut_down(data);
+	if (ret < 0)
+		dev_err(data->dev, "failed to suspend: %d\n", ret);
+
+	return ret;
+}
+
+static int veml3235_runtime_resume(struct device *dev)
+{
+	struct veml3235_data *data = iio_priv(dev_get_drvdata(dev));
+	int ret;
+
+	ret = veml3235_power_on(data);
+	if (ret < 0)
+		dev_err(data->dev, "failed to resume: %d\n", ret);
+
+	return ret;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(veml3235_pm_ops, veml3235_runtime_suspend,
+				 veml3235_runtime_resume, NULL);
+
+static const struct of_device_id veml3235_of_match[] = {
+	{ .compatible = "vishay,veml3235" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, veml3235_of_match);
+
+static const struct i2c_device_id veml3235_id[] = {
+	{ "veml3235" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, veml3235_id);
+
+static struct i2c_driver veml3235_driver = {
+	.driver = {
+		.name = "veml3235",
+		.of_match_table = veml3235_of_match,
+		.pm = pm_ptr(&veml3235_pm_ops),
+	},
+	.probe = veml3235_probe,
+	.id_table = veml3235_id,
+};
+module_i2c_driver(veml3235_driver);
+
+MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>");
+MODULE_DESCRIPTION("VEML3235 Ambient Light Sensor");
+MODULE_LICENSE("GPL");