1031 lines
27 KiB
C
1031 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* VEML6046X00 High Accuracy RGBIR Color Sensor
|
|
*
|
|
* Copyright (c) 2025 Andreas Klinger <ak@it-klinger.de>
|
|
*/
|
|
|
|
#include <linux/array_size.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/bits.h>
|
|
#include <linux/dev_printk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/time.h>
|
|
#include <linux/types.h>
|
|
#include <linux/units.h>
|
|
|
|
#include <asm/byteorder.h>
|
|
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/trigger_consumer.h>
|
|
#include <linux/iio/triggered_buffer.h>
|
|
|
|
/*
|
|
* Device registers
|
|
* Those which are accessed as bulk io are omitted
|
|
*/
|
|
#define VEML6046X00_REG_CONF0 0x00
|
|
#define VEML6046X00_REG_CONF1 0x01
|
|
#define VEML6046X00_REG_THDH 0x04
|
|
#define VEML6046X00_REG_THDL 0x06
|
|
#define VEML6046X00_REG_R 0x10
|
|
#define VEML6046X00_REG_G 0x12
|
|
#define VEML6046X00_REG_B 0x14
|
|
#define VEML6046X00_REG_IR 0x16
|
|
#define VEML6046X00_REG_ID 0x18
|
|
#define VEML6046X00_REG_INT 0x1A
|
|
#define VEML6046X00_REG_INT_H 0x1B
|
|
|
|
/* Bit masks for specific functionality */
|
|
#define VEML6046X00_CONF0_ON_0 BIT(0)
|
|
#define VEML6046X00_CONF0_INT BIT(1)
|
|
#define VEML6046X00_CONF0_AF_TRIG BIT(2)
|
|
#define VEML6046X00_CONF0_AF BIT(3)
|
|
#define VEML6046X00_CONF0_IT GENMASK(6, 4)
|
|
#define VEML6046X00_CONF1_CAL BIT(0)
|
|
#define VEML6046X00_CONF1_PERS GENMASK(2, 1)
|
|
#define VEML6046X00_CONF1_GAIN GENMASK(4, 3)
|
|
#define VEML6046X00_CONF1_PD_D2 BIT(6)
|
|
#define VEML6046X00_CONF1_ON_1 BIT(7)
|
|
#define VEML6046X00_INT_TH_H BIT(1)
|
|
#define VEML6046X00_INT_TH_L BIT(2)
|
|
#define VEML6046X00_INT_DRDY BIT(3)
|
|
#define VEML6046X00_INT_MASK \
|
|
(VEML6046X00_INT_TH_H | VEML6046X00_INT_TH_L | VEML6046X00_INT_DRDY)
|
|
|
|
#define VEML6046X00_GAIN_1 0x0
|
|
#define VEML6046X00_GAIN_2 0x1
|
|
#define VEML6046X00_GAIN_0_66 0x2
|
|
#define VEML6046X00_GAIN_0_5 0x3
|
|
|
|
#define VEML6046X00_PD_2_2 0x0
|
|
#define VEML6046X00_PD_1_2 BIT(6)
|
|
|
|
/* Autosuspend delay */
|
|
#define VEML6046X00_AUTOSUSPEND_MS (3 * MSEC_PER_SEC)
|
|
|
|
enum veml6046x00_scan {
|
|
VEML6046X00_SCAN_R,
|
|
VEML6046X00_SCAN_G,
|
|
VEML6046X00_SCAN_B,
|
|
VEML6046X00_SCAN_IR,
|
|
VEML6046X00_SCAN_TIMESTAMP,
|
|
};
|
|
|
|
/**
|
|
* struct veml6046x00_rf - Regmap field of configuration registers.
|
|
* @int_en: Interrupt enable of green channel.
|
|
* @mode: Mode of operation.
|
|
* Driver uses always Active force mode.
|
|
* @trig: Trigger to be set in active force mode for starting
|
|
* measurement.
|
|
* @it: Integration time.
|
|
* @pers: Persistense - Number of threshold crossing for triggering
|
|
* interrupt.
|
|
*/
|
|
struct veml6046x00_rf {
|
|
struct regmap_field *int_en;
|
|
struct regmap_field *mode;
|
|
struct regmap_field *trig;
|
|
struct regmap_field *it;
|
|
struct regmap_field *pers;
|
|
};
|
|
|
|
/**
|
|
* struct veml6046x00_data - Private data of driver.
|
|
* @regmap: Regmap definition of sensor.
|
|
* @trig: Industrial-IO trigger.
|
|
* @rf: Regmap field of configuration.
|
|
*/
|
|
struct veml6046x00_data {
|
|
struct regmap *regmap;
|
|
struct iio_trigger *trig;
|
|
struct veml6046x00_rf rf;
|
|
};
|
|
|
|
/**
|
|
* DOC: Valid integration times (IT)
|
|
*
|
|
* static const int veml6046x00_it contains the array with valid IT.
|
|
*
|
|
* Register value to be read or written in regmap_field it on veml6046x00 is
|
|
* identical with array index.
|
|
* This means there is no separate translation table between valid integration
|
|
* times and register values needed. The index of the array is identical with
|
|
* the register value.
|
|
*
|
|
* The array is in the form as expected by the callback of the sysfs attribute
|
|
* integration_time_available (IIO_CHAN_INFO_INT_TIME). So there is no
|
|
* additional conversion needed.
|
|
*/
|
|
static const int veml6046x00_it[][2] = {
|
|
{ 0, 3125 },
|
|
{ 0, 6250 },
|
|
{ 0, 12500 },
|
|
{ 0, 25000 },
|
|
{ 0, 50000 },
|
|
{ 0, 100000 },
|
|
{ 0, 200000 },
|
|
{ 0, 400000 },
|
|
};
|
|
|
|
/**
|
|
* DOC: Handling of gain and photodiode size (PD)
|
|
*
|
|
* Gains here in the driver are not exactly the same as in the datasheet of the
|
|
* sensor. The gain in the driver is a combination of the gain of the sensor
|
|
* with the photodiode size (PD).
|
|
* The following combinations are possible:
|
|
* gain(driver) = gain(sensor) * PD
|
|
* 0.25 = x0.5 * 1/2
|
|
* 0.33 = x0.66 * 1/2
|
|
* 0.5 = x0.5 * 2/2
|
|
* 0.66 = x0.66 * 2/2
|
|
* 1 = x1 * 2/2
|
|
* 2 = x2 * 2/2
|
|
*/
|
|
|
|
/**
|
|
* struct veml6046x00_gain_pd - Translation of gain and photodiode size (PD).
|
|
* @gain_sen: Gain used in the sensor as described in the datasheet of the
|
|
* sensor
|
|
* @pd: Photodiode size in the sensor
|
|
*
|
|
* This is the translation table from the gain used in the driver (and also used
|
|
* by the userspace interface in sysfs) to the gain and PD used in the sensor
|
|
* hardware.
|
|
*
|
|
* There are six gain values visible to the user (0.25 .. 2) which translate to
|
|
* two different gains in the sensor hardware (x0.5 .. x2) and two PD (1/2 and
|
|
* 2/2). Theoretical are there eight combinations, but gain values 0.5 and 1 are
|
|
* doubled and therefore the combination with the larger PD (2/2) is taken as
|
|
* more photodiode cells are supposed to deliver a more precise result.
|
|
*/
|
|
struct veml6046x00_gain_pd {
|
|
unsigned int gain_sen;
|
|
unsigned int pd;
|
|
};
|
|
|
|
static const struct veml6046x00_gain_pd veml6046x00_gain_pd[] = {
|
|
{ .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_1_2 },
|
|
{ .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_1_2 },
|
|
{ .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_2_2 },
|
|
{ .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_2_2 },
|
|
{ .gain_sen = VEML6046X00_GAIN_1, .pd = VEML6046X00_PD_2_2 },
|
|
{ .gain_sen = VEML6046X00_GAIN_2, .pd = VEML6046X00_PD_2_2 },
|
|
};
|
|
|
|
/**
|
|
* DOC: Factors for calculation of lux
|
|
*
|
|
* static const int veml6046x00_it_gains contains the factors for calculation of
|
|
* lux.
|
|
*
|
|
* Depending on the set up integration time (IT), gain and photodiode size (PD)
|
|
* the measured raw values are different if the light is constant. As the gain
|
|
* and PD are already coupled in the driver (see &struct veml6046x00_gain_pd)
|
|
* there are two dimensions remaining: IT and gain(driver).
|
|
*
|
|
* The array of available factors for a certain IT are grouped together in the
|
|
* same form as expected by the callback of scale_available
|
|
* (IIO_CHAN_INFO_SCALE).
|
|
*
|
|
* Factors for lux / raw count are taken directly from the datasheet.
|
|
*/
|
|
static const int veml6046x00_it_gains[][6][2] = {
|
|
/* integration time: 3.125 ms */
|
|
{
|
|
{ 5, 376000 }, /* gain: x0.25 */
|
|
{ 4, 72700 }, /* gain: x0.33 */
|
|
{ 2, 688000 }, /* gain: x0.5 */
|
|
{ 2, 36400 }, /* gain: x0.66 */
|
|
{ 1, 344000 }, /* gain: x1 */
|
|
{ 0, 672000 }, /* gain: x2 */
|
|
},
|
|
/* integration time: 6.25 ms */
|
|
{
|
|
{ 2, 688000 }, /* gain: x0.25 */
|
|
{ 2, 36350 }, /* gain: x0.33 */
|
|
{ 1, 344000 }, /* gain: x0.5 */
|
|
{ 1, 18200 }, /* gain: x0.66 */
|
|
{ 0, 672000 }, /* gain: x1 */
|
|
{ 0, 336000 }, /* gain: x2 */
|
|
},
|
|
/* integration time: 12.5 ms */
|
|
{
|
|
{ 1, 344000 }, /* gain: x0.25 */
|
|
{ 1, 18175 }, /* gain: x0.33 */
|
|
{ 0, 672000 }, /* gain: x0.5 */
|
|
{ 0, 509100 }, /* gain: x0.66 */
|
|
{ 0, 336000 }, /* gain: x1 */
|
|
{ 0, 168000 }, /* gain: x2 */
|
|
},
|
|
/* integration time: 25 ms */
|
|
{
|
|
{ 0, 672000 }, /* gain: x0.25 */
|
|
{ 0, 509087 }, /* gain: x0.33 */
|
|
{ 0, 336000 }, /* gain: x0.5 */
|
|
{ 0, 254550 }, /* gain: x0.66 */
|
|
{ 0, 168000 }, /* gain: x1 */
|
|
{ 0, 84000 }, /* gain: x2 */
|
|
},
|
|
/* integration time: 50 ms */
|
|
{
|
|
{ 0, 336000 }, /* gain: x0.25 */
|
|
{ 0, 254543 }, /* gain: x0.33 */
|
|
{ 0, 168000 }, /* gain: x0.5 */
|
|
{ 0, 127275 }, /* gain: x0.66 */
|
|
{ 0, 84000 }, /* gain: x1 */
|
|
{ 0, 42000 }, /* gain: x2 */
|
|
},
|
|
/* integration time: 100 ms */
|
|
{
|
|
{ 0, 168000 }, /* gain: x0.25 */
|
|
{ 0, 127271 }, /* gain: x0.33 */
|
|
{ 0, 84000 }, /* gain: x0.5 */
|
|
{ 0, 63637 }, /* gain: x0.66 */
|
|
{ 0, 42000 }, /* gain: x1 */
|
|
{ 0, 21000 }, /* gain: x2 */
|
|
},
|
|
/* integration time: 200 ms */
|
|
{
|
|
{ 0, 84000 }, /* gain: x0.25 */
|
|
{ 0, 63635 }, /* gain: x0.33 */
|
|
{ 0, 42000 }, /* gain: x0.5 */
|
|
{ 0, 31818 }, /* gain: x0.66 */
|
|
{ 0, 21000 }, /* gain: x1 */
|
|
{ 0, 10500 }, /* gain: x2 */
|
|
},
|
|
/* integration time: 400 ms */
|
|
{
|
|
{ 0, 42000 }, /* gain: x0.25 */
|
|
{ 0, 31817 }, /* gain: x0.33 */
|
|
{ 0, 21000 }, /* gain: x0.5 */
|
|
{ 0, 15909 }, /* gain: x0.66 */
|
|
{ 0, 10500 }, /* gain: x1 */
|
|
{ 0, 5250 }, /* gain: x2 */
|
|
},
|
|
};
|
|
|
|
/*
|
|
* Two bits (RGB_ON_0 and RGB_ON_1) must be cleared to power on the device.
|
|
*/
|
|
static int veml6046x00_power_on(struct veml6046x00_data *data)
|
|
{
|
|
int ret;
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
|
|
ret = regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF0,
|
|
VEML6046X00_CONF0_ON_0);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to set bit for power on %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF1,
|
|
VEML6046X00_CONF1_ON_1);
|
|
}
|
|
|
|
/*
|
|
* Two bits (RGB_ON_0 and RGB_ON_1) must be set to power off the device.
|
|
*/
|
|
static int veml6046x00_shutdown(struct veml6046x00_data *data)
|
|
{
|
|
int ret;
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
|
|
ret = regmap_set_bits(data->regmap, VEML6046X00_REG_CONF0,
|
|
VEML6046X00_CONF0_ON_0);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to set bit for shutdown %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return regmap_set_bits(data->regmap, VEML6046X00_REG_CONF1,
|
|
VEML6046X00_CONF1_ON_1);
|
|
}
|
|
|
|
static void veml6046x00_shutdown_action(void *data)
|
|
{
|
|
veml6046x00_shutdown(data);
|
|
}
|
|
|
|
static const struct iio_chan_spec veml6046x00_channels[] = {
|
|
{
|
|
.type = IIO_INTENSITY,
|
|
.address = VEML6046X00_REG_R,
|
|
.modified = 1,
|
|
.channel2 = IIO_MOD_LIGHT_RED,
|
|
.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),
|
|
.scan_index = VEML6046X00_SCAN_R,
|
|
.scan_type = {
|
|
.sign = 'u',
|
|
.realbits = 16,
|
|
.storagebits = 16,
|
|
.endianness = IIO_LE,
|
|
},
|
|
},
|
|
{
|
|
.type = IIO_INTENSITY,
|
|
.address = VEML6046X00_REG_G,
|
|
.modified = 1,
|
|
.channel2 = IIO_MOD_LIGHT_GREEN,
|
|
.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),
|
|
.scan_index = VEML6046X00_SCAN_G,
|
|
.scan_type = {
|
|
.sign = 'u',
|
|
.realbits = 16,
|
|
.storagebits = 16,
|
|
.endianness = IIO_LE,
|
|
},
|
|
},
|
|
{
|
|
.type = IIO_INTENSITY,
|
|
.address = VEML6046X00_REG_B,
|
|
.modified = 1,
|
|
.channel2 = IIO_MOD_LIGHT_BLUE,
|
|
.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),
|
|
.scan_index = VEML6046X00_SCAN_B,
|
|
.scan_type = {
|
|
.sign = 'u',
|
|
.realbits = 16,
|
|
.storagebits = 16,
|
|
.endianness = IIO_LE,
|
|
},
|
|
},
|
|
{
|
|
.type = IIO_INTENSITY,
|
|
.address = VEML6046X00_REG_IR,
|
|
.modified = 1,
|
|
.channel2 = IIO_MOD_LIGHT_IR,
|
|
.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),
|
|
.scan_index = VEML6046X00_SCAN_IR,
|
|
.scan_type = {
|
|
.sign = 'u',
|
|
.realbits = 16,
|
|
.storagebits = 16,
|
|
.endianness = IIO_LE,
|
|
},
|
|
},
|
|
IIO_CHAN_SOFT_TIMESTAMP(VEML6046X00_SCAN_TIMESTAMP),
|
|
};
|
|
|
|
static const struct regmap_config veml6046x00_regmap_config = {
|
|
.name = "veml6046x00_regm",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.max_register = VEML6046X00_REG_INT_H,
|
|
};
|
|
|
|
static const struct reg_field veml6046x00_rf_int_en =
|
|
REG_FIELD(VEML6046X00_REG_CONF0, 1, 1);
|
|
|
|
static const struct reg_field veml6046x00_rf_trig =
|
|
REG_FIELD(VEML6046X00_REG_CONF0, 2, 2);
|
|
|
|
static const struct reg_field veml6046x00_rf_mode =
|
|
REG_FIELD(VEML6046X00_REG_CONF0, 3, 3);
|
|
|
|
static const struct reg_field veml6046x00_rf_it =
|
|
REG_FIELD(VEML6046X00_REG_CONF0, 4, 6);
|
|
|
|
static const struct reg_field veml6046x00_rf_pers =
|
|
REG_FIELD(VEML6046X00_REG_CONF1, 1, 2);
|
|
|
|
static int veml6046x00_regfield_init(struct veml6046x00_data *data)
|
|
{
|
|
struct regmap *regmap = data->regmap;
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
struct regmap_field *rm_field;
|
|
struct veml6046x00_rf *rf = &data->rf;
|
|
|
|
rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_int_en);
|
|
if (IS_ERR(rm_field))
|
|
return PTR_ERR(rm_field);
|
|
rf->int_en = rm_field;
|
|
|
|
rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_mode);
|
|
if (IS_ERR(rm_field))
|
|
return PTR_ERR(rm_field);
|
|
rf->mode = rm_field;
|
|
|
|
rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_trig);
|
|
if (IS_ERR(rm_field))
|
|
return PTR_ERR(rm_field);
|
|
rf->trig = rm_field;
|
|
|
|
rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_it);
|
|
if (IS_ERR(rm_field))
|
|
return PTR_ERR(rm_field);
|
|
rf->it = rm_field;
|
|
|
|
rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_pers);
|
|
if (IS_ERR(rm_field))
|
|
return PTR_ERR(rm_field);
|
|
rf->pers = rm_field;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int veml6046x00_get_it_index(struct veml6046x00_data *data)
|
|
{
|
|
int ret;
|
|
unsigned int reg;
|
|
|
|
ret = regmap_field_read(data->rf.it, ®);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* register value is identical with index of array */
|
|
if (reg >= ARRAY_SIZE(veml6046x00_it))
|
|
return -EINVAL;
|
|
|
|
return reg;
|
|
}
|
|
|
|
static int veml6046x00_get_it_usec(struct veml6046x00_data *data, unsigned int *it_usec)
|
|
{
|
|
int ret;
|
|
unsigned int reg;
|
|
|
|
ret = regmap_field_read(data->rf.it, ®);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (reg >= ARRAY_SIZE(veml6046x00_it))
|
|
return -EINVAL;
|
|
|
|
*it_usec = veml6046x00_it[reg][1];
|
|
|
|
return IIO_VAL_INT_PLUS_MICRO;
|
|
}
|
|
|
|
static int veml6046x00_set_it(struct iio_dev *iio, int val, int val2)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(veml6046x00_it); i++) {
|
|
if ((veml6046x00_it[i][0] == val) &&
|
|
(veml6046x00_it[i][1] == val2))
|
|
return regmap_field_write(data->rf.it, i);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int veml6046x00_get_val_gain_idx(struct veml6046x00_data *data, int val,
|
|
int val2)
|
|
{
|
|
unsigned int i;
|
|
int it_idx;
|
|
|
|
it_idx = veml6046x00_get_it_index(data);
|
|
if (it_idx < 0)
|
|
return it_idx;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(veml6046x00_it_gains[it_idx]); i++) {
|
|
if ((veml6046x00_it_gains[it_idx][i][0] == val) &&
|
|
(veml6046x00_it_gains[it_idx][i][1] == val2))
|
|
return i;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int veml6046x00_get_gain_idx(struct veml6046x00_data *data)
|
|
{
|
|
int ret;
|
|
unsigned int i, reg, reg_gain, reg_pd;
|
|
|
|
ret = regmap_read(data->regmap, VEML6046X00_REG_CONF1, ®);
|
|
if (ret)
|
|
return ret;
|
|
|
|
reg_gain = FIELD_GET(VEML6046X00_CONF1_GAIN, reg);
|
|
reg_pd = reg & VEML6046X00_CONF1_PD_D2;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(veml6046x00_gain_pd); i++) {
|
|
if ((veml6046x00_gain_pd[i].gain_sen == reg_gain) &&
|
|
(veml6046x00_gain_pd[i].pd == reg_pd))
|
|
return i;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int veml6046x00_set_scale(struct iio_dev *iio, int val, int val2)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
unsigned int new_scale;
|
|
int gain_idx;
|
|
|
|
gain_idx = veml6046x00_get_val_gain_idx(data, val, val2);
|
|
if (gain_idx < 0)
|
|
return gain_idx;
|
|
|
|
new_scale = FIELD_PREP(VEML6046X00_CONF1_GAIN,
|
|
veml6046x00_gain_pd[gain_idx].gain_sen) |
|
|
veml6046x00_gain_pd[gain_idx].pd;
|
|
|
|
return regmap_update_bits(data->regmap, VEML6046X00_REG_CONF1,
|
|
VEML6046X00_CONF1_GAIN |
|
|
VEML6046X00_CONF1_PD_D2,
|
|
new_scale);
|
|
}
|
|
|
|
static int veml6046x00_get_scale(struct veml6046x00_data *data,
|
|
int *val, int *val2)
|
|
{
|
|
int gain_idx, it_idx;
|
|
|
|
gain_idx = veml6046x00_get_gain_idx(data);
|
|
if (gain_idx < 0)
|
|
return gain_idx;
|
|
|
|
it_idx = veml6046x00_get_it_index(data);
|
|
if (it_idx < 0)
|
|
return it_idx;
|
|
|
|
*val = veml6046x00_it_gains[it_idx][gain_idx][0];
|
|
*val2 = veml6046x00_it_gains[it_idx][gain_idx][1];
|
|
|
|
return IIO_VAL_INT_PLUS_MICRO;
|
|
}
|
|
|
|
/**
|
|
* veml6046x00_read_data_ready() - Read data ready bit
|
|
* @data: Private data.
|
|
*
|
|
* Helper function for reading data ready bit from interrupt register.
|
|
*
|
|
* Return:
|
|
* * %1 - Data is available (AF_DATA_READY is set)
|
|
* * %0 - No data available
|
|
* * %-EIO - Error during bulk read
|
|
*/
|
|
static int veml6046x00_read_data_ready(struct veml6046x00_data *data)
|
|
{
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
int ret;
|
|
u8 reg[2];
|
|
|
|
/*
|
|
* Note from the vendor, but not explicitly in the datasheet: we
|
|
* should always read both registers together.
|
|
*/
|
|
ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT,
|
|
®, sizeof(reg));
|
|
if (ret) {
|
|
dev_err(dev, "Failed to read interrupt register %d\n", ret);
|
|
return -EIO;
|
|
}
|
|
|
|
if (reg[1] & VEML6046X00_INT_DRDY)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* veml6046x00_wait_data_available() - Wait until data is available
|
|
* @iio: Industrial IO.
|
|
* @usecs: Microseconds to wait for data.
|
|
*
|
|
* This function waits for a certain bit in the interrupt register which signals
|
|
* that there is data to be read available.
|
|
*
|
|
* It tries it two times with a waiting time of usecs in between.
|
|
*
|
|
* Return:
|
|
* * %1 - Data is available (AF_DATA_READY is set)
|
|
* * %0 - Timeout, no data available after usecs timeout
|
|
* * %-EIO - Error during bulk read
|
|
*/
|
|
static int veml6046x00_wait_data_available(struct iio_dev *iio, unsigned int usecs)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
int ret;
|
|
|
|
ret = veml6046x00_read_data_ready(data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
fsleep(usecs);
|
|
return veml6046x00_read_data_ready(data);
|
|
}
|
|
|
|
static int veml6046x00_single_read(struct iio_dev *iio,
|
|
enum iio_modifier modifier, int *val)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
unsigned int addr, it_usec;
|
|
int ret;
|
|
__le16 reg;
|
|
|
|
switch (modifier) {
|
|
case IIO_MOD_LIGHT_RED:
|
|
addr = VEML6046X00_REG_R;
|
|
break;
|
|
case IIO_MOD_LIGHT_GREEN:
|
|
addr = VEML6046X00_REG_G;
|
|
break;
|
|
case IIO_MOD_LIGHT_BLUE:
|
|
addr = VEML6046X00_REG_B;
|
|
break;
|
|
case IIO_MOD_LIGHT_IR:
|
|
addr = VEML6046X00_REG_IR;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
ret = pm_runtime_resume_and_get(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = veml6046x00_get_it_usec(data, &it_usec);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to get integration time ret: %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = regmap_field_write(data->rf.mode, 1);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to write mode ret: %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = regmap_field_write(data->rf.trig, 1);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to write trigger ret: %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
/* integration time + 12.5 % to ensure completion */
|
|
fsleep(it_usec + it_usec / 8);
|
|
|
|
ret = veml6046x00_wait_data_available(iio, it_usec * 4);
|
|
if (ret < 0)
|
|
goto out;
|
|
if (ret == 0) {
|
|
ret = -EAGAIN;
|
|
goto out;
|
|
}
|
|
|
|
if (!iio_device_claim_direct(iio)) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
ret = regmap_bulk_read(data->regmap, addr, ®, sizeof(reg));
|
|
iio_device_release_direct(iio);
|
|
if (ret)
|
|
goto out;
|
|
|
|
*val = le16_to_cpu(reg);
|
|
|
|
ret = IIO_VAL_INT;
|
|
|
|
out:
|
|
pm_runtime_put_autosuspend(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int veml6046x00_read_raw(struct iio_dev *iio,
|
|
struct iio_chan_spec const *chan, int *val,
|
|
int *val2, long mask)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
if (chan->type != IIO_INTENSITY)
|
|
return -EINVAL;
|
|
return veml6046x00_single_read(iio, chan->channel2, val);
|
|
case IIO_CHAN_INFO_INT_TIME:
|
|
*val = 0;
|
|
return veml6046x00_get_it_usec(data, val2);
|
|
case IIO_CHAN_INFO_SCALE:
|
|
return veml6046x00_get_scale(data, val, val2);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int veml6046x00_read_avail(struct iio_dev *iio,
|
|
struct iio_chan_spec const *chan,
|
|
const int **vals, int *type, int *length,
|
|
long mask)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
int it_idx;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_INT_TIME:
|
|
*vals = (int *)&veml6046x00_it;
|
|
*length = 2 * ARRAY_SIZE(veml6046x00_it);
|
|
*type = IIO_VAL_INT_PLUS_MICRO;
|
|
return IIO_AVAIL_LIST;
|
|
case IIO_CHAN_INFO_SCALE:
|
|
it_idx = veml6046x00_get_it_index(data);
|
|
if (it_idx < 0)
|
|
return it_idx;
|
|
*vals = (int *)&veml6046x00_it_gains[it_idx];
|
|
*length = 2 * ARRAY_SIZE(veml6046x00_it_gains[it_idx]);
|
|
*type = IIO_VAL_INT_PLUS_MICRO;
|
|
return IIO_AVAIL_LIST;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int veml6046x00_write_raw(struct iio_dev *iio,
|
|
struct iio_chan_spec const *chan,
|
|
int val, int val2, long mask)
|
|
{
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_INT_TIME:
|
|
return veml6046x00_set_it(iio, val, val2);
|
|
case IIO_CHAN_INFO_SCALE:
|
|
return veml6046x00_set_scale(iio, val, val2);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static const struct iio_info veml6046x00_info_no_irq = {
|
|
.read_raw = veml6046x00_read_raw,
|
|
.read_avail = veml6046x00_read_avail,
|
|
.write_raw = veml6046x00_write_raw,
|
|
};
|
|
|
|
static int veml6046x00_buffer_preenable(struct iio_dev *iio)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
int ret;
|
|
|
|
ret = regmap_field_write(data->rf.mode, 0);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to set mode %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_field_write(data->rf.trig, 0);
|
|
if (ret) {
|
|
/*
|
|
* no unrolling of mode as it is set appropriately with next
|
|
* single read.
|
|
*/
|
|
dev_err(dev, "Failed to set trigger %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return pm_runtime_resume_and_get(dev);
|
|
}
|
|
|
|
static int veml6046x00_buffer_postdisable(struct iio_dev *iio)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
int ret;
|
|
|
|
ret = regmap_field_write(data->rf.mode, 1);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to set mode %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
pm_runtime_put_autosuspend(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct iio_buffer_setup_ops veml6046x00_buffer_setup_ops = {
|
|
.preenable = veml6046x00_buffer_preenable,
|
|
.postdisable = veml6046x00_buffer_postdisable,
|
|
};
|
|
|
|
static irqreturn_t veml6046x00_trig_handler(int irq, void *p)
|
|
{
|
|
struct iio_poll_func *pf = p;
|
|
struct iio_dev *iio = pf->indio_dev;
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
int ret;
|
|
struct {
|
|
__le16 chans[4];
|
|
aligned_s64 timestamp;
|
|
} scan;
|
|
|
|
ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_R,
|
|
&scan.chans, sizeof(scan.chans));
|
|
if (ret)
|
|
goto done;
|
|
|
|
iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan),
|
|
iio_get_time_ns(iio));
|
|
|
|
done:
|
|
iio_trigger_notify_done(iio->trig);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int veml6046x00_validate_part_id(struct veml6046x00_data *data)
|
|
{
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
unsigned int part_id;
|
|
int ret;
|
|
__le16 reg;
|
|
|
|
ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_ID,
|
|
®, sizeof(reg));
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to read ID\n");
|
|
|
|
part_id = le16_to_cpu(reg);
|
|
if (part_id != 0x01)
|
|
dev_info(dev, "Unknown ID %#04x\n", part_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int veml6046x00_setup_device(struct iio_dev *iio)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(iio);
|
|
struct device *dev = regmap_get_device(data->regmap);
|
|
int ret;
|
|
__le16 reg16;
|
|
|
|
reg16 = cpu_to_le16(VEML6046X00_CONF0_AF);
|
|
ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_CONF0,
|
|
®16, sizeof(reg16));
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to set configuration\n");
|
|
|
|
reg16 = cpu_to_le16(0);
|
|
ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDL,
|
|
®16, sizeof(reg16));
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to set low threshold\n");
|
|
|
|
reg16 = cpu_to_le16(U16_MAX);
|
|
ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDH,
|
|
®16, sizeof(reg16));
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to set high threshold\n");
|
|
|
|
ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT,
|
|
®16, sizeof(reg16));
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to clear interrupts\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int veml6046x00_probe(struct i2c_client *i2c)
|
|
{
|
|
struct device *dev = &i2c->dev;
|
|
struct veml6046x00_data *data;
|
|
struct iio_dev *iio;
|
|
struct regmap *regmap;
|
|
int ret;
|
|
|
|
regmap = devm_regmap_init_i2c(i2c, &veml6046x00_regmap_config);
|
|
if (IS_ERR(regmap))
|
|
return dev_err_probe(dev, PTR_ERR(regmap), "Failed to set regmap\n");
|
|
|
|
iio = devm_iio_device_alloc(dev, sizeof(*data));
|
|
if (!iio)
|
|
return -ENOMEM;
|
|
|
|
data = iio_priv(iio);
|
|
/* struct iio_dev is retrieved via dev_get_drvdata(). */
|
|
i2c_set_clientdata(i2c, iio);
|
|
data->regmap = regmap;
|
|
|
|
ret = veml6046x00_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");
|
|
|
|
/* bring device in a known state and switch device on */
|
|
ret = veml6046x00_setup_device(iio);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = devm_add_action_or_reset(dev, veml6046x00_shutdown_action, data);
|
|
if (ret < 0)
|
|
return dev_err_probe(dev, ret, "Failed to add shut down action\n");
|
|
|
|
ret = pm_runtime_set_active(dev);
|
|
if (ret < 0)
|
|
return dev_err_probe(dev, ret, "Failed to activate PM runtime\n");
|
|
|
|
ret = devm_pm_runtime_enable(dev);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to enable PM runtime\n");
|
|
|
|
pm_runtime_get_noresume(dev);
|
|
pm_runtime_set_autosuspend_delay(dev, VEML6046X00_AUTOSUSPEND_MS);
|
|
pm_runtime_use_autosuspend(dev);
|
|
|
|
ret = veml6046x00_validate_part_id(data);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to validate device ID\n");
|
|
|
|
iio->name = "veml6046x00";
|
|
iio->channels = veml6046x00_channels;
|
|
iio->num_channels = ARRAY_SIZE(veml6046x00_channels);
|
|
iio->modes = INDIO_DIRECT_MODE;
|
|
|
|
iio->info = &veml6046x00_info_no_irq;
|
|
|
|
ret = devm_iio_triggered_buffer_setup(dev, iio, NULL,
|
|
veml6046x00_trig_handler,
|
|
&veml6046x00_buffer_setup_ops);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret,
|
|
"Failed to register triggered buffer");
|
|
|
|
pm_runtime_put_autosuspend(dev);
|
|
|
|
ret = devm_iio_device_register(dev, iio);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to register iio device");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int veml6046x00_runtime_suspend(struct device *dev)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev));
|
|
|
|
return veml6046x00_shutdown(data);
|
|
}
|
|
|
|
static int veml6046x00_runtime_resume(struct device *dev)
|
|
{
|
|
struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev));
|
|
|
|
return veml6046x00_power_on(data);
|
|
}
|
|
|
|
static DEFINE_RUNTIME_DEV_PM_OPS(veml6046x00_pm_ops,
|
|
veml6046x00_runtime_suspend,
|
|
veml6046x00_runtime_resume, NULL);
|
|
|
|
static const struct of_device_id veml6046x00_of_match[] = {
|
|
{ .compatible = "vishay,veml6046x00" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, veml6046x00_of_match);
|
|
|
|
static const struct i2c_device_id veml6046x00_id[] = {
|
|
{ "veml6046x00" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, veml6046x00_id);
|
|
|
|
static struct i2c_driver veml6046x00_driver = {
|
|
.driver = {
|
|
.name = "veml6046x00",
|
|
.of_match_table = veml6046x00_of_match,
|
|
.pm = pm_ptr(&veml6046x00_pm_ops),
|
|
},
|
|
.probe = veml6046x00_probe,
|
|
.id_table = veml6046x00_id,
|
|
};
|
|
module_i2c_driver(veml6046x00_driver);
|
|
|
|
MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
|
|
MODULE_DESCRIPTION("VEML6046X00 RGBIR Color Sensor");
|
|
MODULE_LICENSE("GPL");
|