mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 20:22:09 +00:00
3b82f43238
The Vishay VEMl6075 is a low power, 16-bit resolution UVA and UVB light sensor with I2C interface and noise compensation (visible and infrarred). Every UV channel generates an output signal measured in counts per integration period, where the integration time is configurable. This driver adds support for both UV channels and the ultraviolet index (UVI) inferred from them according to the device application note with open-air (no teflon) coefficients. Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com> Link: https://lore.kernel.org/r/20231110-veml6075-v3-3-6ee46775b422@gmail.com Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
475 lines
11 KiB
C
475 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Vishay VEML6075 UVA and UVB light sensor
|
|
*
|
|
* Copyright 2023 Javier Carrasco <javier.carrasco.cruz@gmail.com>
|
|
*
|
|
* 7-bit I2C slave, address 0x10
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/units.h>
|
|
|
|
#include <linux/iio/iio.h>
|
|
|
|
#define VEML6075_CMD_CONF 0x00 /* configuration register */
|
|
#define VEML6075_CMD_UVA 0x07 /* UVA channel */
|
|
#define VEML6075_CMD_UVB 0x09 /* UVB channel */
|
|
#define VEML6075_CMD_COMP1 0x0A /* visible light compensation */
|
|
#define VEML6075_CMD_COMP2 0x0B /* infrarred light compensation */
|
|
#define VEML6075_CMD_ID 0x0C /* device ID */
|
|
|
|
#define VEML6075_CONF_IT GENMASK(6, 4) /* intregration time */
|
|
#define VEML6075_CONF_HD BIT(3) /* dynamic setting */
|
|
#define VEML6075_CONF_TRIG BIT(2) /* trigger */
|
|
#define VEML6075_CONF_AF BIT(1) /* active force enable */
|
|
#define VEML6075_CONF_SD BIT(0) /* shutdown */
|
|
|
|
#define VEML6075_IT_50_MS 0x00
|
|
#define VEML6075_IT_100_MS 0x01
|
|
#define VEML6075_IT_200_MS 0x02
|
|
#define VEML6075_IT_400_MS 0x03
|
|
#define VEML6075_IT_800_MS 0x04
|
|
|
|
#define VEML6075_AF_DISABLE 0x00
|
|
#define VEML6075_AF_ENABLE 0x01
|
|
|
|
#define VEML6075_SD_DISABLE 0x00
|
|
#define VEML6075_SD_ENABLE 0x01
|
|
|
|
/* Open-air coefficients and responsivity */
|
|
#define VEML6075_A_COEF 2220
|
|
#define VEML6075_B_COEF 1330
|
|
#define VEML6075_C_COEF 2950
|
|
#define VEML6075_D_COEF 1740
|
|
#define VEML6075_UVA_RESP 1461
|
|
#define VEML6075_UVB_RESP 2591
|
|
|
|
static const int veml6075_it_ms[] = { 50, 100, 200, 400, 800 };
|
|
|
|
struct veml6075_data {
|
|
struct i2c_client *client;
|
|
struct regmap *regmap;
|
|
/*
|
|
* prevent integration time modification and triggering
|
|
* measurements while a measurement is underway.
|
|
*/
|
|
struct mutex lock;
|
|
};
|
|
|
|
/* channel number */
|
|
enum veml6075_chan {
|
|
CH_UVA,
|
|
CH_UVB,
|
|
};
|
|
|
|
static const struct iio_chan_spec veml6075_channels[] = {
|
|
{
|
|
.type = IIO_INTENSITY,
|
|
.channel = CH_UVA,
|
|
.modified = 1,
|
|
.channel2 = IIO_MOD_LIGHT_UVA,
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
|
BIT(IIO_CHAN_INFO_SCALE),
|
|
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
|
|
.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),
|
|
},
|
|
{
|
|
.type = IIO_INTENSITY,
|
|
.channel = CH_UVB,
|
|
.modified = 1,
|
|
.channel2 = IIO_MOD_LIGHT_UVB,
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
|
BIT(IIO_CHAN_INFO_SCALE),
|
|
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
|
|
.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),
|
|
},
|
|
{
|
|
.type = IIO_UVINDEX,
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
|
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
|
|
.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),
|
|
},
|
|
};
|
|
|
|
static int veml6075_request_measurement(struct veml6075_data *data)
|
|
{
|
|
int ret, conf, int_time;
|
|
|
|
ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* disable shutdown and trigger measurement */
|
|
ret = regmap_write(data->regmap, VEML6075_CMD_CONF,
|
|
(conf | VEML6075_CONF_TRIG) & ~VEML6075_CONF_SD);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/*
|
|
* A measurement requires between 1.30 and 1.40 times the integration
|
|
* time for all possible configurations. Using a 1.50 factor simplifies
|
|
* operations and ensures reliability under all circumstances.
|
|
*/
|
|
int_time = veml6075_it_ms[FIELD_GET(VEML6075_CONF_IT, conf)];
|
|
msleep(int_time + (int_time / 2));
|
|
|
|
/* shutdown again, data registers are still accessible */
|
|
return regmap_update_bits(data->regmap, VEML6075_CMD_CONF,
|
|
VEML6075_CONF_SD, VEML6075_CONF_SD);
|
|
}
|
|
|
|
static int veml6075_uva_comp(int raw_uva, int comp1, int comp2)
|
|
{
|
|
int comp1a_c, comp2a_c, uva_comp;
|
|
|
|
comp1a_c = (comp1 * VEML6075_A_COEF) / 1000U;
|
|
comp2a_c = (comp2 * VEML6075_B_COEF) / 1000U;
|
|
uva_comp = raw_uva - comp1a_c - comp2a_c;
|
|
|
|
return clamp_val(uva_comp, 0, U16_MAX);
|
|
}
|
|
|
|
static int veml6075_uvb_comp(int raw_uvb, int comp1, int comp2)
|
|
{
|
|
int comp1b_c, comp2b_c, uvb_comp;
|
|
|
|
comp1b_c = (comp1 * VEML6075_C_COEF) / 1000U;
|
|
comp2b_c = (comp2 * VEML6075_D_COEF) / 1000U;
|
|
uvb_comp = raw_uvb - comp1b_c - comp2b_c;
|
|
|
|
return clamp_val(uvb_comp, 0, U16_MAX);
|
|
}
|
|
|
|
static int veml6075_read_comp(struct veml6075_data *data, int *c1, int *c2)
|
|
{
|
|
int ret;
|
|
|
|
ret = regmap_read(data->regmap, VEML6075_CMD_COMP1, c1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return regmap_read(data->regmap, VEML6075_CMD_COMP2, c2);
|
|
}
|
|
|
|
static int veml6075_read_uv_direct(struct veml6075_data *data, int chan,
|
|
int *val)
|
|
{
|
|
int c1, c2, ret;
|
|
|
|
guard(mutex)(&data->lock);
|
|
|
|
ret = veml6075_request_measurement(data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = veml6075_read_comp(data, &c1, &c2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
switch (chan) {
|
|
case CH_UVA:
|
|
ret = regmap_read(data->regmap, VEML6075_CMD_UVA, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*val = veml6075_uva_comp(*val, c1, c2);
|
|
return IIO_VAL_INT;
|
|
case CH_UVB:
|
|
ret = regmap_read(data->regmap, VEML6075_CMD_UVB, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*val = veml6075_uvb_comp(*val, c1, c2);
|
|
return IIO_VAL_INT;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int veml6075_read_int_time_index(struct veml6075_data *data)
|
|
{
|
|
int ret, conf;
|
|
|
|
ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return FIELD_GET(VEML6075_CONF_IT, conf);
|
|
}
|
|
|
|
static int veml6075_read_int_time_ms(struct veml6075_data *data, int *val)
|
|
{
|
|
int int_index;
|
|
|
|
guard(mutex)(&data->lock);
|
|
int_index = veml6075_read_int_time_index(data);
|
|
if (int_index < 0)
|
|
return int_index;
|
|
|
|
*val = veml6075_it_ms[int_index];
|
|
|
|
return IIO_VAL_INT;
|
|
}
|
|
|
|
static int veml6075_get_uvi_micro(struct veml6075_data *data, int uva_comp,
|
|
int uvb_comp)
|
|
{
|
|
int uvia_micro = uva_comp * VEML6075_UVA_RESP;
|
|
int uvib_micro = uvb_comp * VEML6075_UVB_RESP;
|
|
int int_index;
|
|
|
|
int_index = veml6075_read_int_time_index(data);
|
|
if (int_index < 0)
|
|
return int_index;
|
|
|
|
switch (int_index) {
|
|
case VEML6075_IT_50_MS:
|
|
return uvia_micro + uvib_micro;
|
|
case VEML6075_IT_100_MS:
|
|
case VEML6075_IT_200_MS:
|
|
case VEML6075_IT_400_MS:
|
|
case VEML6075_IT_800_MS:
|
|
return (uvia_micro + uvib_micro) / (2 << int_index);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int veml6075_read_uvi(struct veml6075_data *data, int *val, int *val2)
|
|
{
|
|
int ret, c1, c2, uva, uvb, uvi_micro;
|
|
|
|
guard(mutex)(&data->lock);
|
|
|
|
ret = veml6075_request_measurement(data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = veml6075_read_comp(data, &c1, &c2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = regmap_read(data->regmap, VEML6075_CMD_UVA, &uva);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = regmap_read(data->regmap, VEML6075_CMD_UVB, &uvb);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
uvi_micro = veml6075_get_uvi_micro(data, veml6075_uva_comp(uva, c1, c2),
|
|
veml6075_uvb_comp(uvb, c1, c2));
|
|
if (uvi_micro < 0)
|
|
return uvi_micro;
|
|
|
|
*val = uvi_micro / MICRO;
|
|
*val2 = uvi_micro % MICRO;
|
|
|
|
return IIO_VAL_INT_PLUS_MICRO;
|
|
}
|
|
|
|
static int veml6075_read_responsivity(int chan, int *val, int *val2)
|
|
{
|
|
/* scale = 1 / resp */
|
|
switch (chan) {
|
|
case CH_UVA:
|
|
/* resp = 0.93 c/uW/cm2: scale = 1.75268817 */
|
|
*val = 1;
|
|
*val2 = 75268817;
|
|
return IIO_VAL_INT_PLUS_NANO;
|
|
case CH_UVB:
|
|
/* resp = 2.1 c/uW/cm2: scale = 0.476190476 */
|
|
*val = 0;
|
|
*val2 = 476190476;
|
|
return IIO_VAL_INT_PLUS_NANO;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int veml6075_read_avail(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
const int **vals, int *type, int *length,
|
|
long mask)
|
|
{
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_INT_TIME:
|
|
*length = ARRAY_SIZE(veml6075_it_ms);
|
|
*vals = veml6075_it_ms;
|
|
*type = IIO_VAL_INT;
|
|
return IIO_AVAIL_LIST;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int veml6075_read_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
int *val, int *val2, long mask)
|
|
{
|
|
struct veml6075_data *data = iio_priv(indio_dev);
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
return veml6075_read_uv_direct(data, chan->channel, val);
|
|
case IIO_CHAN_INFO_PROCESSED:
|
|
return veml6075_read_uvi(data, val, val2);
|
|
case IIO_CHAN_INFO_INT_TIME:
|
|
return veml6075_read_int_time_ms(data, val);
|
|
case IIO_CHAN_INFO_SCALE:
|
|
return veml6075_read_responsivity(chan->channel, val, val2);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int veml6075_write_int_time_ms(struct veml6075_data *data, int val)
|
|
{
|
|
int i = ARRAY_SIZE(veml6075_it_ms);
|
|
|
|
guard(mutex)(&data->lock);
|
|
|
|
while (i-- > 0) {
|
|
if (val == veml6075_it_ms[i])
|
|
break;
|
|
}
|
|
if (i < 0)
|
|
return -EINVAL;
|
|
|
|
return regmap_update_bits(data->regmap, VEML6075_CMD_CONF,
|
|
VEML6075_CONF_IT,
|
|
FIELD_PREP(VEML6075_CONF_IT, i));
|
|
}
|
|
|
|
static int veml6075_write_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
int val, int val2, long mask)
|
|
{
|
|
struct veml6075_data *data = iio_priv(indio_dev);
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_INT_TIME:
|
|
return veml6075_write_int_time_ms(data, val);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static const struct iio_info veml6075_info = {
|
|
.read_avail = veml6075_read_avail,
|
|
.read_raw = veml6075_read_raw,
|
|
.write_raw = veml6075_write_raw,
|
|
};
|
|
|
|
static bool veml6075_readable_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case VEML6075_CMD_CONF:
|
|
case VEML6075_CMD_UVA:
|
|
case VEML6075_CMD_UVB:
|
|
case VEML6075_CMD_COMP1:
|
|
case VEML6075_CMD_COMP2:
|
|
case VEML6075_CMD_ID:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool veml6075_writable_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case VEML6075_CMD_CONF:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct regmap_config veml6075_regmap_config = {
|
|
.name = "veml6075",
|
|
.reg_bits = 8,
|
|
.val_bits = 16,
|
|
.max_register = VEML6075_CMD_ID,
|
|
.readable_reg = veml6075_readable_reg,
|
|
.writeable_reg = veml6075_writable_reg,
|
|
.val_format_endian = REGMAP_ENDIAN_LITTLE,
|
|
};
|
|
|
|
static int veml6075_probe(struct i2c_client *client)
|
|
{
|
|
struct veml6075_data *data;
|
|
struct iio_dev *indio_dev;
|
|
struct regmap *regmap;
|
|
int config, ret;
|
|
|
|
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
|
if (!indio_dev)
|
|
return -ENOMEM;
|
|
|
|
regmap = devm_regmap_init_i2c(client, &veml6075_regmap_config);
|
|
if (IS_ERR(regmap))
|
|
return PTR_ERR(regmap);
|
|
|
|
data = iio_priv(indio_dev);
|
|
data->client = client;
|
|
data->regmap = regmap;
|
|
|
|
mutex_init(&data->lock);
|
|
|
|
indio_dev->name = "veml6075";
|
|
indio_dev->info = &veml6075_info;
|
|
indio_dev->channels = veml6075_channels;
|
|
indio_dev->num_channels = ARRAY_SIZE(veml6075_channels);
|
|
indio_dev->modes = INDIO_DIRECT_MODE;
|
|
|
|
ret = devm_regulator_get_enable(&client->dev, "vdd");
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* default: 100ms integration time, active force enable, shutdown */
|
|
config = FIELD_PREP(VEML6075_CONF_IT, VEML6075_IT_100_MS) |
|
|
FIELD_PREP(VEML6075_CONF_AF, VEML6075_AF_ENABLE) |
|
|
FIELD_PREP(VEML6075_CONF_SD, VEML6075_SD_ENABLE);
|
|
ret = regmap_write(data->regmap, VEML6075_CMD_CONF, config);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return devm_iio_device_register(&client->dev, indio_dev);
|
|
}
|
|
|
|
static const struct i2c_device_id veml6075_id[] = {
|
|
{ "veml6075" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, veml6075_id);
|
|
|
|
static const struct of_device_id veml6075_of_match[] = {
|
|
{ .compatible = "vishay,veml6075" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, veml6075_of_match);
|
|
|
|
static struct i2c_driver veml6075_driver = {
|
|
.driver = {
|
|
.name = "veml6075",
|
|
.of_match_table = veml6075_of_match,
|
|
},
|
|
.probe = veml6075_probe,
|
|
.id_table = veml6075_id,
|
|
};
|
|
|
|
module_i2c_driver(veml6075_driver);
|
|
|
|
MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>");
|
|
MODULE_DESCRIPTION("Vishay VEML6075 UVA and UVB light sensor driver");
|
|
MODULE_LICENSE("GPL");
|