1 /*
2 * Copyright (c) 2024 Juliane Schulze, deveritec GmbH
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT vishay_vcnl36825t
8
9 #include "vcnl36825t.h"
10
11 #include <stdlib.h>
12
13 #include <zephyr/drivers/i2c.h>
14 #include <zephyr/drivers/sensor.h>
15 #include <zephyr/logging/log.h>
16 #include <zephyr/pm/device.h>
17 #include <zephyr/sys/byteorder.h>
18 #include <zephyr/sys/check.h>
19 #include <zephyr/sys/util.h>
20
21 LOG_MODULE_REGISTER(VCNL36825T, CONFIG_SENSOR_LOG_LEVEL);
22
vcnl36825t_read(const struct i2c_dt_spec * spec,uint8_t reg_addr,uint16_t * value)23 int vcnl36825t_read(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t *value)
24 {
25 uint8_t rx_buf[2];
26 int rc;
27
28 rc = i2c_write_read_dt(spec, ®_addr, sizeof(reg_addr), rx_buf, sizeof(rx_buf));
29 if (rc < 0) {
30 return rc;
31 }
32
33 *value = sys_get_le16(rx_buf);
34
35 return 0;
36 }
37
vcnl36825t_write(const struct i2c_dt_spec * spec,uint8_t reg_addr,uint16_t value)38 int vcnl36825t_write(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t value)
39 {
40 uint8_t tx_buf[3] = {reg_addr};
41
42 sys_put_le16(value, &tx_buf[1]);
43 return i2c_write_dt(spec, tx_buf, sizeof(tx_buf));
44 }
45
vcnl36825t_update(const struct i2c_dt_spec * spec,uint8_t reg_addr,uint16_t mask,uint16_t value)46 int vcnl36825t_update(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t mask,
47 uint16_t value)
48 {
49 int rc;
50 uint16_t old_value, new_value;
51
52 rc = vcnl36825t_read(spec, reg_addr, &old_value);
53 if (rc < 0) {
54 return rc;
55 }
56
57 new_value = (old_value & ~mask) | (value & mask);
58
59 if (new_value == old_value) {
60 return 0;
61 }
62
63 return vcnl36825t_write(spec, reg_addr, new_value);
64 }
65
66 #if CONFIG_PM_DEVICE
vcnl36825t_pm_action(const struct device * dev,enum pm_device_action action)67 static int vcnl36825t_pm_action(const struct device *dev, enum pm_device_action action)
68 {
69 const struct vcnl36825t_config *config = dev->config;
70 struct vcnl36825t_data *data = dev->data;
71
72 int rc;
73
74 switch (action) {
75 case PM_DEVICE_ACTION_RESUME:
76 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF1, VCNL36825T_PS_ON_MSK,
77 VCNL36825T_PS_ON);
78 if (rc < 0) {
79 return rc;
80 }
81
82 if (config->low_power) {
83 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF4,
84 VCNL36825T_PS_LPEN_MSK, VCNL36825T_PS_LPEN_ENABLED);
85 if (rc < 0) {
86 return rc;
87 }
88 }
89
90 if (config->operation_mode == VCNL36825T_OPERATION_MODE_AUTO) {
91 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF3,
92 VCNL36825T_PS_AF_MSK, VCNL36825T_PS_AF_AUTO);
93 if (rc < 0) {
94 return rc;
95 }
96 }
97
98 k_usleep(VCNL36825T_POWER_UP_US);
99
100 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_ST_MSK,
101 VCNL36825T_PS_ST_START);
102 if (rc < 0) {
103 return rc;
104 }
105
106 data->meas_timeout_us = data->meas_timeout_wakeup_us;
107
108 break;
109 case PM_DEVICE_ACTION_SUSPEND:
110 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_ST_MSK,
111 VCNL36825T_PS_ST_STOP);
112 if (rc < 0) {
113 return rc;
114 }
115
116 if (config->operation_mode == VCNL36825T_OPERATION_MODE_AUTO) {
117 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF3,
118 VCNL36825T_PS_AF_MSK, VCNL36825T_PS_AF_FORCE);
119 if (rc < 0) {
120 return rc;
121 }
122 }
123
124 /* unset LPEN-bit if active, otherwise high current draw can be observed */
125 if (config->low_power) {
126 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF4,
127 VCNL36825T_PS_LPEN_MSK, VCNL36825T_PS_LPEN_DISABLED);
128 if (rc < 0) {
129 return rc;
130 }
131 }
132
133 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF1, VCNL36825T_PS_ON_MSK,
134 VCNL36825T_PS_OFF);
135 if (rc < 0) {
136 return rc;
137 }
138 break;
139 default:
140 LOG_ERR("action %d not supported", (int)action);
141 return -ENOTSUP;
142 }
143
144 return 0;
145 }
146
147 #endif
148
vcnl36825t_sample_fetch(const struct device * dev,enum sensor_channel chan)149 static int vcnl36825t_sample_fetch(const struct device *dev, enum sensor_channel chan)
150 {
151 const struct vcnl36825t_config *config = dev->config;
152 struct vcnl36825t_data *data = dev->data;
153 int rc;
154
155 #if CONFIG_PM_DEVICE
156 enum pm_device_state state;
157
158 (void)pm_device_state_get(dev, &state);
159 if (state != PM_DEVICE_STATE_ACTIVE) {
160 return -EBUSY;
161 }
162 #endif
163
164 switch (chan) {
165 case SENSOR_CHAN_ALL:
166 case SENSOR_CHAN_PROX:
167 if (config->operation_mode == VCNL36825T_OPERATION_MODE_FORCE) {
168 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF3,
169 VCNL36825T_PS_TRIG_MSK, VCNL36825T_PS_TRIG_ONCE);
170 if (rc < 0) {
171 LOG_ERR("could not trigger proximity measurement %d", rc);
172 return rc;
173 }
174
175 k_usleep(data->meas_timeout_us);
176
177 #ifdef CONFIG_PM_DEVICE
178 data->meas_timeout_us = data->meas_timeout_running_us;
179 #endif
180 }
181
182 rc = vcnl36825t_read(&config->i2c, VCNL36825T_REG_PS_DATA, &data->proximity);
183 if (rc < 0) {
184 LOG_ERR("could not fetch proximity measurement %d", rc);
185 return rc;
186 }
187
188 break;
189 default:
190 LOG_ERR("invalid sensor channel");
191 return -EINVAL;
192 }
193
194 return 0;
195 }
196
vcnl36825t_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)197 static int vcnl36825t_channel_get(const struct device *dev, enum sensor_channel chan,
198 struct sensor_value *val)
199 {
200 struct vcnl36825t_data *data = dev->data;
201
202 switch (chan) {
203 case SENSOR_CHAN_ALL:
204 case SENSOR_CHAN_PROX:
205 val->val1 = data->proximity & VCNL36825T_OS_DATA_MSK;
206 val->val2 = 0;
207 break;
208 default:
209 return -ENOTSUP;
210 }
211
212 return 0;
213 }
214
vcnl36825t_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)215 static int vcnl36825t_attr_set(const struct device *dev, enum sensor_channel chan,
216 enum sensor_attribute attr, const struct sensor_value *val)
217 {
218 CHECKIF(dev == NULL) {
219 LOG_ERR("dev: NULL");
220 return -EINVAL;
221 }
222
223 CHECKIF(val == NULL) {
224 LOG_ERR("val: NULL");
225 return -EINVAL;
226 }
227
228 int __maybe_unused rc;
229
230 switch (attr) {
231 default:
232 #if CONFIG_VCNL36825T_TRIGGER
233 rc = vcnl36825t_trigger_attr_set(dev, chan, attr, val);
234 if (rc < 0) {
235 return rc;
236 }
237 #else
238 return -ENOTSUP;
239 #endif
240 }
241
242 return 0;
243 }
244
245 /**
246 * @brief calculate measurement timeout in us
247 *
248 * @param meas_duration base duration of a measurement in us*VCNL36825T_FORCED_FACTOR_SCALE
249 * @param meas_factor factor which needs to be multiplied to cope with additional delays (multiplied
250 * by VCNL36825T_FORCED_FACTOR_SCALE)
251 *
252 * @note
253 * Always add 1 to prevent corner case losses due to precision.
254 */
vcn36825t_measurement_timeout_us(unsigned int meas_duration,unsigned int forced_factor)255 static inline unsigned int vcn36825t_measurement_timeout_us(unsigned int meas_duration,
256 unsigned int forced_factor)
257 {
258 return ((meas_duration * forced_factor) / VCNL36825T_FORCED_FACTOR_SCALE) + 1;
259 }
260
261 /**
262 * @brief helper function to configure the registers
263 *
264 * @param dev pointer to the VCNL36825T instance
265 */
vcnl36825t_init_registers(const struct device * dev)266 static int vcnl36825t_init_registers(const struct device *dev)
267 {
268 const struct vcnl36825t_config *config = dev->config;
269 struct vcnl36825t_data *data = dev->data;
270
271 int rc;
272 uint16_t reg_value;
273
274 unsigned int meas_duration = 1;
275
276 /* reset registers as defined by the datasheet */
277 const uint16_t resetValues[][2] = {
278 {VCNL36825T_REG_PS_CONF1, VCNL36825T_CONF1_DEFAULT},
279 {VCNL36825T_REG_PS_CONF2, VCNL36825T_CONF2_DEFAULT},
280 {VCNL36825T_REG_PS_CONF3, VCNL36825T_CONF3_DEFAULT},
281 {VCNL36825T_REG_PS_THDL, VCNL36825T_THDL_DEFAULT},
282 {VCNL36825T_REG_PS_THDH, VCNL36825T_THDH_DEFAULT},
283 {VCNL36825T_REG_PS_CANC, VCNL36825T_CANC_DEFAULT},
284 {VCNL36825T_REG_PS_CONF4, VCNL36825T_CONF4_DEFAULT},
285 };
286
287 for (size_t i = 0; i < ARRAY_SIZE(resetValues); ++i) {
288 vcnl36825t_write(&config->i2c, resetValues[i][0], resetValues[i][1]);
289 }
290
291 /* PS_CONF1 */
292 reg_value = 0x01; /* must be set according to datasheet */
293 reg_value |= VCNL36825T_PS_ON;
294
295 rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF1, reg_value);
296 if (rc < 0) {
297 LOG_ERR("I2C for PS_ON returned %d", rc);
298 return -EIO;
299 }
300
301 reg_value |= VCNL36825T_PS_CAL;
302 reg_value |= 1 << 9; /* reserved, must be set by datasheet */
303
304 rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF1, reg_value);
305 if (rc < 0) {
306 LOG_ERR("I2C for PS_CAL returned %d", rc);
307 }
308
309 k_usleep(VCNL36825T_POWER_UP_US);
310
311 /* PS_CONF2 */
312 reg_value = 0;
313
314 switch (config->period) {
315 case VCNL36825T_MEAS_PERIOD_10MS:
316 reg_value |= VCNL36825T_PS_PERIOD_10MS;
317 break;
318 case VCNL36825T_MEAS_PERIOD_20MS:
319 reg_value |= VCNL36825T_PS_PERIOD_20MS;
320 break;
321 case VCNL36825T_MEAS_PERIOD_40MS:
322 reg_value |= VCNL36825T_PS_PERIOD_40MS;
323 break;
324 case VCNL36825T_MEAS_PERIOD_80MS:
325 __fallthrough;
326 default:
327 reg_value |= VCNL36825T_PS_PERIOD_80MS;
328 break;
329 }
330
331 reg_value |= VCNL36825T_PS_PERS_1;
332 reg_value |= VCNL36825T_PS_ST_STOP;
333
334 switch (config->proximity_it) {
335 case VCNL36825T_PROXIMITY_INTEGRATION_1T:
336 reg_value |= VCNL36825T_PS_IT_1T;
337 meas_duration *= 1;
338 break;
339 case VCNL36825T_PROXIMITY_INTEGRATION_2T:
340 reg_value |= VCNL36825T_PS_IT_2T;
341 meas_duration *= 2;
342 break;
343 case VCNL36825T_PROXIMITY_INTEGRATION_4T:
344 reg_value |= VCNL36825T_PS_IT_4T;
345 meas_duration *= 4;
346 break;
347 case VCNL36825T_PROXIMITY_INTEGRATION_8T:
348 __fallthrough;
349 default:
350 reg_value |= VCNL36825T_PS_IT_8T;
351 meas_duration *= 8;
352 break;
353 }
354
355 switch (config->multi_pulse) {
356 case VCNL38652T_MULTI_PULSE_1:
357 reg_value |= VCNL36825T_MPS_PULSES_1;
358 break;
359 case VCNL38652T_MULTI_PULSE_2:
360 reg_value |= VCNL36825T_MPS_PULSES_2;
361 break;
362 case VCNL38652T_MULTI_PULSE_4:
363 reg_value |= VCNL36825T_MPS_PULSES_4;
364 break;
365 case VCNL38652T_MULTI_PULSE_8:
366 __fallthrough;
367 default:
368 reg_value |= VCNL36825T_MPS_PULSES_8;
369 break;
370 }
371
372 switch (config->proximity_itb) {
373 case VCNL36825T_PROXIMITY_INTEGRATION_DURATION_25us:
374 reg_value |= VCNL36825T_PS_ITB_25us;
375 meas_duration *= 25;
376 break;
377 case VCNL36825T_PROXIMITY_INTEGRATION_DURATION_50us:
378 __fallthrough;
379 default:
380 reg_value |= VCNL36825T_PS_ITB_50us;
381 meas_duration *= 50;
382 break;
383 }
384
385 if (config->high_gain) {
386 reg_value |= VCNL36825T_PS_HG_HIGH;
387 }
388
389 rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF2, reg_value);
390 if (rc < 0) {
391 LOG_ERR("I2C for setting PS_CONF2 returned %d", rc);
392 return -EIO;
393 }
394
395 /* PS_CONF3 */
396 reg_value = 0;
397
398 if (config->operation_mode == VCNL36825T_OPERATION_MODE_FORCE) {
399 reg_value |= VCNL36825T_PS_AF_FORCE;
400 }
401
402 switch (config->laser_current) {
403 case VCNL36825T_LASER_CURRENT_10MS:
404 reg_value |= VCNL36825T_PS_I_VCSEL_10MA;
405 break;
406 case VCNL36825T_LASER_CURRENT_12MS:
407 reg_value |= VCNL36825T_PS_I_VCSEL_12MA;
408 break;
409 case VCNL36825T_LASER_CURRENT_14MS:
410 reg_value |= VCNL36825T_PS_I_VCSEL_14MA;
411 break;
412 case VCNL36825T_LASER_CURRENT_16MS:
413 reg_value |= VCNL36825T_PS_I_VCSEL_16MA;
414 break;
415 case VCNL36825T_LASER_CURRENT_18MS:
416 reg_value |= VCNL36825T_PS_I_VCSEL_18MA;
417 break;
418 case VCNL36825T_LASER_CURRENT_20MS:
419 __fallthrough;
420 default:
421 reg_value |= VCNL36825T_PS_I_VCSEL_20MA;
422 break;
423 }
424
425 if (config->high_dynamic_output) {
426 reg_value |= VCNL36825T_PS_HD_16BIT;
427 }
428
429 if (config->sunlight_cancellation) {
430 reg_value |= VCNL36825T_PS_SC_ENABLED;
431 }
432
433 rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF3, reg_value);
434 if (rc < 0) {
435 LOG_ERR("I2C for setting PS_CONF3 returned %d", rc);
436 return -EIO;
437 }
438
439 /* PS_CONF4 */
440 reg_value = 0;
441
442 if (config->low_power) {
443 reg_value |= VCNL36825T_PS_LPEN_ENABLED;
444 }
445
446 switch (config->period) {
447 case VCNL36825T_MEAS_PERIOD_40MS:
448 reg_value |= VCNL36825T_PS_LPPER_40MS;
449 break;
450 case VCNL36825T_MEAS_PERIOD_80MS:
451 reg_value |= VCNL36825T_PS_LPPER_80MS;
452 break;
453 case VCNL36825T_MEAS_PERIOD_160MS:
454 reg_value |= VCNL36825T_PS_LPPER_160MS;
455 break;
456 case VCNL36825T_MEAS_PERIOD_320MS:
457 __fallthrough;
458 default:
459 reg_value |= VCNL36825T_PS_LPPER_320MS;
460 break;
461 }
462
463 rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF4, reg_value);
464 if (rc < 0) {
465 LOG_ERR("I2C for setting PS_CONF4 returned %d", rc);
466 return -EIO;
467 }
468
469 data->meas_timeout_us =
470 vcn36825t_measurement_timeout_us(meas_duration, VCNL36825T_FORCED_FACTOR_SUM);
471
472 #ifdef CONFIG_PM_DEVICE
473 data->meas_timeout_running_us = data->meas_timeout_us;
474 data->meas_timeout_wakeup_us = vcn36825t_measurement_timeout_us(
475 meas_duration, VCNL36825T_FORCED_FACTOR_WAKEUP_SUM);
476
477 /* ensure that the time is roughly around VCNL36825T_FORCED_WAKEUP_DELAY_MAX_US if the
478 * wakeup time is bigger but "normal" measurement time is less
479 */
480 if (data->meas_timeout_wakeup_us > VCNL36825T_FORCED_WAKEUP_DELAY_MAX_US) {
481 data->meas_timeout_wakeup_us =
482 MAX(data->meas_timeout_running_us, VCNL36825T_FORCED_WAKEUP_DELAY_MAX_US);
483 }
484 #endif
485
486 return 0;
487 }
488
vcnl36825t_init(const struct device * dev)489 static int vcnl36825t_init(const struct device *dev)
490 {
491 const struct vcnl36825t_config *config = dev->config;
492 int rc;
493
494 uint16_t reg_value;
495
496 if (!i2c_is_ready_dt(&config->i2c)) {
497 LOG_ERR("device is not ready");
498 return -ENODEV;
499 }
500
501 rc = vcnl36825t_read(&config->i2c, VCNL36825T_REG_DEV_ID, ®_value);
502 if (rc < 0) {
503 LOG_ERR("could not read device id");
504 return rc;
505 }
506
507 if ((reg_value & VCNL36825T_ID_MSK) != VCNL36825T_DEVICE_ID) {
508 LOG_ERR("incorrect device id (%d)", reg_value);
509 return -EIO;
510 }
511
512 LOG_INF("version code: 0x%X",
513 (unsigned int)FIELD_GET(VCNL36825T_VERSION_CODE_MSK, reg_value));
514
515 rc = vcnl36825t_init_registers(dev);
516 if (rc < 0) {
517 return rc;
518 }
519
520 #if CONFIG_VCNL36825T_TRIGGER
521 rc = vcnl36825t_trigger_init(dev);
522 if (rc < 0) {
523 return rc;
524 }
525 #endif
526 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_ST_MSK,
527 VCNL36825T_PS_ST_START);
528 if (rc < 0) {
529 LOG_ERR("error starting measurement");
530 return -EIO;
531 }
532
533 return 0;
534 }
535
536 static DEVICE_API(sensor, vcnl36825t_driver_api) = {
537 .sample_fetch = vcnl36825t_sample_fetch,
538 .channel_get = vcnl36825t_channel_get,
539 .attr_set = vcnl36825t_attr_set,
540 #if CONFIG_VCNL36825T_TRIGGER
541 .trigger_set = vcnl36825t_trigger_set,
542 #endif
543 };
544
545 #define VCNL36825T_DEFINE(inst) \
546 BUILD_ASSERT(!DT_INST_PROP(inst, low_power) || (DT_INST_PROP(inst, measurement_period) >= \
547 VCNL36825T_PS_LPPER_VALUE_MIN_MS), \
548 "measurement-period must be greater/equal 40 ms in low-power mode"); \
549 BUILD_ASSERT( \
550 DT_INST_PROP(inst, low_power) || (DT_INST_PROP(inst, measurement_period) <= \
551 VCNL36825T_PS_PERIOD_VALUE_MAX_MS), \
552 "measurement-period must be less/equal 80 ms with deactivated low-power mode"); \
553 BUILD_ASSERT(!DT_INST_PROP(inst, low_power) || (DT_INST_ENUM_IDX(inst, operation_mode) == \
554 VCNL36825T_OPERATION_MODE_AUTO), \
555 "operation-mode \"force\" only available if low-power mode deactivated"); \
556 static struct vcnl36825t_data vcnl36825t_data_##inst; \
557 static const struct vcnl36825t_config vcnl36825t_config_##inst = { \
558 .i2c = I2C_DT_SPEC_INST_GET(inst), \
559 .operation_mode = DT_INST_ENUM_IDX(inst, operation_mode), \
560 .period = DT_INST_ENUM_IDX(inst, measurement_period), \
561 .proximity_it = DT_INST_ENUM_IDX(inst, proximity_it), \
562 .proximity_itb = DT_INST_ENUM_IDX(inst, proximity_itb), \
563 .multi_pulse = DT_INST_ENUM_IDX(inst, multi_pulse), \
564 .low_power = DT_INST_PROP(inst, low_power), \
565 .high_gain = DT_INST_PROP(inst, high_gain), \
566 .laser_current = DT_INST_ENUM_IDX(inst, laser_current), \
567 .high_dynamic_output = DT_INST_PROP(inst, high_dynamic_output), \
568 .sunlight_cancellation = DT_INST_PROP(inst, sunlight_cancellation), \
569 IF_ENABLED(CONFIG_VCNL36825T_TRIGGER, \
570 (.int_gpio = GPIO_DT_SPEC_INST_GET(inst, int_gpios), \
571 .int_mode = DT_INST_ENUM_IDX(inst, int_mode), \
572 .int_proximity_count = DT_INST_PROP(inst, int_proximity_count), \
573 .int_smart_persistence = DT_INST_PROP(inst, int_smart_persistence)))}; \
574 IF_ENABLED(CONFIG_PM_DEVICE, (PM_DEVICE_DT_INST_DEFINE(inst, vcnl36825t_pm_action))); \
575 SENSOR_DEVICE_DT_INST_DEFINE( \
576 inst, vcnl36825t_init, \
577 COND_CODE_1(CONFIG_PM_DEVICE, (PM_DEVICE_DT_INST_GET(inst)), (NULL)), \
578 &vcnl36825t_data_##inst, &vcnl36825t_config_##inst, POST_KERNEL, \
579 CONFIG_SENSOR_INIT_PRIORITY, &vcnl36825t_driver_api);
580
581 DT_INST_FOREACH_STATUS_OKAY(VCNL36825T_DEFINE)
582