linux/drivers/iio/light/rohm-bu27034.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * BU27034 ROHM Ambient Light Sensor
 *
 * Copyright (c) 2023, ROHM Semiconductor.
 * https://fscdn.rohm.com/en/products/databook/datasheet/ic/sensor/light/bu27034nuc-e.pdf
 */

#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/units.h>

#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/iio-gts-helper.h>
#include <linux/iio/kfifo_buf.h>

#define BU27034_REG_SYSTEM_CONTROL
#define BU27034_MASK_SW_RESET
#define BU27034_MASK_PART_ID
#define BU27034_ID
#define BU27034_REG_MODE_CONTROL1
#define BU27034_MASK_MEAS_MODE

#define BU27034_REG_MODE_CONTROL2
#define BU27034_MASK_D01_GAIN
#define BU27034_MASK_D2_GAIN_HI
#define BU27034_MASK_D2_GAIN_LO

#define BU27034_REG_MODE_CONTROL3
#define BU27034_REG_MODE_CONTROL4
#define BU27034_MASK_MEAS_EN
#define BU27034_MASK_VALID
#define BU27034_REG_DATA0_LO
#define BU27034_REG_DATA1_LO
#define BU27034_REG_DATA2_LO
#define BU27034_REG_DATA2_HI
#define BU27034_REG_MANUFACTURER_ID
#define BU27034_REG_MAX

/*
 * The BU27034 does not have interrupt to trigger the data read when a
 * measurement has finished. Hence we poll the VALID bit in a thread. We will
 * try to wake the thread BU27034_MEAS_WAIT_PREMATURE_MS milliseconds before
 * the expected sampling time to prevent the drifting.
 *
 * If we constantly wake up a bit too late we would eventually skip a sample.
 * And because the sleep can't wake up _exactly_ at given time this would be
 * inevitable even if the sensor clock would be perfectly phase-locked to CPU
 * clock - which we can't say is the case.
 *
 * This is still fragile. No matter how big advance do we have, we will still
 * risk of losing a sample because things can in a rainy-day scenario be
 * delayed a lot. Yet, more we reserve the time for polling, more we also lose
 * the performance by spending cycles polling the register. So, selecting this
 * value is a balancing dance between severity of wasting CPU time and severity
 * of losing samples.
 *
 * In most cases losing the samples is not _that_ crucial because light levels
 * tend to change slowly.
 *
 * Other option that was pointed to me would be always sleeping 1/2 of the
 * measurement time, checking the VALID bit and just sleeping again if the bit
 * was not set. That should be pretty tolerant against missing samples due to
 * the scheduling delays while also not wasting much of cycles for polling.
 * Downside is that the time-stamps would be very inaccurate as the wake-up
 * would not really be tied to the sensor toggling the valid bit. This would also
 * result 'jumps' in the time-stamps when the delay drifted so that wake-up was
 * performed during the consecutive wake-ups (Or, when sensor and CPU clocks
 * were very different and scheduling the wake-ups was very close to given
 * timeout - and when the time-outs were very close to the actual sensor
 * sampling, Eg. once in a blue moon, two consecutive time-outs would occur
 * without having a sample ready).
 */
#define BU27034_MEAS_WAIT_PREMATURE_MS
#define BU27034_DATA_WAIT_TIME_US
#define BU27034_TOTAL_DATA_WAIT_TIME_US

#define BU27034_RETRY_LIMIT

enum {};

static const unsigned long bu27034_scan_masks[] =;

/*
 * Available scales with gain 1x - 4096x, timings 55, 100, 200, 400 mS
 * Time impacts to gain: 1x, 2x, 4x, 8x.
 *
 * => Max total gain is HWGAIN * gain by integration time (8 * 4096) = 32768
 *
 * Using NANO precision for scale we must use scale 64x corresponding gain 1x
 * to avoid precision loss. (32x would result scale 976 562.5(nanos).
 */
#define BU27034_SCALE_1X

/* See the data sheet for the "Gain Setting" table */
#define BU27034_GSEL_1X
#define BU27034_GSEL_4X
#define BU27034_GSEL_16X
#define BU27034_GSEL_32X
#define BU27034_GSEL_64X
#define BU27034_GSEL_256X
#define BU27034_GSEL_512X
#define BU27034_GSEL_1024X
#define BU27034_GSEL_2048X
#define BU27034_GSEL_4096X

/* Available gain settings */
static const struct iio_gain_sel_pair bu27034_gains[] =;

/*
 * The IC has 5 modes for sampling time. 5 mS mode is exceptional as it limits
 * the data collection to data0-channel only and cuts the supported range to
 * 10 bit. It is not supported by the driver.
 *
 * "normal" modes are 55, 100, 200 and 400 mS modes - which do have direct
 * multiplying impact to the register values (similar to gain).
 *
 * This means that if meas-mode is changed for example from 400 => 200,
 * the scale is doubled. Eg, time impact to total gain is x1, x2, x4, x8.
 */
#define BU27034_MEAS_MODE_100MS
#define BU27034_MEAS_MODE_55MS
#define BU27034_MEAS_MODE_200MS
#define BU27034_MEAS_MODE_400MS

static const struct iio_itime_sel_mul bu27034_itimes[] =;

#define BU27034_CHAN_DATA(_name, _ch2)

static const struct iio_chan_spec bu27034_channels[] =;

struct bu27034_data {};

static const struct regmap_range bu27034_volatile_ranges[] =;

static const struct regmap_access_table bu27034_volatile_regs =;

static const struct regmap_range bu27034_read_only_ranges[] =;

static const struct regmap_access_table bu27034_ro_regs =;

static const struct regmap_config bu27034_regmap =;

struct bu27034_gain_check {};

static int bu27034_get_gain_sel(struct bu27034_data *data, int chan)
{}

static int bu27034_get_gain(struct bu27034_data *data, int chan, int *gain)
{}

static int bu27034_get_int_time(struct bu27034_data *data)
{}

static int _bu27034_get_scale(struct bu27034_data *data, int channel, int *val,
			      int *val2)
{}

static int bu27034_get_scale(struct bu27034_data *data, int channel, int *val,
			      int *val2)
{}

/* Caller should hold the lock to protect lux reading */
static int bu27034_write_gain_sel(struct bu27034_data *data, int chan, int sel)
{}

static int bu27034_set_gain(struct bu27034_data *data, int chan, int gain)
{}

/* Caller should hold the lock to protect data->int_time */
static int bu27034_set_int_time(struct bu27034_data *data, int time)
{}

/*
 * We try to change the time in such way that the scale is maintained for
 * given channels by adjusting gain so that it compensates the time change.
 */
static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us)
{}

static int bu27034_set_scale(struct bu27034_data *data, int chan,
			    int val, int val2)
{}

/*
 * for (D1/D0 < 0.87):
 * lx = 0.004521097 * D1 - 0.002663996 * D0 +
 *	0.00012213 * D1 * D1 / D0
 *
 * =>	115.7400832 * ch1 / gain1 / mt -
 *	68.1982976 * ch0 / gain0 / mt +
 *	0.00012213 * 25600 * (ch1 / gain1 / mt) * 25600 *
 *	(ch1 /gain1 / mt) / (25600 * ch0 / gain0 / mt)
 *
 * A =	0.00012213 * 25600 * (ch1 /gain1 / mt) * 25600 *
 *	(ch1 /gain1 / mt) / (25600 * ch0 / gain0 / mt)
 * =>	0.00012213 * 25600 * (ch1 /gain1 / mt) *
 *	(ch1 /gain1 / mt) / (ch0 / gain0 / mt)
 * =>	0.00012213 * 25600 * (ch1 / gain1) * (ch1 /gain1 / mt) /
 *	(ch0 / gain0)
 * =>	0.00012213 * 25600 * (ch1 / gain1) * (ch1 /gain1 / mt) *
 *	gain0 / ch0
 * =>	3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / mt /ch0
 *
 * lx = (115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0) /
 *	mt + A
 * =>	(115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0) /
 *	mt + 3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / mt /
 *	ch0
 *
 * =>	(115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0 +
 *	  3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / ch0) /
 *	  mt
 *
 * For (0.87 <= D1/D0 < 1.00)
 * lx = (0.001331* D0 + 0.0000354 * D1) * ((D1/D0 – 0.87) * (0.385) + 1)
 * =>	(0.001331 * 256 * 100 * ch0 / gain0 / mt + 0.0000354 * 256 *
 *	100 * ch1 / gain1 / mt) * ((D1/D0 -  0.87) * (0.385) + 1)
 * =>	(34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
 *	((D1/D0 -  0.87) * (0.385) + 1)
 * =>	(34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
 *	(0.385 * D1/D0 - 0.66505)
 * =>	(34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
 *	(0.385 * 256 * 100 * ch1 / gain1 / mt / (256 * 100 * ch0 / gain0 / mt) - 0.66505)
 * =>	(34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
 *	(9856 * ch1 / gain1 / mt / (25600 * ch0 / gain0 / mt) + 0.66505)
 * =>	13.118336 * ch1 / (gain1 * mt)
 *	+ 22.66064768 * ch0 / (gain0 * mt)
 *	+ 8931.90144 * ch1 * ch1 * gain0 /
 *	  (25600 * ch0 * gain1 * gain1 * mt)
 *	+ 0.602694912 * ch1 / (gain1 * mt)
 *
 * =>	[0.3489024 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1)
 *	 + 22.66064768 * ch0 / gain0
 *	 + 13.721030912 * ch1 / gain1
 *	] / mt
 *
 * For (D1/D0 >= 1.00)
 *
 * lx	= (0.001331* D0 + 0.0000354 * D1) * ((D1/D0 – 2.0) * (-0.05) + 1)
 *	=> (0.001331* D0 + 0.0000354 * D1) * (-0.05D1/D0 + 1.1)
 *	=> (0.001331 * 256 * 100 * ch0 / gain0 / mt + 0.0000354 * 256 *
 *	   100 * ch1 / gain1 / mt) * (-0.05D1/D0 + 1.1)
 *	=> (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
 *	   (-0.05 * 256 * 100 * ch1 / gain1 / mt / (256 * 100 * ch0 / gain0 / mt) + 1.1)
 *	=> (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) *
 *	   (-1280 * ch1 / (gain1 * mt * 25600 * ch0 / gain0 / mt) + 1.1)
 *	=> (34.0736 * ch0 * -1280 * ch1 * gain0 * mt /( gain0 * mt * gain1 * mt * 25600 * ch0)
 *	    + 34.0736 * 1.1 * ch0 / (gain0 * mt)
 *	    + 0.90624 * ch1 * -1280 * ch1 *gain0 * mt / (gain1 * mt *gain1 * mt * 25600 * ch0)
 *	    + 1.1 * 0.90624 * ch1 / (gain1 * mt)
 *	=> -43614.208 * ch1 / (gain1 * mt * 25600)
 *	    + 37.48096  ch0 / (gain0 * mt)
 *	    - 1159.9872 * ch1 * ch1 * gain0 / (gain1 * gain1 * mt * 25600 * ch0)
 *	    + 0.996864 ch1 / (gain1 * mt)
 *	=> [
 *		- 0.045312 * ch1 * ch1 * gain0 / (gain1 * gain1 * ch0)
 *		- 0.706816 * ch1 / gain1
 *		+ 37.48096  ch0 /gain0
 *	   ] * mt
 *
 *
 * So, the first case (D1/D0 < 0.87) can be computed to a form:
 *
 * lx = (3.126528 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) +
 *	 115.7400832 * ch1 / gain1 +
 *	-68.1982976 * ch0 / gain0
 *	 / mt
 *
 * Second case (0.87 <= D1/D0 < 1.00) goes to form:
 *
 *	=> [0.3489024 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) +
 *	    13.721030912 * ch1 / gain1 +
 *	    22.66064768 * ch0 / gain0
 *	   ] / mt
 *
 * Third case (D1/D0 >= 1.00) goes to form:
 *	=> [-0.045312 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) +
 *	    -0.706816 * ch1 / gain1 +
 *	    37.48096  ch0 /(gain0
 *	   ] / mt
 *
 * This can be unified to format:
 * lx = [
 *	 A * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) +
 *	 B * ch1 / gain1 +
 *	 C * ch0 / gain0
 *	] / mt
 *
 * For case 1:
 * A = 3.126528,
 * B = 115.7400832
 * C = -68.1982976
 *
 * For case 2:
 * A = 0.3489024
 * B = 13.721030912
 * C = 22.66064768
 *
 * For case 3:
 * A = -0.045312
 * B = -0.706816
 * C = 37.48096
 */

struct bu27034_lx_coeff {};

static inline u64 gain_mul_div_helper(u64 val, unsigned int gain,
				      unsigned int div)
{}

static u64 bu27034_fixp_calc_t1_64bit(unsigned int coeff, unsigned int ch0,
				      unsigned int ch1, unsigned int gain0,
				      unsigned int gain1)
{}

static u64 bu27034_fixp_calc_t1(unsigned int coeff, unsigned int ch0,
				unsigned int ch1, unsigned int gain0,
				unsigned int gain1)
{}

static u64 bu27034_fixp_calc_t23(unsigned int coeff, unsigned int ch,
				 unsigned int gain)
{}

static int bu27034_fixp_calc_lx(unsigned int ch0, unsigned int ch1,
				unsigned int gain0, unsigned int gain1,
				unsigned int meastime, int coeff_idx)
{}

static bool bu27034_has_valid_sample(struct bu27034_data *data)
{}

/*
 * Reading the register where VALID bit is clears this bit. (So does changing
 * any gain / integration time configuration registers) The bit gets
 * set when we have acquired new data. We use this bit to indicate data
 * validity.
 */
static void bu27034_invalidate_read_data(struct bu27034_data *data)
{}

static int bu27034_read_result(struct bu27034_data *data, int chan, int *res)
{}

static int bu27034_get_result_unlocked(struct bu27034_data *data, __le16 *res,
				       int size)
{}

static int bu27034_meas_set(struct bu27034_data *data, bool en)
{}

static int bu27034_get_single_result(struct bu27034_data *data, int chan,
				     int *val)
{}

/*
 * The formula given by vendor for computing luxes out of data0 and data1
 * (in open air) is as follows:
 *
 * Let's mark:
 * D0 = data0/ch0_gain/meas_time_ms * 25600
 * D1 = data1/ch1_gain/meas_time_ms * 25600
 *
 * Then:
 * if (D1/D0 < 0.87)
 *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 0.87) * 3.45 + 1)
 * else if (D1/D0 < 1)
 *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 0.87) * 0.385 + 1)
 * else
 *	lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 2) * -0.05 + 1)
 *
 * We use it here. Users who have for example some colored lens
 * need to modify the calculation but I hope this gives a starting point for
 * those working with such devices.
 */

static int bu27034_calc_mlux(struct bu27034_data *data, __le16 *res, int *val)
{}

static int bu27034_get_mlux(struct bu27034_data *data, int chan, int *val)
{}

static int bu27034_read_raw(struct iio_dev *idev,
			   struct iio_chan_spec const *chan,
			   int *val, int *val2, long mask)
{}

static int bu27034_write_raw_get_fmt(struct iio_dev *indio_dev,
				     struct iio_chan_spec const *chan,
				     long mask)
{}

static int bu27034_write_raw(struct iio_dev *idev,
			     struct iio_chan_spec const *chan,
			     int val, int val2, long mask)
{}

static int bu27034_read_avail(struct iio_dev *idev,
			      struct iio_chan_spec const *chan, const int **vals,
			      int *type, int *length, long mask)
{}

static const struct iio_info bu27034_info =;

static int bu27034_chip_init(struct bu27034_data *data)
{}

static int bu27034_wait_for_data(struct bu27034_data *data)
{}

static int bu27034_buffer_thread(void *arg)
{}

static int bu27034_buffer_enable(struct iio_dev *idev)
{}

static int bu27034_buffer_disable(struct iio_dev *idev)
{}

static const struct iio_buffer_setup_ops bu27034_buffer_ops =;

static int bu27034_probe(struct i2c_client *i2c)
{}

static const struct of_device_id bu27034_of_match[] =;
MODULE_DEVICE_TABLE(of, bu27034_of_match);

static struct i2c_driver bu27034_i2c_driver =;
module_i2c_driver();

MODULE_LICENSE();
MODULE_AUTHOR();
MODULE_DESCRIPTION();
MODULE_IMPORT_NS();