// SPDX-License-Identifier: GPL-2.0-only /* * Vishay VEML6075 UVA and UVB light sensor * * Copyright 2023 Javier Carrasco * * 7-bit I2C slave, address 0x10 */ #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("Vishay VEML6075 UVA and UVB light sensor driver"); MODULE_LICENSE("GPL");