475 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Vishay VEML6075 UVA and UVB light sensor4 *5 * Copyright 2023 Javier Carrasco <javier.carrasco.cruz@gmail.com>6 *7 * 7-bit I2C slave, address 0x108 */9 10#include <linux/bitfield.h>11#include <linux/delay.h>12#include <linux/err.h>13#include <linux/i2c.h>14#include <linux/module.h>15#include <linux/mutex.h>16#include <linux/regmap.h>17#include <linux/units.h>18 19#include <linux/iio/iio.h>20 21#define VEML6075_CMD_CONF 0x00 /* configuration register */22#define VEML6075_CMD_UVA 0x07 /* UVA channel */23#define VEML6075_CMD_UVB 0x09 /* UVB channel */24#define VEML6075_CMD_COMP1 0x0A /* visible light compensation */25#define VEML6075_CMD_COMP2 0x0B /* infrarred light compensation */26#define VEML6075_CMD_ID 0x0C /* device ID */27 28#define VEML6075_CONF_IT GENMASK(6, 4) /* intregration time */29#define VEML6075_CONF_HD BIT(3) /* dynamic setting */30#define VEML6075_CONF_TRIG BIT(2) /* trigger */31#define VEML6075_CONF_AF BIT(1) /* active force enable */32#define VEML6075_CONF_SD BIT(0) /* shutdown */33 34#define VEML6075_IT_50_MS 0x0035#define VEML6075_IT_100_MS 0x0136#define VEML6075_IT_200_MS 0x0237#define VEML6075_IT_400_MS 0x0338#define VEML6075_IT_800_MS 0x0439 40#define VEML6075_AF_DISABLE 0x0041#define VEML6075_AF_ENABLE 0x0142 43#define VEML6075_SD_DISABLE 0x0044#define VEML6075_SD_ENABLE 0x0145 46/* Open-air coefficients and responsivity */47#define VEML6075_A_COEF 222048#define VEML6075_B_COEF 133049#define VEML6075_C_COEF 295050#define VEML6075_D_COEF 174051#define VEML6075_UVA_RESP 146152#define VEML6075_UVB_RESP 259153 54static const int veml6075_it_ms[] = { 50, 100, 200, 400, 800 };55 56struct veml6075_data {57 struct i2c_client *client;58 struct regmap *regmap;59 /*60 * prevent integration time modification and triggering61 * measurements while a measurement is underway.62 */63 struct mutex lock;64};65 66/* channel number */67enum veml6075_chan {68 CH_UVA,69 CH_UVB,70};71 72static const struct iio_chan_spec veml6075_channels[] = {73 {74 .type = IIO_INTENSITY,75 .channel = CH_UVA,76 .modified = 1,77 .channel2 = IIO_MOD_LIGHT_UVA,78 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |79 BIT(IIO_CHAN_INFO_SCALE),80 .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),81 .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),82 },83 {84 .type = IIO_INTENSITY,85 .channel = CH_UVB,86 .modified = 1,87 .channel2 = IIO_MOD_LIGHT_UVB,88 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |89 BIT(IIO_CHAN_INFO_SCALE),90 .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),91 .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),92 },93 {94 .type = IIO_UVINDEX,95 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),96 .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),97 .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),98 },99};100 101static int veml6075_request_measurement(struct veml6075_data *data)102{103 int ret, conf, int_time;104 105 ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf);106 if (ret < 0)107 return ret;108 109 /* disable shutdown and trigger measurement */110 ret = regmap_write(data->regmap, VEML6075_CMD_CONF,111 (conf | VEML6075_CONF_TRIG) & ~VEML6075_CONF_SD);112 if (ret < 0)113 return ret;114 115 /*116 * A measurement requires between 1.30 and 1.40 times the integration117 * time for all possible configurations. Using a 1.50 factor simplifies118 * operations and ensures reliability under all circumstances.119 */120 int_time = veml6075_it_ms[FIELD_GET(VEML6075_CONF_IT, conf)];121 msleep(int_time + (int_time / 2));122 123 /* shutdown again, data registers are still accessible */124 return regmap_update_bits(data->regmap, VEML6075_CMD_CONF,125 VEML6075_CONF_SD, VEML6075_CONF_SD);126}127 128static int veml6075_uva_comp(int raw_uva, int comp1, int comp2)129{130 int comp1a_c, comp2a_c, uva_comp;131 132 comp1a_c = (comp1 * VEML6075_A_COEF) / 1000U;133 comp2a_c = (comp2 * VEML6075_B_COEF) / 1000U;134 uva_comp = raw_uva - comp1a_c - comp2a_c;135 136 return clamp_val(uva_comp, 0, U16_MAX);137}138 139static int veml6075_uvb_comp(int raw_uvb, int comp1, int comp2)140{141 int comp1b_c, comp2b_c, uvb_comp;142 143 comp1b_c = (comp1 * VEML6075_C_COEF) / 1000U;144 comp2b_c = (comp2 * VEML6075_D_COEF) / 1000U;145 uvb_comp = raw_uvb - comp1b_c - comp2b_c;146 147 return clamp_val(uvb_comp, 0, U16_MAX);148}149 150static int veml6075_read_comp(struct veml6075_data *data, int *c1, int *c2)151{152 int ret;153 154 ret = regmap_read(data->regmap, VEML6075_CMD_COMP1, c1);155 if (ret < 0)156 return ret;157 158 return regmap_read(data->regmap, VEML6075_CMD_COMP2, c2);159}160 161static int veml6075_read_uv_direct(struct veml6075_data *data, int chan,162 int *val)163{164 int c1, c2, ret;165 166 guard(mutex)(&data->lock);167 168 ret = veml6075_request_measurement(data);169 if (ret < 0)170 return ret;171 172 ret = veml6075_read_comp(data, &c1, &c2);173 if (ret < 0)174 return ret;175 176 switch (chan) {177 case CH_UVA:178 ret = regmap_read(data->regmap, VEML6075_CMD_UVA, val);179 if (ret < 0)180 return ret;181 182 *val = veml6075_uva_comp(*val, c1, c2);183 return IIO_VAL_INT;184 case CH_UVB:185 ret = regmap_read(data->regmap, VEML6075_CMD_UVB, val);186 if (ret < 0)187 return ret;188 189 *val = veml6075_uvb_comp(*val, c1, c2);190 return IIO_VAL_INT;191 default:192 return -EINVAL;193 }194}195 196static int veml6075_read_int_time_index(struct veml6075_data *data)197{198 int ret, conf;199 200 ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf);201 if (ret < 0)202 return ret;203 204 return FIELD_GET(VEML6075_CONF_IT, conf);205}206 207static int veml6075_read_int_time_ms(struct veml6075_data *data, int *val)208{209 int int_index;210 211 guard(mutex)(&data->lock);212 int_index = veml6075_read_int_time_index(data);213 if (int_index < 0)214 return int_index;215 216 *val = veml6075_it_ms[int_index];217 218 return IIO_VAL_INT;219}220 221static int veml6075_get_uvi_micro(struct veml6075_data *data, int uva_comp,222 int uvb_comp)223{224 int uvia_micro = uva_comp * VEML6075_UVA_RESP;225 int uvib_micro = uvb_comp * VEML6075_UVB_RESP;226 int int_index;227 228 int_index = veml6075_read_int_time_index(data);229 if (int_index < 0)230 return int_index;231 232 switch (int_index) {233 case VEML6075_IT_50_MS:234 return uvia_micro + uvib_micro;235 case VEML6075_IT_100_MS:236 case VEML6075_IT_200_MS:237 case VEML6075_IT_400_MS:238 case VEML6075_IT_800_MS:239 return (uvia_micro + uvib_micro) / (2 << int_index);240 default:241 return -EINVAL;242 }243}244 245static int veml6075_read_uvi(struct veml6075_data *data, int *val, int *val2)246{247 int ret, c1, c2, uva, uvb, uvi_micro;248 249 guard(mutex)(&data->lock);250 251 ret = veml6075_request_measurement(data);252 if (ret < 0)253 return ret;254 255 ret = veml6075_read_comp(data, &c1, &c2);256 if (ret < 0)257 return ret;258 259 ret = regmap_read(data->regmap, VEML6075_CMD_UVA, &uva);260 if (ret < 0)261 return ret;262 263 ret = regmap_read(data->regmap, VEML6075_CMD_UVB, &uvb);264 if (ret < 0)265 return ret;266 267 uvi_micro = veml6075_get_uvi_micro(data, veml6075_uva_comp(uva, c1, c2),268 veml6075_uvb_comp(uvb, c1, c2));269 if (uvi_micro < 0)270 return uvi_micro;271 272 *val = uvi_micro / MICRO;273 *val2 = uvi_micro % MICRO;274 275 return IIO_VAL_INT_PLUS_MICRO;276}277 278static int veml6075_read_responsivity(int chan, int *val, int *val2)279{280 /* scale = 1 / resp */281 switch (chan) {282 case CH_UVA:283 /* resp = 0.93 c/uW/cm2: scale = 1.75268817 */284 *val = 1;285 *val2 = 75268817;286 return IIO_VAL_INT_PLUS_NANO;287 case CH_UVB:288 /* resp = 2.1 c/uW/cm2: scale = 0.476190476 */289 *val = 0;290 *val2 = 476190476;291 return IIO_VAL_INT_PLUS_NANO;292 default:293 return -EINVAL;294 }295}296 297static int veml6075_read_avail(struct iio_dev *indio_dev,298 struct iio_chan_spec const *chan,299 const int **vals, int *type, int *length,300 long mask)301{302 switch (mask) {303 case IIO_CHAN_INFO_INT_TIME:304 *length = ARRAY_SIZE(veml6075_it_ms);305 *vals = veml6075_it_ms;306 *type = IIO_VAL_INT;307 return IIO_AVAIL_LIST;308 309 default:310 return -EINVAL;311 }312}313 314static int veml6075_read_raw(struct iio_dev *indio_dev,315 struct iio_chan_spec const *chan,316 int *val, int *val2, long mask)317{318 struct veml6075_data *data = iio_priv(indio_dev);319 320 switch (mask) {321 case IIO_CHAN_INFO_RAW:322 return veml6075_read_uv_direct(data, chan->channel, val);323 case IIO_CHAN_INFO_PROCESSED:324 return veml6075_read_uvi(data, val, val2);325 case IIO_CHAN_INFO_INT_TIME:326 return veml6075_read_int_time_ms(data, val);327 case IIO_CHAN_INFO_SCALE:328 return veml6075_read_responsivity(chan->channel, val, val2);329 default:330 return -EINVAL;331 }332}333 334static int veml6075_write_int_time_ms(struct veml6075_data *data, int val)335{336 int i = ARRAY_SIZE(veml6075_it_ms);337 338 guard(mutex)(&data->lock);339 340 while (i-- > 0) {341 if (val == veml6075_it_ms[i])342 break;343 }344 if (i < 0)345 return -EINVAL;346 347 return regmap_update_bits(data->regmap, VEML6075_CMD_CONF,348 VEML6075_CONF_IT,349 FIELD_PREP(VEML6075_CONF_IT, i));350}351 352static int veml6075_write_raw(struct iio_dev *indio_dev,353 struct iio_chan_spec const *chan,354 int val, int val2, long mask)355{356 struct veml6075_data *data = iio_priv(indio_dev);357 358 switch (mask) {359 case IIO_CHAN_INFO_INT_TIME:360 return veml6075_write_int_time_ms(data, val);361 default:362 return -EINVAL;363 }364}365 366static const struct iio_info veml6075_info = {367 .read_avail = veml6075_read_avail,368 .read_raw = veml6075_read_raw,369 .write_raw = veml6075_write_raw,370};371 372static bool veml6075_readable_reg(struct device *dev, unsigned int reg)373{374 switch (reg) {375 case VEML6075_CMD_CONF:376 case VEML6075_CMD_UVA:377 case VEML6075_CMD_UVB:378 case VEML6075_CMD_COMP1:379 case VEML6075_CMD_COMP2:380 case VEML6075_CMD_ID:381 return true;382 default:383 return false;384 }385}386 387static bool veml6075_writable_reg(struct device *dev, unsigned int reg)388{389 switch (reg) {390 case VEML6075_CMD_CONF:391 return true;392 default:393 return false;394 }395}396 397static const struct regmap_config veml6075_regmap_config = {398 .name = "veml6075",399 .reg_bits = 8,400 .val_bits = 16,401 .max_register = VEML6075_CMD_ID,402 .readable_reg = veml6075_readable_reg,403 .writeable_reg = veml6075_writable_reg,404 .val_format_endian = REGMAP_ENDIAN_LITTLE,405};406 407static int veml6075_probe(struct i2c_client *client)408{409 struct veml6075_data *data;410 struct iio_dev *indio_dev;411 struct regmap *regmap;412 int config, ret;413 414 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));415 if (!indio_dev)416 return -ENOMEM;417 418 regmap = devm_regmap_init_i2c(client, &veml6075_regmap_config);419 if (IS_ERR(regmap))420 return PTR_ERR(regmap);421 422 data = iio_priv(indio_dev);423 data->client = client;424 data->regmap = regmap;425 426 mutex_init(&data->lock);427 428 indio_dev->name = "veml6075";429 indio_dev->info = &veml6075_info;430 indio_dev->channels = veml6075_channels;431 indio_dev->num_channels = ARRAY_SIZE(veml6075_channels);432 indio_dev->modes = INDIO_DIRECT_MODE;433 434 ret = devm_regulator_get_enable(&client->dev, "vdd");435 if (ret < 0)436 return ret;437 438 /* default: 100ms integration time, active force enable, shutdown */439 config = FIELD_PREP(VEML6075_CONF_IT, VEML6075_IT_100_MS) |440 FIELD_PREP(VEML6075_CONF_AF, VEML6075_AF_ENABLE) |441 FIELD_PREP(VEML6075_CONF_SD, VEML6075_SD_ENABLE);442 ret = regmap_write(data->regmap, VEML6075_CMD_CONF, config);443 if (ret < 0)444 return ret;445 446 return devm_iio_device_register(&client->dev, indio_dev);447}448 449static const struct i2c_device_id veml6075_id[] = {450 { "veml6075" },451 { }452};453MODULE_DEVICE_TABLE(i2c, veml6075_id);454 455static const struct of_device_id veml6075_of_match[] = {456 { .compatible = "vishay,veml6075" },457 {}458};459MODULE_DEVICE_TABLE(of, veml6075_of_match);460 461static struct i2c_driver veml6075_driver = {462 .driver = {463 .name = "veml6075",464 .of_match_table = veml6075_of_match,465 },466 .probe = veml6075_probe,467 .id_table = veml6075_id,468};469 470module_i2c_driver(veml6075_driver);471 472MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>");473MODULE_DESCRIPTION("Vishay VEML6075 UVA and UVB light sensor driver");474MODULE_LICENSE("GPL");475