blob: e60f24d46e7bc09c831adac540850a957a231f8a [file] [log] [blame] [edit]
// 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, &reg);
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, &reg);
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, &reg);
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,
&reg, 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, &reg, 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,
&reg, 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,
&reg16, 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,
&reg16, 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,
&reg16, 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,
&reg16, 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");