vcnl36825t: add trigger capability

Adds trigger capability to the Vishay VCNL36825T sensor.

Signed-off-by: Juliane Schulze <juliane.schulze@deveritec.com>
This commit is contained in:
Juliane Schulze 2024-05-02 09:50:44 +02:00 committed by Henrik Brix Andersen
parent a61484f7ad
commit 7adcebc675
10 changed files with 513 additions and 9 deletions

View File

@ -4,3 +4,4 @@
zephyr_library()
zephyr_library_sources(vcnl36825t.c)
zephyr_library_sources_ifdef(CONFIG_VCNL36825T_TRIGGER vcnl36825t_trigger.c)

View File

@ -10,3 +10,49 @@ config VCNL36825T
select I2C
help
Enable driver for VCNL36825T sensors.
if VCNL36825T
config VCNL36825T_TRIGGER
bool
choice VCNL36825T_TRIGGER_CHOICE
prompt "trigger mode"
default VCNL36825T_TRIGGER_NONE
help
Specify the type of triggering to be used by the driver.
Note: Since figuring out which interrupt was triggered, using the Zephyr
standard types will deactivate the other interrupt.
config VCNL36825T_TRIGGER_NONE
bool "no trigger"
config VCNL36825T_TRIGGER_GLOBAL_THREAD
bool "use global thread"
select VCNL36825T_TRIGGER
config VCNL36825T_TRIGGER_OWN_THREAD
bool "use own thread"
select VCNL36825T_TRIGGER
endchoice
if VCNL36825T_TRIGGER
config VCNL36825T_THREAD_PRIORITY
int "thread priority"
depends on VCNL36825T_TRIGGER_OWN_THREAD
default 10
help
Priority of thread used by the driver to handle interrupts.
config VCNL36825T_THREAD_STACK_SIZE
int "thread stack size"
depends on VCNL36825T_TRIGGER_OWN_THREAD
default 1024
help
Stack size of thread used by the driver to handle interrupts.
endif # VCNL36825T_TRIGGER
endif # VCNL36825T

View File

@ -11,14 +11,16 @@
#include <stdlib.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.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)
int vcnl36825t_read(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t *value)
{
uint8_t rx_buf[2];
int rc;
@ -33,7 +35,7 @@ static int vcnl36825t_read(const struct i2c_dt_spec *spec, uint8_t reg_addr, uin
return 0;
}
static int vcnl36825t_write(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t value)
int vcnl36825t_write(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t value)
{
uint8_t tx_buf[3] = {reg_addr};
@ -41,8 +43,8 @@ static int vcnl36825t_write(const struct i2c_dt_spec *spec, uint8_t reg_addr, ui
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 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;
@ -210,6 +212,36 @@ static int vcnl36825t_channel_get(const struct device *dev, enum sensor_channel
return 0;
}
static int vcnl36825t_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
CHECKIF(dev == NULL) {
LOG_ERR("dev: NULL");
return -EINVAL;
}
CHECKIF(val == NULL) {
LOG_ERR("val: NULL");
return -EINVAL;
}
int __maybe_unused rc;
switch (attr) {
default:
#if CONFIG_VCNL36825T_TRIGGER
rc = vcnl36825t_trigger_attr_set(dev, chan, attr, val);
if (rc < 0) {
return rc;
}
#else
return -ENOTSUP;
#endif
}
return 0;
}
/**
* @brief calculate measurement timeout in us
*
@ -485,6 +517,12 @@ static int vcnl36825t_init(const struct device *dev)
return rc;
}
#if CONFIG_VCNL36825T_TRIGGER
rc = vcnl36825t_trigger_init(dev);
if (rc < 0) {
return rc;
}
#endif
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_ST_MSK,
VCNL36825T_PS_ST_START);
if (rc < 0) {
@ -498,6 +536,10 @@ static int vcnl36825t_init(const struct device *dev)
static const struct sensor_driver_api vcnl36825t_driver_api = {
.sample_fetch = vcnl36825t_sample_fetch,
.channel_get = vcnl36825t_channel_get,
.attr_set = vcnl36825t_attr_set,
#if CONFIG_VCNL36825T_TRIGGER
.trigger_set = vcnl36825t_trigger_set,
#endif
};
#define VCNL36825T_DEFINE(inst) \
@ -524,7 +566,11 @@ static const struct sensor_driver_api vcnl36825t_driver_api = {
.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_VCNL36825T_TRIGGER, \
(.int_gpio = GPIO_DT_SPEC_INST_GET(inst, int_gpios), \
.int_mode = DT_INST_ENUM_IDX(inst, int_mode), \
.int_proximity_count = DT_INST_PROP(inst, int_proximity_count), \
.int_smart_persistence = DT_INST_PROP(inst, int_smart_persistence)))}; \
IF_ENABLED(CONFIG_PM_DEVICE, (PM_DEVICE_DT_INST_DEFINE(inst, vcnl36825t_pm_action))); \
SENSOR_DEVICE_DT_INST_DEFINE( \
inst, vcnl36825t_init, \

View File

@ -56,7 +56,10 @@
#define VCNL36825T_PS_MPS_POS 12
#define VCNL36825T_PS_IT_POS 14
#define VCNL36825T_PS_ST_MSK GENMASK(0, 0)
#define VCNL36825T_PS_ST_MSK GENMASK(0, 0)
#define VCNL36825T_PS_SMART_PERS_MSK GENMASK(1, 1)
#define VCNL36825T_PS_INT_MSK GENMASK(3, 2)
#define VCNL36825T_PS_PERS_MSK GENMASK(5, 4)
#define VCNL36825T_PS_ST_START (0 << VCNL36825T_PS_ST_POS)
#define VCNL36825T_PS_ST_STOP (1 << VCNL36825T_PS_ST_POS)
@ -65,9 +68,9 @@
#define VCNL36825T_PS_SMART_PERS_ENABLED (1 << VCNL36825T_PS_PS_SMART_PERS_POS)
#define VCNL36825T_PS_INT_DISABLE (0 << VCNL36825T_PS_INT_POS)
#define VCNL36825T_PS_INT_THDH_PERS_LATCHED (1 << VCNL36825T_PS_INT_POS)
#define VCNL36825T_PS_INT_THDH_FIRST_LATCHED (2 << VCNL36825T_PS_INT_POS)
#define VCNL36825T_PS_INT_ENABLED (3 << VCNL36825T_PS_INT_POS)
#define VCNL36825T_PS_INT_MODE_LOGIC_HIGH_LOW (1 << VCNL36825T_PS_INT_POS)
#define VCNL36825T_PS_INT_MODE_FIRST_HIGH (2 << VCNL36825T_PS_INT_POS)
#define VCNL36825T_PS_INT_MODE_NORMAL (3 << VCNL36825T_PS_INT_POS)
#define VCNL36825T_PS_PERS_1 (0 << VCNL36825T_PS_PERS_POS)
#define VCNL36825T_PS_PERS_2 (1 << VCNL36825T_PS_PERS_POS)
@ -281,6 +284,12 @@ enum vcnl38625t_laser_current {
VCNL36825T_LASER_CURRENT_20MS,
};
enum vcnl36825t_int_mode {
VCNL36825T_INT_MODE_NORMAL,
VCNL36825T_INT_MODE_FIRST_HIGH,
VCNL36825T_INT_MODE_LOGIC_HIGH_LOW,
};
struct vcnl36825t_config {
struct i2c_dt_spec i2c;
@ -297,6 +306,13 @@ struct vcnl36825t_config {
enum vcnl38625t_laser_current laser_current;
bool high_dynamic_output;
bool sunlight_cancellation;
#if CONFIG_VCNL36825T_TRIGGER
struct gpio_dt_spec int_gpio;
enum vcnl36825t_int_mode int_mode;
uint8_t int_proximity_count;
bool int_smart_persistence;
#endif
};
struct vcnl36825t_data {
@ -308,6 +324,42 @@ struct vcnl36825t_data {
unsigned int meas_timeout_running_us;
unsigned int meas_timeout_wakeup_us;
#endif
#if CONFIG_VCNL36825T_TRIGGER
const struct device *dev;
const struct gpio_dt_spec *int_gpio;
const struct sensor_trigger *int_trigger;
sensor_trigger_handler_t int_handler;
struct gpio_callback int_gpio_handler;
#if CONFIG_VCNL36825T_TRIGGER_OWN_THREAD
K_KERNEL_STACK_MEMBER(int_thread_stack, CONFIG_VCNL36825T_THREAD_STACK_SIZE);
struct k_thread int_thread;
struct k_sem int_gpio_sem;
#elif CONFIG_VCNL36825T_TRIGGER_GLOBAL_THREAD
struct k_work int_work;
#endif
#endif
};
int vcnl36825t_read(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t *value);
int vcnl36825t_write(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t value);
int vcnl36825t_update(const struct i2c_dt_spec *spec, uint8_t reg_addr, uint16_t mask,
uint16_t value);
#if CONFIG_VCNL36825T_TRIGGER
int vcnl36825t_trigger_init(const struct device *dev);
int vcnl36825t_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
int vcnl36825t_trigger_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val);
#endif
#endif

View File

@ -0,0 +1,319 @@
/*
* Copyright (c) 2024 deveritec GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT vishay_vcnl36825t
#include "vcnl36825t.h"
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(VCNL36825T, CONFIG_SENSOR_LOG_LEVEL);
int vcnl36825t_trigger_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
CHECKIF(dev == NULL) {
return -EINVAL;
}
CHECKIF(val == NULL) {
return -EINVAL;
}
const struct vcnl36825t_config *config = dev->config;
int rc;
uint16_t reg_addr;
switch (attr) {
case SENSOR_ATTR_UPPER_THRESH:
reg_addr = VCNL36825T_REG_PS_THDH;
break;
case SENSOR_ATTR_LOWER_THRESH:
reg_addr = VCNL36825T_REG_PS_THDL;
break;
default:
LOG_ERR("unknown attribute %d", (int)attr);
return -ENOTSUP;
}
rc = vcnl36825t_write(&config->i2c, reg_addr, (uint16_t)val->val1);
if (rc < 0) {
LOG_ERR("error writing attribute %d", attr);
return rc;
}
return 0;
}
/**
* @brief callback called if a GPIO-interrupt is registered
*
* @param port device struct for the GPIO device
* @param cb @ref struct gpio_callback owning this handler
* @param pins mask of pins that triggered the callback handler
*/
static void vcnl36825t_gpio_callback(const struct device *port, struct gpio_callback *cb,
uint32_t pins)
{
ARG_UNUSED(port);
ARG_UNUSED(pins);
CHECKIF(!cb) {
LOG_ERR("cb: NULL");
return;
}
struct vcnl36825t_data *data = CONTAINER_OF(cb, struct vcnl36825t_data, int_gpio_handler);
int rc;
rc = gpio_pin_interrupt_configure_dt(data->int_gpio, GPIO_INT_DISABLE);
if (rc != 0) {
LOG_ERR("error deactivating SoC interrupt %d", rc);
}
#if CONFIG_VCNL36825T_TRIGGER_OWN_THREAD
k_sem_give(&data->int_gpio_sem);
#elif CONFIG_VCNL36825T_TRIGGER_GLOBAL_THREAD
k_work_submit(&data->int_work);
#endif
}
static void vcnl36825t_thread_cb(const struct device *dev)
{
const struct vcnl36825t_config *config = dev->config;
struct vcnl36825t_data *data = dev->data;
int rc;
uint16_t reg_value;
if (data->int_handler != NULL) {
data->int_handler(dev, data->int_trigger);
}
rc = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_FALLING);
if (rc < 0) {
LOG_ERR("error activating SoC interrupt %d", rc);
return;
}
rc = vcnl36825t_read(&config->i2c, VCNL36825T_REG_INT_FLAG, &reg_value);
if (rc < 0) {
LOG_ERR("error reading interrupt flag register %d", rc);
return;
}
if (FIELD_GET(VCNL36825T_PS_IF_AWAY_MSK, reg_value) == 1) {
LOG_INF("\"away\" trigger (PS below THDL)");
} else if (FIELD_GET(VCNL36825T_PS_IF_CLOSE_MSK, reg_value) == 1) {
LOG_INF("\"close\" trigger (PS above THDH)");
} else if (FIELD_GET(VCNL36825T_PS_SPFLAG_MSK, reg_value) == 1) {
LOG_INF("enter protection mode trigger");
} else if (FIELD_GET(VCNL36825T_PS_ACFLAG_MSK, reg_value) == 1) {
LOG_INF("finished auto calibration trigger");
}
}
#if CONFIG_VCNL36825T_TRIGGER_OWN_THREAD
static void vcnl36825t_thread_main(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p2);
ARG_UNUSED(p3);
struct vcnl36825t_data *data = p1;
while (1) {
k_sem_take(&data->int_gpio_sem, K_FOREVER);
vcnl36825t_thread_cb(data->dev);
}
}
#endif
#if CONFIG_VCNL36825T_TRIGGER_GLOBAL_THREAD
static void vcnl36825t_work_cb(struct k_work *work)
{
struct vcnl36825t_data *data = CONTAINER_OF(work, struct vcnl36825t_data, int_work);
vcnl36825t_thread_cb(data->dev);
}
#endif
int vcnl36825t_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
CHECKIF(dev == NULL) {
LOG_ERR("dev: NULL");
return -EINVAL;
}
CHECKIF(trig == NULL) {
LOG_ERR("trig: NULL");
return -EINVAL;
}
const struct vcnl36825t_config *config = dev->config;
struct vcnl36825t_data *data = dev->data;
int rc;
uint16_t regdata;
if (trig->chan != SENSOR_CHAN_PROX) {
LOG_ERR("invalid channel %d", (int)trig->chan);
return -ENOTSUP;
}
if (trig->type != SENSOR_TRIG_THRESHOLD) {
LOG_ERR("invalid trigger type %d", (int)trig->type);
return -ENOTSUP;
}
rc = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_DISABLE);
if (rc < 0) {
LOG_ERR("error configuring SoC interrupt %d", rc);
return rc;
}
data->int_trigger = trig;
data->int_handler = handler;
if (handler == NULL) {
regdata = VCNL36825T_PS_INT_DISABLE;
} else {
switch (config->int_mode) {
case VCNL36825T_INT_MODE_NORMAL:
regdata = VCNL36825T_PS_INT_MODE_NORMAL;
break;
case VCNL36825T_INT_MODE_FIRST_HIGH:
regdata = VCNL36825T_PS_INT_MODE_FIRST_HIGH;
break;
case VCNL36825T_INT_MODE_LOGIC_HIGH_LOW:
regdata = VCNL36825T_PS_INT_MODE_LOGIC_HIGH_LOW;
break;
default:
LOG_ERR("unknown interrupt mode");
return -ENOTSUP;
}
}
rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_INT_MSK,
regdata);
if (rc < 0) {
LOG_ERR("error updating interrupt %d", rc);
return rc;
}
if (handler) {
rc = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_FALLING);
if (rc < 0) {
LOG_ERR("error configuring SoC interrupt %d", rc);
return rc;
}
/* read interrupt register one time to clear any pending interrupt */
rc = vcnl36825t_read(&config->i2c, VCNL36825T_REG_INT_FLAG, &regdata);
if (rc < 0) {
LOG_ERR("error clearing interrupt flag register %d", rc);
return rc;
}
}
return 0;
}
int vcnl36825t_trigger_init(const struct device *dev)
{
CHECKIF(dev == NULL) {
LOG_ERR("dev: NULL");
return -EINVAL;
}
const struct vcnl36825t_config *config = dev->config;
struct vcnl36825t_data *data = dev->data;
int rc;
uint8_t reg_value;
if (!gpio_is_ready_dt(&config->int_gpio)) {
LOG_ERR("interrupt GPIO not ready");
return -ENODEV;
}
data->dev = dev;
data->int_gpio = &config->int_gpio;
rc = gpio_pin_configure_dt(data->int_gpio, GPIO_INPUT);
if (rc < 0) {
LOG_ERR("error setting interrupt gpio configuration %d", rc);
return rc;
}
/* PS_CONF2 */
reg_value = VCNL36825T_PS_INT_DISABLE;
if (config->int_smart_persistence) {
reg_value |= VCNL36825T_PS_SMART_PERS_ENABLED;
}
switch (config->int_proximity_count) {
case 1:
reg_value |= VCNL36825T_PS_PERS_1;
break;
case 2:
reg_value |= VCNL36825T_PS_PERS_2;
break;
case 3:
reg_value |= VCNL36825T_PS_PERS_3;
break;
case 4:
__fallthrough;
default:
reg_value |= VCNL36825T_PS_PERS_4;
}
rc = vcnl36825t_update(
&config->i2c, VCNL36825T_REG_PS_CONF2,
(VCNL36825T_PS_SMART_PERS_MSK | VCNL36825T_PS_INT_MSK | VCNL36825T_PS_PERS_MSK),
reg_value);
if (rc < 0) {
LOG_ERR("could not write interrupt configuration %d", rc);
return rc;
}
#if CONFIG_VCNL36825T_TRIGGER_OWN_THREAD
k_sem_init(&data->int_gpio_sem, 0, K_SEM_MAX_LIMIT);
k_thread_create(&data->int_thread, data->int_thread_stack,
CONFIG_VCNL36825T_THREAD_STACK_SIZE, vcnl36825t_thread_main, data, NULL,
NULL, K_PRIO_COOP(CONFIG_VCNL36825T_THREAD_PRIORITY), 0, K_NO_WAIT);
#elif CONFIG_VCNL36825T_TRIGGER_GLOBAL_THREAD
k_work_init(&data->int_work, vcnl36825t_work_cb);
#else
#error "invalid interrupt threading configuration"
#endif
gpio_init_callback(&data->int_gpio_handler, vcnl36825t_gpio_callback,
BIT(config->int_gpio.pin));
rc = gpio_add_callback(config->int_gpio.port, &data->int_gpio_handler);
if (rc < 0) {
LOG_ERR("could not set gpio callback %d", rc);
return rc;
}
rc = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_DISABLE);
if (rc < 0) {
LOG_ERR("could not set SoC interrupt configuration %d", rc);
return rc;
}
return 0;
}

View File

@ -90,3 +90,38 @@ properties:
description: |
Activate 16bit high dynamic output mode.
Cannot be used with threshold interrupt.
int-gpios:
type: phandle-array
description: |
The INT signal connection.
The signal is active-low as produced by the sensor.
int-mode:
type: string
default: "normal"
enum: ["normal", "first high", "logic high / low"]
description: |
Specifies the interrupt behavior.
- "normal": signal if exceeds high or falls below lower threshold
and proximity count is reached.
- "first high": signal if exceeds high threshold first time and signal again
if falls below lower threshold, and proximity count is reached.
Do not trigger if high threshold was never exceeded.
- "logic high / low": signal if high threshold is exceeded and proximity counts is reached,
deactivate if falls below lower threshold.
Defaults to "normal" as this is the easiest configurable mode.
int-proximity-count:
type: int
default: 1
enum: [1, 2, 3, 4]
description: |
Number of consecutive measurements above/below threshold to signal an interrupt.
Defaults to sensor reset value
int-smart-persistence:
type: boolean
description: |
Activates "smart persistence" feature, aimed to reduce total reaction time
until an interrupt is issued.

View File

@ -882,6 +882,8 @@ test_i2c_vishay_vcnl36825t: vcnl36825t@81 {
multi-pulse = <8>;
low-power;
int-gpios = <&test_gpio 0 0>;
};
test_i2c_tmag5273: tmag5273@82 {

View File

@ -63,4 +63,5 @@ CONFIG_TMD2620_TRIGGER_GLOBAL_THREAD=y
CONFIG_TMP007_TRIGGER_GLOBAL_THREAD=y
CONFIG_TSL2540_TRIGGER_GLOBAL_THREAD=y
CONFIG_TSL2591_TRIGGER_GLOBAL_THREAD=y
CONFIG_VCNL36825T_TRIGGER_GLOBAL_THREAD=y
CONFIG_VCNL4040_TRIGGER_GLOBAL_THREAD=y

View File

@ -64,4 +64,5 @@ CONFIG_TMD2620_TRIGGER_NONE=y
CONFIG_TMP007_TRIGGER_NONE=y
CONFIG_TSL2540_TRIGGER_NONE=y
CONFIG_TSL2591_TRIGGER_NONE=y
CONFIG_VCNL36825T_TRIGGER_NONE=y
CONFIG_VCNL4040_TRIGGER_NONE=y

View File

@ -60,4 +60,5 @@ CONFIG_TMAG5170_TRIGGER_OWN_THREAD=y
CONFIG_TMP007_TRIGGER_OWN_THREAD=y
CONFIG_TSL2540_TRIGGER_OWN_THREAD=y
CONFIG_TSL2591_TRIGGER_OWN_THREAD=y
CONFIG_VCNL36825T_TRIGGER_OWN_THREAD=y
CONFIG_VCNL4040_TRIGGER_OWN_THREAD=y