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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, 20 Oct 2023 13:53:47 +0300
Matti Vaittinen <mazziesaccount@xxxxxxxxx> wrote:

> The RGB + IR data can be used to calculate the illuminance value (luxes).
> Implement the equation obtained from the ROHM HW colleagues and add a
> raw light data channel outputting illuminance values in (nano) Luxes.
> 
> Both the read_raw and buffering values are supported, with the limitation
> that buffering is only allowed when a 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 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 on
> BU27010 as well.
> 
> Calculate and output illuminance values from BU27008 and BU27010.
> 
> Signed-off-by: Matti Vaittinen <mazziesaccount@xxxxxxxxx>
LGTM.
Applied to the togreg branch of iio.git but that will only be pushed out
as testing until after the merge window closes and I can rebase on rc1.

Thanks,

Jonathan

> 
> ---
> Revision history:
> v1 => v2:
> - reduce lux function parameters using an array
> - streamline code by converting channel data to meaningful values in
>   bu27008_calc_nlux() for both the raw read and buffering
> - some minor styling
> 
> 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 | 201 ++++++++++++++++++++++++++++++-
>  1 file changed, 196 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/iio/light/rohm-bu27008.c b/drivers/iio/light/rohm-bu27008.c
> index 6a6d77805091..0f010eff1981 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,169 @@ static int bu27008_read_one(struct bu27008_data *data, struct iio_dev *idev,
>  	return ret;
>  }
>  
> +#define BU27008_LUX_DATA_RED	0
> +#define BU27008_LUX_DATA_GREEN	1
> +#define BU27008_LUX_DATA_BLUE	2
> +#define BU27008_LUX_DATA_IR	3
> +#define LUX_DATA_SIZE (BU27008_NUM_HW_CHANS * sizeof(__le16))
> +
> +static int bu27008_read_lux_chans(struct bu27008_data *data, unsigned int time,
> +				  __le16 *chan_data)
> +{
> +	int ret, chan_sel, tmpret, valid;
> +
> +	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;
> +
> +	msleep(time / USEC_PER_MSEC);
> +
> +	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, chan_data,
> +			       LUX_DATA_SIZE);
> +	if (ret)
> +		goto out;
> +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. This won't overflow.
> + */
> +#define NORM_CHAN_DATA_FOR_LX_CALC(chan, gain, time) (le16_to_cpu(chan) * \
> +				   1024 * 20 / (gain) / (time))
> +static u64 bu27008_calc_nlux(struct bu27008_data *data, __le16 *lux_data,
> +		unsigned int gain, unsigned int gain_ir, unsigned int time)
> +{
> +	unsigned int red, green, blue, ir;
> +	s64 c1, c2, c3, nlux;
> +
> +	time /= 10000;
> +	ir = NORM_CHAN_DATA_FOR_LX_CALC(lux_data[BU27008_LUX_DATA_IR], gain_ir, time);
> +	red = NORM_CHAN_DATA_FOR_LX_CALC(lux_data[BU27008_LUX_DATA_RED], gain, time);
> +	green = NORM_CHAN_DATA_FOR_LX_CALC(lux_data[BU27008_LUX_DATA_GREEN], gain, time);
> +	blue = NORM_CHAN_DATA_FOR_LX_CALC(lux_data[BU27008_LUX_DATA_BLUE], gain, time);
> +
> +	if ((u64)ir * 100LLU > (u64)green * 18LLU) {
> +		c1 = -22370;
> +		c2 = 321900;
> +		c3 = -120371;
> +	} else {
> +		c1 = -10740;
> +		c2 = 305415;
> +		c3 = -129367;
> +	}
> +	nlux = c1 * red + c2 * green + c3 * blue;
> +
> +	return max_t(s64, 0, nlux);
> +}
> +
> +static int bu27008_get_time_n_gains(struct bu27008_data *data,
> +		unsigned int *gain, unsigned int *gain_ir, unsigned int *time)
> +{
> +	int ret;
> +
> +	ret = bu27008_get_gain(data, &data->gts, gain);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = bu27008_get_gain(data, &data->gts_ir, gain_ir);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = bu27008_get_int_time_us(data);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Max integration time is 400000. Fits in signed int. */
> +	*time = ret;
> +
> +	return 0;
> +}
> +
> +struct bu27008_buf {
> +	__le16 chan[BU27008_NUM_HW_CHANS];
> +	u64 lux __aligned(8);
> +	s64 ts __aligned(8);
> +};
> +
> +static int bu27008_buffer_fill_lux(struct bu27008_data *data,
> +				   struct bu27008_buf *raw)
> +{
> +	unsigned int gain, gain_ir, time;
> +	int ret;
> +
> +	ret = bu27008_get_time_n_gains(data, &gain, &gain_ir, &time);
> +	if (ret)
> +		return ret;
> +
> +	raw->lux = bu27008_calc_nlux(data, raw->chan, gain, gain_ir, time);
> +
> +	return 0;
> +}
> +
> +static int bu27008_read_lux(struct bu27008_data *data, struct iio_dev *idev,
> +			    struct iio_chan_spec const *chan,
> +			    int *val, int *val2)
> +{
> +	__le16 lux_data[BU27008_NUM_HW_CHANS];
> +	unsigned int gain, gain_ir, time;
> +	u64 nlux;
> +	int ret;
> +
> +	ret = bu27008_get_time_n_gains(data, &gain, &gain_ir, &time);
> +	if (ret)
> +		return ret;
> +
> +	ret = bu27008_read_lux_chans(data, time, lux_data);
> +	if (ret)
> +		return ret;
> +
> +	nlux = bu27008_calc_nlux(data, lux_data, gain, gain_ir, time);
> +	*val = (int)nlux;
> +	*val2 = nlux >> 32LLU;
> +
> +	return IIO_VAL_INT_64;
> +}
> +
>  static int bu27008_read_raw(struct iio_dev *idev,
>  			   struct iio_chan_spec const *chan,
>  			   int *val, int *val2, long mask)
> @@ -1018,7 +1198,10 @@ static int bu27008_read_raw(struct iio_dev *idev,
>  			return -EBUSY;
>  
>  		mutex_lock(&data->mutex);
> -		ret = bu27008_read_one(data, idev, chan, val, val2);
> +		if (chan->type == IIO_LIGHT)
> +			ret = bu27008_read_lux(data, idev, chan, val, val2);
> +		else
> +			ret = bu27008_read_one(data, idev, chan, val, val2);
>  		mutex_unlock(&data->mutex);
>  
>  		iio_device_release_direct_mode(idev);
> @@ -1026,6 +1209,11 @@ static int bu27008_read_raw(struct iio_dev *idev,
>  		return ret;
>  
>  	case IIO_CHAN_INFO_SCALE:
> +		if (chan->type == IIO_LIGHT) {
> +			*val = 0;
> +			*val2 = 1;
> +			return IIO_VAL_INT_PLUS_NANO;
> +		}
>  		ret = bu27008_get_scale(data, chan->scan_index == BU27008_IR,
>  					val, val2);
>  		if (ret)
> @@ -1236,10 +1424,7 @@ static irqreturn_t bu27008_trigger_handler(int irq, void *p)
>  	struct iio_poll_func *pf = p;
>  	struct iio_dev *idev = pf->indio_dev;
>  	struct bu27008_data *data = iio_priv(idev);
> -	struct {
> -		__le16 chan[BU27008_NUM_HW_CHANS];
> -		s64 ts __aligned(8);
> -	} raw;
> +	struct bu27008_buf raw;
>  	int ret, dummy;
>  
>  	memset(&raw, 0, sizeof(raw));
> @@ -1257,6 +1442,12 @@ static irqreturn_t bu27008_trigger_handler(int irq, void *p)
>  	if (ret < 0)
>  		goto err_read;
>  
> +	if (test_bit(BU27008_LUX, idev->active_scan_mask)) {
> +		ret = bu27008_buffer_fill_lux(data, &raw);
> +		if (ret)
> +			goto err_read;
> +	}
> +
>  	iio_push_to_buffers_with_timestamp(idev, &raw, pf->timestamp);
>  err_read:
>  	iio_trigger_notify_done(idev->trig);
> 
> base-commit: 89e2233386a5670d15908628b63e611cb03b0d03




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux