root/drivers/iio/light/veml6046x00.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, &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");