Re: [PATCH] iio: bu27008: Add processed illuminance channel

From: Jonathan Cameron
Date: Tue Oct 10 2023 - 05:40:31 EST


On Fri, 6 Oct 2023 08:01:15 +0300
Matti Vaittinen <mazziesaccount@xxxxxxxxx> wrote:

> On 10/5/23 18:14, Jonathan Cameron wrote:
> > On Mon, 2 Oct 2023 15:33:41 +0300
> > Matti Vaittinen <mazziesaccount@xxxxxxxxx> wrote:
> >
> >> The RGB + IR data can be used to calculate illuminance value (Luxes).
> >> Implement the equation obtained from the ROHM HW colleagues and add a
> >> light data channel outputting illuminance values in (nano) Luxes.
> > Units in the ABI doc for illuminance are Lux, not nanolux.
> > I'm guessing that you actually provide it in Lux but via scale.
> >
> > Make that clearer in this description if so.
>
> Yep. Also, the "processed" is misleading as I implement a raw channel. I
> did originally think I'll only implement the read_raw (as I thought
> we'll need all RGBC + IR and end up doing two accesses - which wouldn't
> be nice due to the doubled measurement time). I actually did that and
> used INT_PLUS_NANO. While implementing this I noticed the 'clear' data
> was not used - and thought I might as well support buffering when RGB+IR
> are enabled. I needed the scale to get the buffered values to decent
> format though - so I converted channel to raw one and added scale. The
> commit title still contains the 'processed' which reflects the original
> thinking. Thanks for pointing out the confusion.
>
> >> Both the read_raw and buffering values is supported, with the limitation
> >> that buffering is only allowed when suitable scan-mask is used. (RGB+IR,
> >> no clear).
> >>
> >> The equation has been developed by ROHM HW colleagues for open air sensor.
> >> Adding any lens to the sensor is likely to impact to the used c1, c2, c3
> >> coefficients. Also, The output values have only been tested on BU27008.
> >>
> >> According to the HW colleagues, the very same equation should work also
> >> on BU27010.
> >>
> >> Calculate and output illuminance values from BU27008 and BU27010.
> >>
> >> Signed-off-by: Matti Vaittinen <mazziesaccount@xxxxxxxxx>
> >>
> >
> > A few comments inline, but in general looks fine to me.
>
> Thanks Jonathan. I had to give also the BU27008 sensor away for a while.
> I guess I won't send the next version until I am able to do some very
> basic testing even if the changes were minor. That's probably sometime
> next week.
>
> >
> > Jonathan
> >
> >> ---
> >>
> >> I did very dummy testing at very normal daylight inside a building. No
> >> special equipments were used - I simply compared values computed from
> >> BU27008 RGB+IR channels, to values displayed by the ALS in my mobile
> >> phone. Results were roughly the same (around 400 lux). Couldn't repeat
> >> test on BU27010, but the data it outputs should be same format as
> >> BU27008 data so equation should work for both sensors.
> >> ---
> >> drivers/iio/light/rohm-bu27008.c | 216 ++++++++++++++++++++++++++++++-
> >> 1 file changed, 211 insertions(+), 5 deletions(-)
> >>
> >> diff --git a/drivers/iio/light/rohm-bu27008.c b/drivers/iio/light/rohm-bu27008.c
> >> index 6a6d77805091..d480cf761377 100644
> >> --- a/drivers/iio/light/rohm-bu27008.c
> >> +++ b/drivers/iio/light/rohm-bu27008.c
> >> @@ -130,6 +130,7 @@
> >> * @BU27008_BLUE: Blue channel. Via data2 (when used).
> >> * @BU27008_CLEAR: Clear channel. Via data2 or data3 (when used).
> >> * @BU27008_IR: IR channel. Via data3 (when used).
> >> + * @BU27008_LUX: Illuminance channel, computed using RGB and IR.
> >> * @BU27008_NUM_CHANS: Number of channel types.
> >> */
> >> enum bu27008_chan_type {
> >> @@ -138,6 +139,7 @@ enum bu27008_chan_type {
> >> BU27008_BLUE,
> >> BU27008_CLEAR,
> >> BU27008_IR,
> >> + BU27008_LUX,
> >> BU27008_NUM_CHANS
> >> };
> >>
> >> @@ -172,6 +174,8 @@ static const unsigned long bu27008_scan_masks[] = {
> >> ALWAYS_SCANNABLE | BIT(BU27008_CLEAR) | BIT(BU27008_IR),
> >> /* buffer is R, G, B, IR */
> >> ALWAYS_SCANNABLE | BIT(BU27008_BLUE) | BIT(BU27008_IR),
> >> + /* buffer is R, G, B, IR, LUX */
> >> + ALWAYS_SCANNABLE | BIT(BU27008_BLUE) | BIT(BU27008_IR) | BIT(BU27008_LUX),
> >> 0
> >> };
> >>
> >> @@ -331,6 +335,19 @@ static const struct iio_chan_spec bu27008_channels[] = {
> >> * Hence we don't advertise available ones either.
> >> */
> >> BU27008_CHAN(IR, DATA3, 0),
> >> + {
> >> + .type = IIO_LIGHT,
> >> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> >> + BIT(IIO_CHAN_INFO_SCALE),
> >> + .channel = BU27008_LUX,
> >> + .scan_index = BU27008_LUX,
> >> + .scan_type = {
> >> + .sign = 'u',
> >> + .realbits = 64,
> >> + .storagebits = 64,
> >> + .endianness = IIO_CPU,
> >> + },
> >> + },
> >> IIO_CHAN_SOFT_TIMESTAMP(BU27008_NUM_CHANS),
> >> };
> >>
> >> @@ -1004,6 +1021,183 @@ static int bu27008_read_one(struct bu27008_data *data, struct iio_dev *idev,
> >> return ret;
> >> }
> >>
> >> +static int bu27008_get_rgb_ir(struct bu27008_data *data, unsigned int *red,
> >> + unsigned int *green, unsigned int *blue, unsigned int *ir)
> >> +{
> >> + int ret, chan_sel, int_time, tmpret, valid;
> >> + __le16 chans[BU27008_NUM_HW_CHANS];
> >> +
> >> + chan_sel = BU27008_BLUE2_IR3 << (ffs(data->cd->chan_sel_mask) - 1);
> >> +
> >> + ret = regmap_update_bits(data->regmap, data->cd->chan_sel_reg,
> >> + data->cd->chan_sel_mask, chan_sel);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + ret = bu27008_meas_set(data, true);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + ret = bu27008_get_int_time_us(data);
> >> + if (ret < 0)
> >> + int_time = BU27008_MEAS_TIME_MAX_MS;
> >> + else
> >> + int_time = ret / USEC_PER_MSEC;
> >> +
> >> + msleep(int_time);
> >> +
> >> + ret = regmap_read_poll_timeout(data->regmap, data->cd->valid_reg,
> >> + valid, (valid & BU27008_MASK_VALID),
> >> + BU27008_VALID_RESULT_WAIT_QUANTA_US,
> >> + BU27008_MAX_VALID_RESULT_WAIT_US);
> >> + if (ret)
> >> + goto out;
> >> +
> >> + ret = regmap_bulk_read(data->regmap, BU27008_REG_DATA0_LO, chans,
> >> + sizeof(chans));
> >> + if (ret)
> >> + goto out;
> >> +
> >> + *red = le16_to_cpu(chans[0]);
> >> + *green = le16_to_cpu(chans[1]);
> >> + *blue = le16_to_cpu(chans[2]);
> >> + *ir = le16_to_cpu(chans[3]);
> >
> > I'd be tempted to use an array + definitely pass them as u16 rather
> > than unsigned int.
>
> I'm not really convinced the u16 is better here. We need the 32 bits
> later for the calculations - and (afaics) using natural size int for
> arguments shouldn't harm. We read the channel data to correct type array
> so code should be pretty clear as to what we have in HW.

ok. I don't like lack of range clamping - so at the point of the caller
I can't immediately see that these will be sub 16bit value. Not htat
important I guess.

>
> Also, I think that having an array obfuscates what element is which
> channel because these ICs didn't have the 1 to 1 mapping from channel
> index to colour. I was thinking of adding a struct for this but decided
> to just keep it simple and clear.
A struct or array + enum would work.
I just don't much like lots of very similar parameters.
>
> >> +
> >> +out:
> >> + tmpret = bu27008_meas_set(data, false);
> >> + if (tmpret)
> >> + dev_warn(data->dev, "Stopping measurement failed\n");
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +/*
> >> + * Following equation for computing lux out of register values was given by
> >> + * ROHM HW colleagues;
> >> + *
> >> + * Red = RedData*1024 / Gain * 20 / meas_mode
> >> + * Green = GreenData* 1024 / Gain * 20 / meas_mode
> >> + * Blue = BlueData* 1024 / Gain * 20 / meas_mode
> >> + * IR = IrData* 1024 / Gain * 20 / meas_mode
> >> + *
> >> + * where meas_mode is the integration time in mS / 10
> >> + *
> >> + * IRratio = (IR > 0.18 * Green) ? 0 : 1
> >> + *
> >> + * Lx = max(c1*Red + c2*Green + c3*Blue,0)
> >> + *
> >> + * for
> >> + * IRratio 0: c1 = -0.00002237, c2 = 0.0003219, c3 = -0.000120371
> >> + * IRratio 1: c1 = -0.00001074, c2 = 0.000305415, c3 = -0.000129367
> >> + */
> >> +
> >> +/*
> >> + * The max chan data is 0xffff. When we multiply it by 1024 * 20, we'll get
> >> + * 0x4FFFB000 which still fits in 32-bit integer. So this can't overflow.
> >> + */
> >> +#define NORM_CHAN_DATA_FOR_LX_CALC(chan, gain, time) ((chan) * 1024 * 20 / \
> >> + (gain) / (time))
> >> +static u64 bu27008_calc_nlux(struct bu27008_data *data, unsigned int red,
> >> + unsigned int green, unsigned int blue, unsigned int ir,
> >> + unsigned int gain, unsigned int gain_ir, unsigned int time)
> >> +{
> >> + s64 c1, c2, c3, nlux;
> >> +
> >> + time /= 10000;
> >> + ir = NORM_CHAN_DATA_FOR_LX_CALC(ir, gain_ir, time);
> >> + red = NORM_CHAN_DATA_FOR_LX_CALC(red, gain, time);
> >> + green = NORM_CHAN_DATA_FOR_LX_CALC(green, gain, time);
> >> + blue = NORM_CHAN_DATA_FOR_LX_CALC(blue, gain, time);
>
> > I'd prefer to see the inputs parameters and the normalized version given different
> > names. Also the inputs are still u16, so nice to reflect that here.
>
> So, you suggest we bring the data as u16 until here and only here we
> assign it into 32bit variables when doing the 'normalization'? I'm sure
> it works, but I dislike doing computations like multiplying u16 by u32
> as I never know (out of my head) how the implicit type conversions work
> and if we get some results cropped. Adding the casts to computation make
> it less pretty for my eyes while having all variables in large enough
> types does not leave me wondering if it works correctly and if explicit
> casts are needed.
>
> I am not strongly opposing this though if you insist - I am sure I can
> at the end of the day get the code right - but I am afraid I will later
> look at the code and wonder if it contains hideous issues...

This isn't particularly important either way. My gut would have
been to keep them as __le16 to the point where the maths happens
but I don't mind it happening elsewhere.

I do want different names though given the inputs and outputs are
different 'things'.


>
> > Also when doing normalization I'd used fixed with types so there is no
> > confusion over what was intended (here u32)
>
> Ok.
>
> >
> >> +
> >> + if ((u64)ir * 100LLU > 18LLU * (u64)green) {
> >
> > Putting scaling for ir to the right and green to the left is
> > unusual. I'd chose one side and stick to it.
>
> Sorry Jonathan. I must be a bit slow today but I just seem to not be
> able to think how you would like to have this? I think this line is
> somehow mappable to the:

if ((u64)ir * 100LLU > (u64)green * 18LLU)
or
if ((100LLU * (u64)ir > 18LLU * (u64)green)

Either is fine. Just don't like the scaling from different sides of
the variable. I can see how you got there from 0.18 * Green but equally
valid to premultiply by 100 as it is to post multiply (when doing the
maths on paper).

>
> IRratio = (IR > 0.18 * Green) ? 0 : 1
> formula I got from HW colleagues and added in the comment preceding the
> function.
>