1 /*
2  * Copyright (c) 2023 Andreas Kilian
3  * Copyright (c) 2024 Jeff Welder (Ellenby Technologies, Inc.)
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #define DT_DRV_COMPAT vishay_veml7700
9 
10 #include <zephyr/device.h>
11 #include <zephyr/drivers/i2c.h>
12 #include <zephyr/drivers/sensor.h>
13 #include <zephyr/kernel.h>
14 #include <zephyr/logging/log.h>
15 #include <zephyr/pm/device.h>
16 #include <zephyr/sys/byteorder.h>
17 
18 #include <zephyr/drivers/sensor/veml7700.h>
19 
20 LOG_MODULE_REGISTER(VEML7700, CONFIG_SENSOR_LOG_LEVEL);
21 
22 /*
23  * Bit mask to zero out all bits except 14 and 15.
24  * Those two are used for the high and low threshold
25  * interrupt flags in the ALS_INT register.
26  */
27 #define VEML7700_ALS_INT_MASK (~BIT_MASK(14))
28 
29 /*
30  * 16-bit command register addresses
31  */
32 #define VEML7700_CMDCODE_ALS_CONF 0x00
33 #define VEML7700_CMDCODE_ALS_WH	  0x01
34 #define VEML7700_CMDCODE_ALS_WL	  0x02
35 #define VEML7700_CMDCODE_PSM	  0x03
36 #define VEML7700_CMDCODE_ALS	  0x04
37 #define VEML7700_CMDCODE_WHITE	  0x05
38 #define VEML7700_CMDCODE_ALS_INT  0x06
39 
40 /*
41  * Devicetree psm-mode property value for "PSM disabled"
42  */
43 #define VEML7700_PSM_DISABLED 0x00
44 
45 /*
46  * ALS integration time setting values.
47  *
48  * The enumerators of <tt>enum veml7700_als_it</tt> provide
49  * indices into this array to get the related value for the
50  * ALS_IT configuration bits.
51  */
52 static const uint8_t veml7700_it_values[VEML7700_ALS_IT_ELEM_COUNT] = {
53 	0x0C, /* 25  - 0b1100 */
54 	0x08, /* 50  - 0b1000 */
55 	0x00, /* 100 - 0b0000 */
56 	0x01, /* 200 - 0b0001 */
57 	0x02, /* 400 - 0b0010 */
58 	0x03, /* 800 - 0b0011 */
59 };
60 
61 /*
62  * Resolution matrix for values to convert between data provided
63  * by the sensor ("counts") and lux.
64  *
65  * These values depend on the current gain and integration time settings.
66  * The enumerators of <tt>enum veml7700_als_gain</tt> and <tt>enum veml7700_als_it</tt>
67  * are used for indices into this matrix.
68  */
69 static const float veml7700_resolution[VEML7700_ALS_GAIN_ELEM_COUNT][VEML7700_ALS_IT_ELEM_COUNT] = {
70 	/* 25ms    50ms    100ms   200ms   400ms   800ms  Integration Time */
71 	{0.2304f, 0.1152f, 0.0576f, 0.0288f, 0.0144f, 0.0072f}, /* Gain 1 */
72 	{0.1152f, 0.0576f, 0.0288f, 0.0144f, 0.0072f, 0.0036f}, /* Gain 2 */
73 	{1.8432f, 0.9216f, 0.4608f, 0.2304f, 0.1152f, 0.0576f}, /* Gain 1/8 */
74 	{0.9216f, 0.4608f, 0.2304f, 0.1152f, 0.0576f, 0.0288f}, /* Gain 1/4 */
75 };
76 
77 struct veml7700_config {
78 	struct i2c_dt_spec bus;
79 	uint8_t psm;
80 };
81 
82 struct veml7700_data {
83 	uint8_t shut_down;
84 	enum veml7700_als_gain gain;
85 	enum veml7700_als_it it;
86 	enum veml7700_int_mode int_mode;
87 	uint16_t thresh_high;
88 	uint16_t thresh_low;
89 	uint16_t als_counts;
90 	uint32_t als_lux;
91 	uint16_t white_counts;
92 	uint32_t int_flags;
93 };
94 
95 /**
96  * @brief Waits for a specific amount of time which depends
97  * on the current integration time setting.
98  *
99  * According to datasheet for a measurement to complete one has
100  * to wait for at least the integration time. But tests showed
101  * that a much longer wait time is needed. Simply adding 50 or
102  * 100ms to the integration time is not enough so we doubled
103  * the integration time to get our wait time.
104  *
105  * This function is only called if the sensor is used in "single shot"
106  * measuring mode. In this mode the sensor measures on demand an
107  * measurements take time depending on the configures integration time.
108  * In continuous mode, activated by one of the power saving modes,
109  * you can always use the last sample value and no waiting is required.
110  *
111  * For more information see the "Designing the VEML7700 Into an Application"
112  * application notes about the power saving modes.
113  */
veml7700_sleep_by_integration_time(const struct veml7700_data * data)114 static void veml7700_sleep_by_integration_time(const struct veml7700_data *data)
115 {
116 	switch (data->it) {
117 	case VEML7700_ALS_IT_25:
118 		k_msleep(50);
119 		break;
120 	case VEML7700_ALS_IT_50:
121 		k_msleep(100);
122 		break;
123 	case VEML7700_ALS_IT_100:
124 		k_msleep(200);
125 		break;
126 	case VEML7700_ALS_IT_200:
127 		k_msleep(400);
128 		break;
129 	case VEML7700_ALS_IT_400:
130 		k_msleep(800);
131 		break;
132 	case VEML7700_ALS_IT_800:
133 		k_msleep(1600);
134 		break;
135 	}
136 }
137 
veml7700_counts_to_lux(const struct veml7700_data * data,uint16_t counts)138 static uint32_t veml7700_counts_to_lux(const struct veml7700_data *data,
139 				       uint16_t counts)
140 {
141 	return counts * veml7700_resolution[data->gain][data->it];
142 }
143 
veml7700_lux_to_counts(const struct veml7700_data * data,uint32_t lux)144 static uint16_t veml7700_lux_to_counts(const struct veml7700_data *data,
145 				       uint32_t lux)
146 {
147 	return lux / veml7700_resolution[data->gain][data->it];
148 }
149 
veml7700_check_gain(const struct sensor_value * val)150 static int veml7700_check_gain(const struct sensor_value *val)
151 {
152 	return val->val1 >= VEML7700_ALS_GAIN_1
153 	       && val->val1 <= VEML7700_ALS_GAIN_1_4;
154 }
155 
veml7700_check_it(const struct sensor_value * val)156 static int veml7700_check_it(const struct sensor_value *val)
157 {
158 	return val->val1 >= VEML7700_ALS_IT_25
159 	       && val->val1 <= VEML7700_ALS_IT_800;
160 }
161 
veml7700_check_int_mode(const struct sensor_value * val)162 static int veml7700_check_int_mode(const struct sensor_value *val)
163 {
164 	return (val->val1 >= VEML7700_ALS_PERS_1
165 		&& val->val1 <= VEML7700_ALS_PERS_8)
166 	       || val->val1 == VEML7700_INT_DISABLED;
167 }
168 
veml7700_build_als_conf_param(const struct veml7700_data * data)169 static uint16_t veml7700_build_als_conf_param(const struct veml7700_data *data)
170 {
171 	uint16_t param = 0;
172 	/* Bits 15:13 -> reserved */
173 	/* Bits 12:11 -> gain selection (ALS_GAIN) */
174 	param |= data->gain << 11;
175 	/* Bit 10 -> reserved */
176 	/* Bits 9:6 -> integration time (ALS_IT) */
177 	param |= veml7700_it_values[data->it] << 6;
178 	/* Bits 5:4 -> interrupt persistent protection (ALS_PERS) */
179 	if (data->int_mode != VEML7700_INT_DISABLED) {
180 		param |= data->int_mode << 4;
181 		/* Bit 1 -> interrupt enable (ALS_INT_EN) */
182 		param |= BIT(1);
183 	}
184 	/* Bits 3:2 -> reserved */
185 	/* Bit 0 -> shut down setting (ALS_SD) */
186 	if (data->shut_down) {
187 		param |= BIT(0);
188 	}
189 	return param;
190 }
191 
veml7700_build_psm_param(const struct veml7700_config * conf)192 static uint16_t veml7700_build_psm_param(const struct veml7700_config *conf)
193 {
194 	/* We can directly use the devicetree configuration value. */
195 	return conf->psm;
196 }
197 
veml7700_write(const struct device * dev,uint8_t cmd,uint16_t data)198 static int veml7700_write(const struct device *dev, uint8_t cmd, uint16_t data)
199 {
200 	const struct veml7700_config *conf = dev->config;
201 	uint8_t send_buf[3];
202 
203 	send_buf[0] = cmd; /* byte 0: command code */
204 	sys_put_le16(data, &send_buf[1]); /* bytes 1,2: command arguments */
205 
206 	return i2c_write_dt(&conf->bus, send_buf, ARRAY_SIZE(send_buf));
207 }
208 
veml7700_read(const struct device * dev,uint8_t cmd,uint16_t * data)209 static int veml7700_read(const struct device *dev, uint8_t cmd, uint16_t *data)
210 {
211 	const struct veml7700_config *conf = dev->config;
212 
213 	uint8_t recv_buf[2];
214 	int ret = i2c_write_read_dt(&conf->bus,
215 				    &cmd,
216 				    sizeof(cmd),
217 				    &recv_buf,
218 				    ARRAY_SIZE(recv_buf));
219 	if (ret < 0) {
220 		return ret;
221 	}
222 
223 	*data = sys_get_le16(recv_buf);
224 
225 	return 0;
226 }
227 
veml7700_write_als_conf(const struct device * dev)228 static int veml7700_write_als_conf(const struct device *dev)
229 {
230 	const struct veml7700_data *data = dev->data;
231 	uint16_t param;
232 
233 	param = veml7700_build_als_conf_param(data);
234 	LOG_DBG("Writing ALS configuration: 0x%04x", param);
235 	return veml7700_write(dev, VEML7700_CMDCODE_ALS_CONF, param);
236 }
237 
veml7700_write_psm(const struct device * dev)238 static int veml7700_write_psm(const struct device *dev)
239 {
240 	const struct veml7700_config *conf = dev->config;
241 	uint16_t psm_param;
242 
243 	psm_param = veml7700_build_psm_param(conf);
244 	LOG_DBG("Writing PSM configuration: 0x%04x", psm_param);
245 	return veml7700_write(dev, VEML7700_CMDCODE_PSM, psm_param);
246 }
247 
veml7700_write_thresh_low(const struct device * dev)248 static int veml7700_write_thresh_low(const struct device *dev)
249 {
250 	const struct veml7700_data *data = dev->data;
251 
252 	LOG_DBG("Writing low threshold counts: %d", data->thresh_low);
253 	return veml7700_write(dev, VEML7700_CMDCODE_ALS_WL, data->thresh_low);
254 }
255 
veml7700_write_thresh_high(const struct device * dev)256 static int veml7700_write_thresh_high(const struct device *dev)
257 {
258 	const struct veml7700_data *data = dev->data;
259 
260 	LOG_DBG("Writing high threshold counts: %d", data->thresh_high);
261 	return veml7700_write(dev, VEML7700_CMDCODE_ALS_WH, data->thresh_high);
262 }
263 
veml7700_set_shutdown_flag(const struct device * dev,uint8_t new_val)264 static int veml7700_set_shutdown_flag(const struct device *dev, uint8_t new_val)
265 {
266 	struct veml7700_data *data = dev->data;
267 	uint8_t prev_sd;
268 	int ret;
269 
270 	prev_sd = data->shut_down;
271 	data->shut_down = new_val;
272 
273 	ret = veml7700_write_als_conf(dev);
274 	if (ret < 0) {
275 		data->shut_down = prev_sd;
276 	}
277 	return ret;
278 }
279 
veml7700_fetch_als(const struct device * dev)280 static int veml7700_fetch_als(const struct device *dev)
281 {
282 	struct veml7700_data *data = dev->data;
283 	uint16_t counts;
284 	int ret;
285 
286 	ret = veml7700_read(dev, VEML7700_CMDCODE_ALS, &counts);
287 	if (ret < 0) {
288 		return ret;
289 	}
290 
291 	data->als_counts = counts;
292 	data->als_lux = veml7700_counts_to_lux(data, counts);
293 	LOG_DBG("Read ALS measurement: counts=%d, lux=%d", data->als_counts, data->als_lux);
294 
295 	return 0;
296 }
297 
veml7700_fetch_white(const struct device * dev)298 static int veml7700_fetch_white(const struct device *dev)
299 {
300 	struct veml7700_data *data = dev->data;
301 	uint16_t counts;
302 	int ret;
303 
304 	ret = veml7700_read(dev, VEML7700_CMDCODE_WHITE, &counts);
305 	if (ret < 0) {
306 		return ret;
307 	}
308 
309 	data->white_counts = counts;
310 	LOG_DBG("Read White Light measurement: counts=%d", data->white_counts);
311 
312 	return 0;
313 }
314 
veml7700_fetch_int_flags(const struct device * dev)315 static int veml7700_fetch_int_flags(const struct device *dev)
316 {
317 	struct veml7700_data *data = dev->data;
318 	uint16_t int_flags = 0;
319 	int ret;
320 
321 	ret = veml7700_read(dev, VEML7700_CMDCODE_ALS_INT, &int_flags);
322 	if (ret < 0) {
323 		return ret;
324 	}
325 
326 	data->int_flags = int_flags & VEML7700_ALS_INT_MASK;
327 	LOG_DBG("Read int state: 0x%02x", data->int_flags);
328 
329 	return 0;
330 }
331 
veml7700_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)332 static int veml7700_attr_set(const struct device *dev,
333 			     enum sensor_channel chan,
334 			     enum sensor_attribute attr,
335 			     const struct sensor_value *val)
336 {
337 	if (chan != SENSOR_CHAN_LIGHT) {
338 		return -ENOTSUP;
339 	}
340 
341 	struct veml7700_data *data = dev->data;
342 
343 	if (attr == SENSOR_ATTR_LOWER_THRESH) {
344 		data->thresh_low = veml7700_lux_to_counts(data, val->val1);
345 		return veml7700_write_thresh_low(dev);
346 	} else if (attr == SENSOR_ATTR_UPPER_THRESH) {
347 		data->thresh_high = veml7700_lux_to_counts(data, val->val1);
348 		return veml7700_write_thresh_high(dev);
349 	} else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_GAIN) {
350 		if (veml7700_check_gain(val)) {
351 			data->gain = (enum veml7700_als_gain)val->val1;
352 			return veml7700_write_als_conf(dev);
353 		} else {
354 			return -EINVAL;
355 		}
356 	} else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_ITIME) {
357 		if (veml7700_check_it(val)) {
358 			data->it = (enum veml7700_als_it)val->val1;
359 			return veml7700_write_als_conf(dev);
360 		} else {
361 			return -EINVAL;
362 		}
363 	} else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_INT_MODE) {
364 		if (veml7700_check_int_mode(val)) {
365 			data->int_mode = (enum veml7700_int_mode)val->val1;
366 			return veml7700_write_als_conf(dev);
367 		} else {
368 			return -EINVAL;
369 		}
370 	} else {
371 		return -ENOTSUP;
372 	}
373 }
374 
veml7700_attr_get(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,struct sensor_value * val)375 static int veml7700_attr_get(const struct device *dev,
376 			     enum sensor_channel chan,
377 			     enum sensor_attribute attr,
378 			     struct sensor_value *val)
379 {
380 	if (chan != SENSOR_CHAN_LIGHT) {
381 		return -ENOTSUP;
382 	}
383 
384 	struct veml7700_data *data = dev->data;
385 
386 	if (attr == SENSOR_ATTR_LOWER_THRESH) {
387 		val->val1 = data->thresh_low;
388 	} else if (attr == SENSOR_ATTR_UPPER_THRESH) {
389 		val->val1 = data->thresh_high;
390 	} else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_GAIN) {
391 		val->val1 = data->gain;
392 	} else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_ITIME) {
393 		val->val1 = data->it;
394 	} else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_INT_MODE) {
395 		val->val1 = data->int_mode;
396 	} else {
397 		return -ENOTSUP;
398 	}
399 
400 	val->val2 = 0;
401 
402 	return 0;
403 }
404 
veml7700_perform_single_measurement(const struct device * dev)405 static int veml7700_perform_single_measurement(const struct device *dev)
406 {
407 	struct veml7700_data *data = dev->data;
408 	int ret;
409 
410 	/* Start sensor */
411 	ret = veml7700_set_shutdown_flag(dev, 0);
412 	if (ret < 0) {
413 		return ret;
414 	}
415 
416 	/* Wait for sensor to finish it's startup sequence */
417 	k_msleep(5);
418 	/* Wait for measurement to complete */
419 	veml7700_sleep_by_integration_time(data);
420 
421 	/* Shut down sensor */
422 	return veml7700_set_shutdown_flag(dev, 1);
423 }
424 
veml7700_sample_fetch(const struct device * dev,enum sensor_channel chan)425 static int veml7700_sample_fetch(const struct device *dev,
426 				 enum sensor_channel chan)
427 {
428 	const struct veml7700_config *conf = dev->config;
429 	struct veml7700_data *data;
430 	int ret;
431 
432 	/* Start sensor for new measurement if power saving mode is disabled */
433 	if ((chan == SENSOR_CHAN_LIGHT || chan == SENSOR_CHAN_ALL)
434 	    && conf->psm == VEML7700_PSM_DISABLED) {
435 		ret = veml7700_perform_single_measurement(dev);
436 		if (ret < 0) {
437 			return ret;
438 		}
439 	}
440 
441 	if (chan == SENSOR_CHAN_LIGHT) {
442 		return veml7700_fetch_als(dev);
443 	} else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_INTERRUPT) {
444 		data = dev->data;
445 		if (data->int_mode != VEML7700_INT_DISABLED) {
446 			return veml7700_fetch_int_flags(dev);
447 		} else {
448 			return -ENOTSUP;
449 		}
450 	} else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_WHITE_RAW_COUNTS) {
451 		return veml7700_fetch_white(dev);
452 	} else if (chan == SENSOR_CHAN_ALL) {
453 		data = dev->data;
454 		if (data->int_mode != VEML7700_INT_DISABLED) {
455 			ret = veml7700_fetch_int_flags(dev);
456 			if (ret < 0) {
457 				return ret;
458 			}
459 		}
460 		ret = veml7700_fetch_white(dev);
461 		if (ret < 0) {
462 			return ret;
463 		}
464 		return veml7700_fetch_als(dev);
465 	} else {
466 		return -ENOTSUP;
467 	}
468 }
469 
veml7700_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)470 static int veml7700_channel_get(const struct device *dev,
471 				enum sensor_channel chan,
472 				struct sensor_value *val)
473 {
474 	struct veml7700_data *data = dev->data;
475 
476 	if (chan == SENSOR_CHAN_LIGHT) {
477 		val->val1 = data->als_lux;
478 	} else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_RAW_COUNTS) {
479 		val->val1 = data->als_counts;
480 	} else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_WHITE_RAW_COUNTS) {
481 		val->val1 = data->white_counts;
482 	} else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_INTERRUPT) {
483 		val->val1 = data->int_flags;
484 	} else {
485 		return -ENOTSUP;
486 	}
487 
488 	val->val2 = 0;
489 
490 	return 0;
491 }
492 
493 #ifdef CONFIG_PM_DEVICE
494 
veml7700_pm_action(const struct device * dev,enum pm_device_action action)495 static int veml7700_pm_action(const struct device *dev,
496 			      enum pm_device_action action)
497 {
498 	const struct veml7700_config *conf = dev->config;
499 
500 	if (conf->psm != VEML7700_PSM_DISABLED) {
501 		switch (action) {
502 		case PM_DEVICE_ACTION_SUSPEND:
503 			return veml7700_set_shutdown_flag(dev, 1);
504 
505 		case PM_DEVICE_ACTION_RESUME:
506 			return veml7700_set_shutdown_flag(dev, 0);
507 
508 		default:
509 			return -ENOTSUP;
510 		}
511 	}
512 
513 	return 0;
514 }
515 
516 #endif /* CONFIG_PM_DEVICE */
517 
veml7700_init(const struct device * dev)518 static int veml7700_init(const struct device *dev)
519 {
520 	const struct veml7700_config *conf = dev->config;
521 	struct veml7700_data *data = dev->data;
522 	int ret;
523 
524 	if (!i2c_is_ready_dt(&conf->bus)) {
525 		LOG_ERR("Device not ready");
526 		return -ENODEV;
527 	}
528 
529 	/* Initialize power saving mode */
530 	ret = veml7700_write_psm(dev);
531 	if (ret < 0) {
532 		return ret;
533 	}
534 
535 	/* Set initial data values */
536 	data->thresh_low = 0;
537 	data->thresh_high = 0xFFFF;
538 	data->gain = VEML7700_ALS_GAIN_1_4;
539 	data->it = VEML7700_ALS_IT_100;
540 	data->int_mode = VEML7700_INT_DISABLED;
541 	data->als_counts = 0;
542 	data->als_lux = 0;
543 	data->white_counts = 0;
544 	data->shut_down = (conf->psm != VEML7700_PSM_DISABLED) ? 0 : 1;
545 
546 	/* Initialize sensor configuration */
547 	ret = veml7700_write_thresh_low(dev);
548 	if (ret < 0) {
549 		return ret;
550 	}
551 
552 	ret = veml7700_write_thresh_high(dev);
553 	if (ret < 0) {
554 		return ret;
555 	}
556 
557 	ret = veml7700_write_als_conf(dev);
558 	if (ret < 0) {
559 		return ret;
560 	}
561 
562 	return 0;
563 }
564 
565 static const struct sensor_driver_api veml7700_api = {
566 	.sample_fetch = veml7700_sample_fetch,
567 	.channel_get = veml7700_channel_get,
568 	.attr_set = veml7700_attr_set,
569 	.attr_get = veml7700_attr_get
570 };
571 
572 #define VEML7700_INIT(n)                                                    \
573 	static struct veml7700_data veml7700_data_##n;                      \
574 									    \
575 	static const struct veml7700_config veml7700_config_##n = {         \
576 		.bus = I2C_DT_SPEC_INST_GET(n),                             \
577 		.psm = DT_INST_PROP(n, psm_mode)                            \
578 	};                                                                  \
579                                                                             \
580 	PM_DEVICE_DT_INST_DEFINE(n, veml7700_pm_action);                    \
581                                                                             \
582 	SENSOR_DEVICE_DT_INST_DEFINE(n,                                     \
583 				     veml7700_init,                         \
584 				     PM_DEVICE_DT_INST_GET(n),              \
585 				     &veml7700_data_##n,                    \
586 				     &veml7700_config_##n,                  \
587 				     POST_KERNEL,                           \
588 				     CONFIG_SENSOR_INIT_PRIORITY,           \
589 				     &veml7700_api);
590 
591 DT_INST_FOREACH_STATUS_OKAY(VEML7700_INIT)
592