Handle interrupts correctly for multi instance, checking per device DT bindings instead of global Kconfig definitions. The old interrupt behaviour has been maintained, fixing DRDY to be on INT1 and Any Motion event on INT2. Signed-off-by: Armando Visconti <armando.visconti@st.com>
492 lines
12 KiB
C
492 lines
12 KiB
C
/*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT st_lis2dh
|
|
|
|
#include <sys/util.h>
|
|
#include <kernel.h>
|
|
#include <logging/log.h>
|
|
|
|
#define START_TRIG_INT1 0
|
|
#define START_TRIG_INT2 1
|
|
#define TRIGGED_INT1 4
|
|
#define TRIGGED_INT2 5
|
|
|
|
LOG_MODULE_DECLARE(lis2dh, CONFIG_SENSOR_LOG_LEVEL);
|
|
#include "lis2dh.h"
|
|
|
|
static inline void setup_int1(const struct device *dev,
|
|
bool enable)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
const struct lis2dh_config *cfg = dev->config;
|
|
|
|
gpio_pin_interrupt_configure(lis2dh->gpio_int1,
|
|
cfg->irq1_pin,
|
|
enable
|
|
? GPIO_INT_EDGE_TO_ACTIVE
|
|
: GPIO_INT_DISABLE);
|
|
}
|
|
|
|
static int lis2dh_trigger_drdy_set(const struct device *dev,
|
|
enum sensor_channel chan,
|
|
sensor_trigger_handler_t handler)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
int status;
|
|
|
|
setup_int1(dev, false);
|
|
|
|
/* cancel potentially pending trigger */
|
|
atomic_clear_bit(&lis2dh->trig_flags, TRIGGED_INT1);
|
|
|
|
status = lis2dh->hw_tf->update_reg(dev, LIS2DH_REG_CTRL3,
|
|
LIS2DH_EN_DRDY1_INT1, 0);
|
|
|
|
lis2dh->handler_drdy = handler;
|
|
if ((handler == NULL) || (status < 0)) {
|
|
return status;
|
|
}
|
|
|
|
lis2dh->chan_drdy = chan;
|
|
|
|
/* serialize start of int1 in thread to synchronize output sampling
|
|
* and first interrupt. this avoids concurrent bus context access.
|
|
*/
|
|
atomic_set_bit(&lis2dh->trig_flags, START_TRIG_INT1);
|
|
#if defined(CONFIG_LIS2DH_TRIGGER_OWN_THREAD)
|
|
k_sem_give(&lis2dh->gpio_sem);
|
|
#elif defined(CONFIG_LIS2DH_TRIGGER_GLOBAL_THREAD)
|
|
k_work_submit(&lis2dh->work);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lis2dh_start_trigger_int1(const struct device *dev)
|
|
{
|
|
int status;
|
|
uint8_t raw[LIS2DH_BUF_SZ];
|
|
uint8_t ctrl1 = 0U;
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
|
|
/* power down temporarily to align interrupt & data output sampling */
|
|
status = lis2dh->hw_tf->read_reg(dev, LIS2DH_REG_CTRL1, &ctrl1);
|
|
if (unlikely(status < 0)) {
|
|
return status;
|
|
}
|
|
status = lis2dh->hw_tf->write_reg(dev, LIS2DH_REG_CTRL1,
|
|
ctrl1 & ~LIS2DH_ODR_MASK);
|
|
|
|
if (unlikely(status < 0)) {
|
|
return status;
|
|
}
|
|
|
|
LOG_DBG("ctrl1=0x%x @tick=%u", ctrl1, k_cycle_get_32());
|
|
|
|
/* empty output data */
|
|
status = lis2dh->hw_tf->read_data(dev, LIS2DH_REG_STATUS,
|
|
raw, sizeof(raw));
|
|
if (unlikely(status < 0)) {
|
|
return status;
|
|
}
|
|
|
|
setup_int1(dev, true);
|
|
|
|
/* re-enable output sampling */
|
|
status = lis2dh->hw_tf->write_reg(dev, LIS2DH_REG_CTRL1, ctrl1);
|
|
if (unlikely(status < 0)) {
|
|
return status;
|
|
}
|
|
|
|
return lis2dh->hw_tf->update_reg(dev, LIS2DH_REG_CTRL3,
|
|
LIS2DH_EN_DRDY1_INT1,
|
|
LIS2DH_EN_DRDY1_INT1);
|
|
}
|
|
|
|
#define LIS2DH_ANYM_CFG (LIS2DH_INT_CFG_ZHIE_ZUPE | LIS2DH_INT_CFG_YHIE_YUPE |\
|
|
LIS2DH_INT_CFG_XHIE_XUPE)
|
|
|
|
static inline void setup_int2(const struct device *dev,
|
|
bool enable)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
const struct lis2dh_config *cfg = dev->config;
|
|
|
|
gpio_pin_interrupt_configure(lis2dh->gpio_int2,
|
|
cfg->irq2_pin,
|
|
enable
|
|
? GPIO_INT_EDGE_TO_ACTIVE
|
|
: GPIO_INT_DISABLE);
|
|
}
|
|
|
|
static int lis2dh_trigger_anym_set(const struct device *dev,
|
|
sensor_trigger_handler_t handler)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
int status;
|
|
uint8_t reg_val;
|
|
|
|
setup_int2(dev, false);
|
|
|
|
/* cancel potentially pending trigger */
|
|
atomic_clear_bit(&lis2dh->trig_flags, TRIGGED_INT2);
|
|
|
|
/* disable all interrupt 2 events */
|
|
status = lis2dh->hw_tf->write_reg(dev, LIS2DH_REG_INT2_CFG, 0);
|
|
|
|
/* make sure any pending interrupt is cleared */
|
|
status = lis2dh->hw_tf->read_reg(dev, LIS2DH_REG_INT2_SRC, ®_val);
|
|
|
|
lis2dh->handler_anymotion = handler;
|
|
if ((handler == NULL) || (status < 0)) {
|
|
return status;
|
|
}
|
|
|
|
/* serialize start of int2 in thread to synchronize output sampling
|
|
* and first interrupt. this avoids concurrent bus context access.
|
|
*/
|
|
atomic_set_bit(&lis2dh->trig_flags, START_TRIG_INT2);
|
|
#if defined(CONFIG_LIS2DH_TRIGGER_OWN_THREAD)
|
|
k_sem_give(&lis2dh->gpio_sem);
|
|
#elif defined(CONFIG_LIS2DH_TRIGGER_GLOBAL_THREAD)
|
|
k_work_submit(&lis2dh->work);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int lis2dh_start_trigger_int2(const struct device *dev)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
|
|
setup_int2(dev, true);
|
|
|
|
return lis2dh->hw_tf->write_reg(dev, LIS2DH_REG_INT2_CFG,
|
|
LIS2DH_ANYM_CFG);
|
|
}
|
|
|
|
int lis2dh_trigger_set(const struct device *dev,
|
|
const struct sensor_trigger *trig,
|
|
sensor_trigger_handler_t handler)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
|
|
if (trig->type == SENSOR_TRIG_DATA_READY &&
|
|
trig->chan == SENSOR_CHAN_ACCEL_XYZ) {
|
|
/* If irq_gpio is not configured in DT just return error */
|
|
if (!lis2dh->gpio_int1) {
|
|
LOG_ERR("DRDY (INT1) trigger not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return lis2dh_trigger_drdy_set(dev, trig->chan, handler);
|
|
} else if (trig->type == SENSOR_TRIG_DELTA) {
|
|
/* If irq_gpio is not configured in DT just return error */
|
|
if (!lis2dh->gpio_int2) {
|
|
LOG_ERR("AnyMotion (INT2) trigger not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
return lis2dh_trigger_anym_set(dev, handler);
|
|
}
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
int lis2dh_acc_slope_config(const struct device *dev,
|
|
enum sensor_attribute attr,
|
|
const struct sensor_value *val)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
int status;
|
|
|
|
if (attr == SENSOR_ATTR_SLOPE_TH) {
|
|
uint8_t range_g, reg_val;
|
|
uint32_t slope_th_ums2;
|
|
|
|
status = lis2dh->hw_tf->read_reg(dev, LIS2DH_REG_CTRL4,
|
|
®_val);
|
|
if (status < 0) {
|
|
return status;
|
|
}
|
|
|
|
/* fs reg value is in the range 0 (2g) - 3 (16g) */
|
|
range_g = 2 * (1 << ((LIS2DH_FS_MASK & reg_val)
|
|
>> LIS2DH_FS_SHIFT));
|
|
|
|
slope_th_ums2 = val->val1 * 1000000 + val->val2;
|
|
|
|
/* make sure the provided threshold does not exceed range */
|
|
if ((slope_th_ums2 - 1) > (range_g * SENSOR_G)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 7 bit full range value */
|
|
reg_val = 128 / range_g * (slope_th_ums2 - 1) / SENSOR_G;
|
|
|
|
LOG_INF("int2_ths=0x%x range_g=%d ums2=%u", reg_val,
|
|
range_g, slope_th_ums2 - 1);
|
|
|
|
status = lis2dh->hw_tf->write_reg(dev, LIS2DH_REG_INT2_THS,
|
|
reg_val);
|
|
} else { /* SENSOR_ATTR_SLOPE_DUR */
|
|
/*
|
|
* slope duration is measured in number of samples:
|
|
* N/ODR where N is the register value
|
|
*/
|
|
if (val->val1 < 0 || val->val1 > 127) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
LOG_INF("int2_dur=0x%x", val->val1);
|
|
|
|
status = lis2dh->hw_tf->write_reg(dev, LIS2DH_REG_INT2_DUR,
|
|
val->val1);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static void lis2dh_gpio_int1_callback(const struct device *dev,
|
|
struct gpio_callback *cb, uint32_t pins)
|
|
{
|
|
struct lis2dh_data *lis2dh =
|
|
CONTAINER_OF(cb, struct lis2dh_data, gpio_int1_cb);
|
|
|
|
ARG_UNUSED(pins);
|
|
|
|
atomic_set_bit(&lis2dh->trig_flags, TRIGGED_INT1);
|
|
|
|
#if defined(CONFIG_LIS2DH_TRIGGER_OWN_THREAD)
|
|
k_sem_give(&lis2dh->gpio_sem);
|
|
#elif defined(CONFIG_LIS2DH_TRIGGER_GLOBAL_THREAD)
|
|
k_work_submit(&lis2dh->work);
|
|
#endif
|
|
}
|
|
|
|
static void lis2dh_gpio_int2_callback(const struct device *dev,
|
|
struct gpio_callback *cb, uint32_t pins)
|
|
{
|
|
struct lis2dh_data *lis2dh =
|
|
CONTAINER_OF(cb, struct lis2dh_data, gpio_int2_cb);
|
|
|
|
ARG_UNUSED(pins);
|
|
|
|
atomic_set_bit(&lis2dh->trig_flags, TRIGGED_INT2);
|
|
|
|
#if defined(CONFIG_LIS2DH_TRIGGER_OWN_THREAD)
|
|
k_sem_give(&lis2dh->gpio_sem);
|
|
#elif defined(CONFIG_LIS2DH_TRIGGER_GLOBAL_THREAD)
|
|
k_work_submit(&lis2dh->work);
|
|
#endif
|
|
}
|
|
|
|
static void lis2dh_thread_cb(const struct device *dev)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
int status;
|
|
|
|
if (lis2dh->gpio_int1 &&
|
|
unlikely(atomic_test_and_clear_bit(&lis2dh->trig_flags,
|
|
START_TRIG_INT1))) {
|
|
status = lis2dh_start_trigger_int1(dev);
|
|
|
|
if (unlikely(status < 0)) {
|
|
LOG_ERR("lis2dh_start_trigger_int1: %d", status);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (lis2dh->gpio_int2 &&
|
|
unlikely(atomic_test_and_clear_bit(&lis2dh->trig_flags,
|
|
START_TRIG_INT2))) {
|
|
status = lis2dh_start_trigger_int2(dev);
|
|
|
|
if (unlikely(status < 0)) {
|
|
LOG_ERR("lis2dh_start_trigger_int2: %d", status);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (lis2dh->gpio_int1 &&
|
|
atomic_test_and_clear_bit(&lis2dh->trig_flags,
|
|
TRIGGED_INT1)) {
|
|
struct sensor_trigger drdy_trigger = {
|
|
.type = SENSOR_TRIG_DATA_READY,
|
|
.chan = lis2dh->chan_drdy,
|
|
};
|
|
|
|
if (likely(lis2dh->handler_drdy != NULL)) {
|
|
lis2dh->handler_drdy(dev, &drdy_trigger);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (lis2dh->gpio_int2 &&
|
|
atomic_test_and_clear_bit(&lis2dh->trig_flags,
|
|
TRIGGED_INT2)) {
|
|
struct sensor_trigger anym_trigger = {
|
|
.type = SENSOR_TRIG_DELTA,
|
|
.chan = lis2dh->chan_drdy,
|
|
};
|
|
uint8_t reg_val;
|
|
|
|
/* clear interrupt 2 to de-assert int2 line */
|
|
status = lis2dh->hw_tf->read_reg(dev, LIS2DH_REG_INT2_SRC,
|
|
®_val);
|
|
if (status < 0) {
|
|
LOG_ERR("clearing interrupt 2 failed: %d", status);
|
|
return;
|
|
}
|
|
|
|
if (likely(lis2dh->handler_anymotion != NULL)) {
|
|
lis2dh->handler_anymotion(dev, &anym_trigger);
|
|
}
|
|
|
|
LOG_DBG("@tick=%u int2_src=0x%x", k_cycle_get_32(),
|
|
reg_val);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_LIS2DH_TRIGGER_OWN_THREAD
|
|
static void lis2dh_thread(struct lis2dh_data *lis2dh)
|
|
{
|
|
while (1) {
|
|
k_sem_take(&lis2dh->gpio_sem, K_FOREVER);
|
|
lis2dh_thread_cb(lis2dh->dev);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_LIS2DH_TRIGGER_GLOBAL_THREAD
|
|
static void lis2dh_work_cb(struct k_work *work)
|
|
{
|
|
struct lis2dh_data *lis2dh =
|
|
CONTAINER_OF(work, struct lis2dh_data, work);
|
|
|
|
lis2dh_thread_cb(lis2dh->dev);
|
|
}
|
|
#endif
|
|
|
|
int lis2dh_init_interrupt(const struct device *dev)
|
|
{
|
|
struct lis2dh_data *lis2dh = dev->data;
|
|
const struct lis2dh_config *cfg = dev->config;
|
|
int status;
|
|
uint8_t raw[2];
|
|
|
|
/*
|
|
* Setup INT1 (for DRDY) if defined in DT
|
|
*/
|
|
|
|
/* setup data ready gpio interrupt */
|
|
lis2dh->gpio_int1 = device_get_binding(cfg->irq1_dev_name);
|
|
if (lis2dh->gpio_int1 == NULL) {
|
|
LOG_INF("Cannot get pointer to irq1_dev_name");
|
|
status = 0;
|
|
goto end;
|
|
}
|
|
|
|
lis2dh->dev = dev;
|
|
|
|
#if defined(CONFIG_LIS2DH_TRIGGER_OWN_THREAD)
|
|
k_sem_init(&lis2dh->gpio_sem, 0, UINT_MAX);
|
|
|
|
k_thread_create(&lis2dh->thread, lis2dh->thread_stack,
|
|
CONFIG_LIS2DH_THREAD_STACK_SIZE,
|
|
(k_thread_entry_t)lis2dh_thread, lis2dh, NULL, NULL,
|
|
K_PRIO_COOP(CONFIG_LIS2DH_THREAD_PRIORITY), 0,
|
|
K_NO_WAIT);
|
|
#elif defined(CONFIG_LIS2DH_TRIGGER_GLOBAL_THREAD)
|
|
lis2dh->work.handler = lis2dh_work_cb;
|
|
#endif
|
|
|
|
/* data ready int1 gpio configuration */
|
|
status = gpio_pin_configure(lis2dh->gpio_int1, cfg->irq1_pin,
|
|
GPIO_INPUT | cfg->irq1_flags);
|
|
if (status < 0) {
|
|
LOG_ERR("Could not configure gpio %d", cfg->irq1_pin);
|
|
return status;
|
|
}
|
|
|
|
gpio_init_callback(&lis2dh->gpio_int1_cb,
|
|
lis2dh_gpio_int1_callback,
|
|
BIT(cfg->irq1_pin));
|
|
|
|
status = gpio_add_callback(lis2dh->gpio_int1, &lis2dh->gpio_int1_cb);
|
|
if (status < 0) {
|
|
LOG_ERR("Could not add gpio int1 callback");
|
|
return status;
|
|
}
|
|
|
|
LOG_INF("int1 on %s.%02u", cfg->irq1_dev_name, cfg->irq1_pin);
|
|
|
|
/*
|
|
* Setup INT2 (for Any Motion) if defined in DT
|
|
*/
|
|
/* setup any motion gpio interrupt */
|
|
lis2dh->gpio_int2 = device_get_binding(cfg->irq2_dev_name);
|
|
if (lis2dh->gpio_int2 == NULL) {
|
|
LOG_INF("Cannot get pointer to irq2_dev_name");
|
|
status = 0;
|
|
goto end;
|
|
}
|
|
|
|
/* any motion int2 gpio configuration */
|
|
status = gpio_pin_configure(lis2dh->gpio_int2, cfg->irq2_pin,
|
|
GPIO_INPUT | cfg->irq2_flags);
|
|
if (status < 0) {
|
|
LOG_ERR("Could not configure gpio %d", cfg->irq2_pin);
|
|
return status;
|
|
}
|
|
|
|
gpio_init_callback(&lis2dh->gpio_int2_cb,
|
|
lis2dh_gpio_int2_callback,
|
|
BIT(cfg->irq2_pin));
|
|
|
|
/* callback is going to be enabled by trigger setting function */
|
|
status = gpio_add_callback(lis2dh->gpio_int2, &lis2dh->gpio_int2_cb);
|
|
if (status < 0) {
|
|
LOG_ERR("Could not add gpio int2 callback (%d)", status);
|
|
return status;
|
|
}
|
|
|
|
LOG_INF("int2 on %s.%02u", cfg->irq2_dev_name, cfg->irq2_pin);
|
|
|
|
/* disable interrupt 2 in case of warm (re)boot */
|
|
status = lis2dh->hw_tf->write_reg(dev, LIS2DH_REG_INT2_CFG, 0);
|
|
if (status < 0) {
|
|
LOG_ERR("Interrupt 2 disable reg write failed (%d)", status);
|
|
return status;
|
|
}
|
|
|
|
(void)memset(raw, 0, sizeof(raw));
|
|
status = lis2dh->hw_tf->write_data(dev, LIS2DH_REG_INT2_THS,
|
|
raw, sizeof(raw));
|
|
if (status < 0) {
|
|
LOG_ERR("Burst write to INT2 THS failed (%d)", status);
|
|
return status;
|
|
}
|
|
|
|
/* enable interrupt 2 on int2 line */
|
|
status = lis2dh->hw_tf->update_reg(dev, LIS2DH_REG_CTRL6,
|
|
LIS2DH_EN_INT2_INT2,
|
|
LIS2DH_EN_INT2_INT2);
|
|
|
|
/* latch int2 line interrupt */
|
|
status = lis2dh->hw_tf->write_reg(dev, LIS2DH_REG_CTRL5,
|
|
LIS2DH_EN_LIR_INT2);
|
|
if (status < 0) {
|
|
LOG_ERR("INT2 latch enable reg write failed (%d)", status);
|
|
return status;
|
|
}
|
|
|
|
end:
|
|
return status;
|
|
}
|