zephyr/drivers/sensor/vishay/vcnl36825t/vcnl36825t.c

501 lines
14 KiB
C

/*
* Copyright (c) 2024 Juliane Schulze, deveritec GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT vishay_vcnl36825t
#include "vcnl36825t.h"
#include <stdlib.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
LOG_MODULE_REGISTER(VCNL36825T, CONFIG_SENSOR_LOG_LEVEL);
static int vcnl36825t_read(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t *value)
{
uint8_t rx_buf[2];
int rc;
rc = i2c_write_read_dt(spec, &reg_addr, sizeof(reg_addr), rx_buf, sizeof(rx_buf));
if (rc < 0) {
return rc;
}
*value = sys_get_le16(rx_buf);
return 0;
}
static int vcnl36825t_write(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t value)
{
uint8_t tx_buf[3] = {reg_addr};
sys_put_le16(value, &tx_buf[1]);
return i2c_write_dt(spec, tx_buf, sizeof(tx_buf));
}
static int vcnl36825t_update(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t mask,
uint16_t value)
{
int rc;
uint16_t old_value, new_value;
rc = vcnl36825t_read(spec, reg_addr, &old_value);
if (rc < 0) {
return rc;
}
new_value = (old_value & ~mask) | (value & mask);
if (new_value == old_value) {
return 0;
}
return vcnl36825t_write(spec, reg_addr, new_value);
}
#if CONFIG_PM_DEVICE
static int vcnl36825t_pm_action(const struct device *dev, enum pm_device_action action)
{
const struct vcnl36825t_config *config = dev->config;
int rc;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF1, VCNL36825T_PS_ON_MSK,
VCNL36825T_PS_ON);
if (rc < 0) {
return rc;
}
if (config->low_power) {
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF4,
VCNL36825T_PS_LPEN_MSK, VCNL36825T_PS_LPEN_ENABLED);
if (rc < 0) {
return rc;
}
}
if (config->operation_mode == VCNL36825T_OPERATION_MODE_AUTO) {
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF3,
VCNL36825T_PS_AF_MSK, VCNL36825T_PS_AF_AUTO);
if (rc < 0) {
return rc;
}
}
k_usleep(VCNL36825T_POWER_UP_US);
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_ST_MSK,
VCNL36825T_PS_ST_START);
if (rc < 0) {
return rc;
}
break;
case PM_DEVICE_ACTION_SUSPEND:
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_ST_MSK,
VCNL36825T_PS_ST_STOP);
if (rc < 0) {
return rc;
}
if (config->operation_mode == VCNL36825T_OPERATION_MODE_AUTO) {
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF3,
VCNL36825T_PS_AF_MSK, VCNL36825T_PS_AF_FORCE);
if (rc < 0) {
return rc;
}
}
/* unset LPEN-bit if active, otherwise high current draw can be observed */
if (config->low_power) {
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF4,
VCNL36825T_PS_LPEN_MSK, VCNL36825T_PS_LPEN_DISABLED);
if (rc < 0) {
return rc;
}
}
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF1, VCNL36825T_PS_ON_MSK,
VCNL36825T_PS_OFF);
if (rc < 0) {
return rc;
}
break;
default:
LOG_ERR("action %d not supported", (int)action);
return -ENOTSUP;
}
return 0;
}
#endif
static int vcnl36825t_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
const struct vcnl36825t_config *config = dev->config;
struct vcnl36825t_data *data = dev->data;
int rc;
#if CONFIG_PM_DEVICE
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -EBUSY;
}
#endif
switch (chan) {
case SENSOR_CHAN_ALL:
case SENSOR_CHAN_PROX:
if (config->operation_mode == VCNL36825T_OPERATION_MODE_FORCE) {
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF3,
VCNL36825T_PS_TRIG_MSK, VCNL36825T_PS_TRIG_ONCE);
if (rc < 0) {
LOG_ERR("could not trigger proximity measurement %d", rc);
return rc;
}
k_usleep(data->meas_timeout_us);
}
rc = vcnl36825t_read(&config->i2c, VCNL36825T_REG_PS_DATA, &data->proximity);
if (rc < 0) {
LOG_ERR("could not fetch proximity measurement %d", rc);
return rc;
}
break;
default:
LOG_ERR("invalid sensor channel");
return -EINVAL;
}
return 0;
}
static int vcnl36825t_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct vcnl36825t_data *data = dev->data;
switch (chan) {
case SENSOR_CHAN_ALL:
case SENSOR_CHAN_PROX:
val->val1 = data->proximity & VCNL36825T_OS_DATA_MSK;
val->val2 = 0;
break;
default:
return -ENOTSUP;
}
return 0;
}
/**
* @brief helper function to configure the registers
*
* @param dev pointer to the VCNL36825T instance
*/
static int vcnl36825t_init_registers(const struct device *dev)
{
const struct vcnl36825t_config *config = dev->config;
struct vcnl36825t_data *data = dev->data;
int rc;
uint16_t reg_value;
/* reset registers as defined by the datasheet */
const uint16_t resetValues[][2] = {
{VCNL36825T_REG_PS_CONF1, VCNL36825T_CONF1_DEFAULT},
{VCNL36825T_REG_PS_CONF2, VCNL36825T_CONF2_DEFAULT},
{VCNL36825T_REG_PS_CONF3, VCNL36825T_CONF3_DEFAULT},
{VCNL36825T_REG_PS_THDL, VCNL36825T_THDL_DEFAULT},
{VCNL36825T_REG_PS_THDH, VCNL36825T_THDH_DEFAULT},
{VCNL36825T_REG_PS_CANC, VCNL36825T_CANC_DEFAULT},
{VCNL36825T_REG_PS_CONF4, VCNL36825T_CONF4_DEFAULT},
};
for (size_t i = 0; i < ARRAY_SIZE(resetValues); ++i) {
vcnl36825t_write(&config->i2c, resetValues[i][0], resetValues[i][1]);
}
data->meas_timeout_us = 1;
/* PS_CONF1 */
reg_value = 0x01; /* must be set according to datasheet */
reg_value |= VCNL36825T_PS_ON;
rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF1, reg_value);
if (rc < 0) {
LOG_ERR("I2C for PS_ON returned %d", rc);
return -EIO;
}
reg_value |= VCNL36825T_PS_CAL;
reg_value |= 1 << 9; /* reserved, must be set by datasheet */
rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF1, reg_value);
if (rc < 0) {
LOG_ERR("I2C for PS_CAL returned %d", rc);
}
k_usleep(VCNL36825T_POWER_UP_US);
/* PS_CONF2 */
reg_value = 0;
switch (config->period) {
case VCNL36825T_MEAS_PERIOD_10MS:
reg_value |= VCNL36825T_PS_PERIOD_10MS;
break;
case VCNL36825T_MEAS_PERIOD_20MS:
reg_value |= VCNL36825T_PS_PERIOD_20MS;
break;
case VCNL36825T_MEAS_PERIOD_40MS:
reg_value |= VCNL36825T_PS_PERIOD_40MS;
break;
case VCNL36825T_MEAS_PERIOD_80MS:
__fallthrough;
default:
reg_value |= VCNL36825T_PS_PERIOD_80MS;
break;
}
reg_value |= VCNL36825T_PS_PERS_1;
reg_value |= VCNL36825T_PS_ST_STOP;
switch (config->proximity_it) {
case VCNL36825T_PROXIMITY_INTEGRATION_1T:
reg_value |= VCNL36825T_PS_IT_1T;
data->meas_timeout_us *= 1;
break;
case VCNL36825T_PROXIMITY_INTEGRATION_2T:
reg_value |= VCNL36825T_PS_IT_2T;
data->meas_timeout_us *= 2;
break;
case VCNL36825T_PROXIMITY_INTEGRATION_4T:
reg_value |= VCNL36825T_PS_IT_4T;
data->meas_timeout_us *= 4;
break;
case VCNL36825T_PROXIMITY_INTEGRATION_8T:
__fallthrough;
default:
reg_value |= VCNL36825T_PS_IT_8T;
data->meas_timeout_us *= 8;
break;
}
switch (config->multi_pulse) {
case VCNL38652T_MULTI_PULSE_1:
reg_value |= VCNL36825T_MPS_PULSES_1;
break;
case VCNL38652T_MULTI_PULSE_2:
reg_value |= VCNL36825T_MPS_PULSES_2;
break;
case VCNL38652T_MULTI_PULSE_4:
reg_value |= VCNL36825T_MPS_PULSES_4;
break;
case VCNL38652T_MULTI_PULSE_8:
__fallthrough;
default:
reg_value |= VCNL36825T_MPS_PULSES_8;
break;
}
switch (config->proximity_itb) {
case VCNL36825T_PROXIMITY_INTEGRATION_DURATION_25us:
reg_value |= VCNL36825T_PS_ITB_25us;
data->meas_timeout_us *= 25;
break;
case VCNL36825T_PROXIMITY_INTEGRATION_DURATION_50us:
__fallthrough;
default:
reg_value |= VCNL36825T_PS_ITB_50us;
data->meas_timeout_us *= 50;
break;
}
if (config->high_gain) {
reg_value |= VCNL36825T_PS_HG_HIGH;
}
rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF2, reg_value);
if (rc < 0) {
LOG_ERR("I2C for setting PS_CONF2 returned %d", rc);
return -EIO;
}
/* PS_CONF3 */
reg_value = 0;
if (config->operation_mode == VCNL36825T_OPERATION_MODE_FORCE) {
reg_value |= VCNL36825T_PS_AF_FORCE;
}
switch (config->laser_current) {
case VCNL36825T_LASER_CURRENT_10MS:
reg_value |= VCNL36825T_PS_I_VCSEL_10MA;
break;
case VCNL36825T_LASER_CURRENT_12MS:
reg_value |= VCNL36825T_PS_I_VCSEL_12MA;
break;
case VCNL36825T_LASER_CURRENT_14MS:
reg_value |= VCNL36825T_PS_I_VCSEL_14MA;
break;
case VCNL36825T_LASER_CURRENT_16MS:
reg_value |= VCNL36825T_PS_I_VCSEL_16MA;
break;
case VCNL36825T_LASER_CURRENT_18MS:
reg_value |= VCNL36825T_PS_I_VCSEL_18MA;
break;
case VCNL36825T_LASER_CURRENT_20MS:
__fallthrough;
default:
reg_value |= VCNL36825T_PS_I_VCSEL_20MA;
break;
}
if (config->high_dynamic_output) {
reg_value |= VCNL36825T_PS_HD_16BIT;
}
if (config->sunlight_cancellation) {
reg_value |= VCNL36825T_PS_SC_ENABLED;
}
rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF3, reg_value);
if (rc < 0) {
LOG_ERR("I2C for setting PS_CONF3 returned %d", rc);
return -EIO;
}
/* PS_CONF4 */
reg_value = 0;
if (config->low_power) {
reg_value |= VCNL36825T_PS_LPEN_ENABLED;
}
switch (config->period) {
case VCNL36825T_MEAS_PERIOD_40MS:
reg_value |= VCNL36825T_PS_LPPER_40MS;
break;
case VCNL36825T_MEAS_PERIOD_80MS:
reg_value |= VCNL36825T_PS_LPPER_80MS;
break;
case VCNL36825T_MEAS_PERIOD_160MS:
reg_value |= VCNL36825T_PS_LPPER_160MS;
break;
case VCNL36825T_MEAS_PERIOD_320MS:
__fallthrough;
default:
reg_value |= VCNL36825T_PS_LPPER_320MS;
break;
}
rc = vcnl36825t_write(&config->i2c, VCNL36825T_REG_PS_CONF4, reg_value);
if (rc < 0) {
LOG_ERR("I2C for setting PS_CONF4 returned %d", rc);
return -EIO;
}
/* calculate measurement timeout
* Note: always add 1 to prevent corner case losses due to precision.
*/
data->meas_timeout_us =
(data->meas_timeout_us * VCNL36825T_FORCED_FACTOR_SUM) /
(VCNL36825T_FORCED_FACTOR_SCALE) +
1;
return 0;
}
static int vcnl36825t_init(const struct device *dev)
{
const struct vcnl36825t_config *config = dev->config;
int rc;
uint16_t reg_value;
if (!i2c_is_ready_dt(&config->i2c)) {
LOG_ERR("device is not ready");
return -ENODEV;
}
rc = vcnl36825t_read(&config->i2c, VCNL36825T_REG_DEV_ID, &reg_value);
if (rc < 0) {
LOG_ERR("could not read device id");
return rc;
}
if ((reg_value & VCNL36825T_ID_MSK) != VCNL36825T_DEVICE_ID) {
LOG_ERR("incorrect device id (%d)", reg_value);
return -EIO;
}
LOG_INF("version code: 0x%X",
(unsigned int)FIELD_GET(VCNL36825T_VERSION_CODE_MSK, reg_value));
rc = vcnl36825t_init_registers(dev);
if (rc < 0) {
return rc;
}
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_ST_MSK,
VCNL36825T_PS_ST_START);
if (rc < 0) {
LOG_ERR("error starting measurement");
return -EIO;
}
return 0;
}
static const struct sensor_driver_api vcnl36825t_driver_api = {
.sample_fetch = vcnl36825t_sample_fetch,
.channel_get = vcnl36825t_channel_get,
};
#define VCNL36825T_DEFINE(inst) \
BUILD_ASSERT(!DT_INST_PROP(inst, low_power) || (DT_INST_PROP(inst, measurement_period) >= \
VCNL36825T_PS_LPPER_VALUE_MIN_MS), \
"measurement-period must be greater/equal 40 ms in low-power mode"); \
BUILD_ASSERT( \
DT_INST_PROP(inst, low_power) || (DT_INST_PROP(inst, measurement_period) <= \
VCNL36825T_PS_PERIOD_VALUE_MAX_MS), \
"measurement-period must be less/equal 80 ms with deactivated low-power mode"); \
BUILD_ASSERT(!DT_INST_PROP(inst, low_power) || (DT_INST_ENUM_IDX(inst, operation_mode) == \
VCNL36825T_OPERATION_MODE_AUTO), \
"operation-mode \"force\" only available if low-power mode deactivated"); \
static struct vcnl36825t_data vcnl36825t_data_##inst; \
static const struct vcnl36825t_config vcnl36825t_config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
.operation_mode = DT_INST_ENUM_IDX(inst, operation_mode), \
.period = DT_INST_ENUM_IDX(inst, measurement_period), \
.proximity_it = DT_INST_ENUM_IDX(inst, proximity_it), \
.proximity_itb = DT_INST_ENUM_IDX(inst, proximity_itb), \
.multi_pulse = DT_INST_ENUM_IDX(inst, multi_pulse), \
.low_power = DT_INST_PROP(inst, low_power), \
.high_gain = DT_INST_PROP(inst, high_gain), \
.laser_current = DT_INST_ENUM_IDX(inst, laser_current), \
.high_dynamic_output = DT_INST_PROP(inst, high_dynamic_output), \
.sunlight_cancellation = DT_INST_PROP(inst, sunlight_cancellation), \
}; \
IF_ENABLED(CONFIG_PM_DEVICE, (PM_DEVICE_DT_INST_DEFINE(inst, vcnl36825t_pm_action))); \
SENSOR_DEVICE_DT_INST_DEFINE( \
inst, vcnl36825t_init, \
COND_CODE_1(CONFIG_PM_DEVICE, (PM_DEVICE_DT_INST_GET(inst)), (NULL)), \
&vcnl36825t_data_##inst, &vcnl36825t_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &vcnl36825t_driver_api);
DT_INST_FOREACH_STATUS_OKAY(VCNL36825T_DEFINE)