blob: 3c075b2a0e881d583908017233acdf17f15a834f [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT st_lis2dh
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <zephyr/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 const gpio_flags_t gpio_int_cfg[5] = {
GPIO_INT_EDGE,
GPIO_INT_EDGE_RISING,
GPIO_INT_EDGE_FALLING,
GPIO_INT_LEVEL_HIGH,
GPIO_INT_LEVEL_LOW,
};
static inline void setup_int1(const struct device *dev,
bool enable)
{
const struct lis2dh_config *cfg = dev->config;
gpio_pin_interrupt_configure_dt(&cfg->gpio_drdy,
enable
? gpio_int_cfg[cfg->int1_mode]
: GPIO_INT_DISABLE);
}
static int lis2dh_trigger_drdy_set(const struct device *dev,
enum sensor_channel chan,
sensor_trigger_handler_t handler,
const struct sensor_trigger *trig)
{
const struct lis2dh_config *cfg = dev->config;
struct lis2dh_data *lis2dh = dev->data;
int status;
if (cfg->gpio_drdy.port == NULL) {
LOG_ERR("trigger_set DRDY int not supported");
return -ENOTSUP;
}
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;
lis2dh->trig_drdy = trig;
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)
{
const struct lis2dh_config *cfg = dev->config;
gpio_pin_interrupt_configure_dt(&cfg->gpio_int,
enable
? gpio_int_cfg[cfg->int2_mode]
: GPIO_INT_DISABLE);
}
/* common handler for any motion and tap triggers */
static int lis2dh_trigger_anym_tap_set(const struct device *dev,
sensor_trigger_handler_t handler,
const struct sensor_trigger *trig)
{
const struct lis2dh_config *cfg = dev->config;
struct lis2dh_data *lis2dh = dev->data;
int status;
uint8_t reg_val;
if (cfg->gpio_int.port == NULL) {
LOG_ERR("trigger_set AnyMotion int not supported");
return -ENOTSUP;
}
setup_int2(dev, false);
/* cancel potentially pending trigger */
atomic_clear_bit(&lis2dh->trig_flags, TRIGGED_INT2);
if (cfg->hw.anym_on_int1) {
status = lis2dh->hw_tf->update_reg(dev, LIS2DH_REG_CTRL3,
LIS2DH_EN_DRDY1_INT1, 0);
}
/* disable any movement interrupt events */
status = lis2dh->hw_tf->write_reg(dev,
cfg->hw.anym_on_int1 ?
LIS2DH_REG_INT1_CFG :
LIS2DH_REG_INT2_CFG,
0);
/* disable any click interrupt events */
status = lis2dh->hw_tf->write_reg(dev,
LIS2DH_REG_CFG_CLICK,
0);
/* make sure any pending interrupt is cleared */
status = lis2dh->hw_tf->read_reg(dev,
cfg->hw.anym_on_int1 ?
LIS2DH_REG_INT1_SRC :
LIS2DH_REG_INT2_SRC,
&reg_val);
status = lis2dh->hw_tf->read_reg(dev,
LIS2DH_REG_CLICK_SRC,
&reg_val);
if (trig->type == SENSOR_TRIG_DELTA) {
lis2dh->handler_anymotion = handler;
lis2dh->trig_anymotion = trig;
} else if (trig->type == SENSOR_TRIG_TAP) {
lis2dh->handler_tap = handler;
lis2dh->trig_tap = trig;
}
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_trigger_anym_set(const struct device *dev,
sensor_trigger_handler_t handler,
const struct sensor_trigger *trig)
{
return lis2dh_trigger_anym_tap_set(dev, handler, trig);
}
static int lis2dh_trigger_tap_set(const struct device *dev,
sensor_trigger_handler_t handler,
const struct sensor_trigger *trig)
{
return lis2dh_trigger_anym_tap_set(dev, handler, trig);
}
static int lis2dh_start_trigger_int2(const struct device *dev)
{
struct lis2dh_data *lis2dh = dev->data;
const struct lis2dh_config *cfg = dev->config;
int status = 0;
uint8_t reg = 0, mask = 0, val = 0;
setup_int2(dev, true);
bool has_anyt = (lis2dh->handler_tap != NULL);
bool has_anym = (lis2dh->handler_anymotion != NULL);
/* configure any motion interrupt */
reg = cfg->hw.anym_on_int1 ? LIS2DH_REG_INT1_CFG : LIS2DH_REG_INT2_CFG;
val = (cfg->hw.anym_mode << LIS2DH_INT_CFG_MODE_SHIFT) | LIS2DH_ANYM_CFG;
status = lis2dh->hw_tf->write_reg(dev, reg, val);
if (status < 0) {
LOG_ERR("Failed to configure any motion interrupt");
return status;
}
/* enable any motion detection on int line */
reg = cfg->hw.anym_on_int1 ? LIS2DH_REG_CTRL3 : LIS2DH_REG_CTRL6;
mask = cfg->hw.anym_on_int1 ? LIS2DH_EN_IA_INT1 : LIS2DH_EN_IA_INT2;
val = has_anym ? mask : 0;
status = lis2dh->hw_tf->update_reg(dev, reg, mask, val);
if (status < 0) {
LOG_ERR("Failed to enable any motion detection on int line");
return status;
}
/* configure tap interrupt on all axes */
reg = LIS2DH_REG_CFG_CLICK;
mask = LIS2DH_EN_CLICK_XS | LIS2DH_EN_CLICK_YS | LIS2DH_EN_CLICK_ZS;
val = has_anyt ? mask : 0;
status = lis2dh->hw_tf->update_reg(dev, reg, mask, val);
if (status < 0) {
LOG_ERR("Failed to configure tap interrupt");
return status;
}
/* set click detection on int line */
reg = cfg->hw.anym_on_int1 ? LIS2DH_REG_CTRL3 : LIS2DH_REG_CTRL6;
mask = cfg->hw.anym_on_int1 ? LIS2DH_EN_CLICK_INT1 : LIS2DH_EN_CLICK_INT2;
val = has_anyt ? mask : 0;
status = lis2dh->hw_tf->update_reg(dev, reg, mask, val);
if (status < 0) {
LOG_ERR("Failed to enable click detection on int line");
return status;
}
return 0;
}
int lis2dh_trigger_set(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
if (trig->type == SENSOR_TRIG_DATA_READY &&
trig->chan == SENSOR_CHAN_ACCEL_XYZ) {
return lis2dh_trigger_drdy_set(dev, trig->chan, handler, trig);
} else if (trig->type == SENSOR_TRIG_DELTA) {
return lis2dh_trigger_anym_set(dev, handler, trig);
} else if (trig->type == SENSOR_TRIG_TAP) {
return lis2dh_trigger_tap_set(dev, handler, trig);
}
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;
const struct lis2dh_config *cfg = dev->config;
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,
&reg_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);
/* Configure threshold for the any motion recognition */
status = lis2dh->hw_tf->write_reg(dev,
cfg->hw.anym_on_int1 ?
LIS2DH_REG_INT1_THS :
LIS2DH_REG_INT2_THS,
reg_val);
/* Configure threshold for the Click recognition */
status = lis2dh->hw_tf->write_reg(dev,
LIS2DH_REG_CFG_CLICK_THS,
LIS2DH_CLICK_LIR | 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);
/* Configure time limit for the any motion recognition */
status = lis2dh->hw_tf->write_reg(dev,
cfg->hw.anym_on_int1 ?
LIS2DH_REG_INT1_DUR :
LIS2DH_REG_INT2_DUR,
val->val1);
/* Configure time limit for the Click recognition */
status = lis2dh->hw_tf->write_reg(dev,
LIS2DH_REG_TIME_LIMIT,
val->val1);
}
return status;
}
#ifdef CONFIG_LIS2DH_ACCEL_HP_FILTERS
int lis2dh_acc_hp_filter_set(const struct device *dev, int32_t val)
{
struct lis2dh_data *lis2dh = dev->data;
int status;
status = lis2dh->hw_tf->update_reg(dev, LIS2DH_REG_CTRL2,
LIS2DH_HPIS_EN_MASK, val);
if (status < 0) {
LOG_ERR("Failed to set high pass filters");
}
return status;
}
#endif
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);
/* int is level triggered so disable until processed */
setup_int1(lis2dh->dev, false);
#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);
/* int is level triggered so disable until processed */
setup_int2(lis2dh->dev, false);
#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;
const struct lis2dh_config *cfg = dev->config;
int status;
if (cfg->gpio_drdy.port &&
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 (cfg->gpio_int.port &&
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 (cfg->gpio_drdy.port &&
atomic_test_and_clear_bit(&lis2dh->trig_flags,
TRIGGED_INT1)) {
if (likely(lis2dh->handler_drdy != NULL)) {
lis2dh->handler_drdy(dev, lis2dh->trig_drdy);
}
/* Reactivate level triggered interrupt if handler did not
* disable itself
*/
if (likely(lis2dh->handler_drdy != NULL)) {
setup_int1(dev, true);
}
return;
}
if (cfg->gpio_int.port &&
atomic_test_and_clear_bit(&lis2dh->trig_flags,
TRIGGED_INT2)) {
uint8_t reg_val = 0;
if (cfg->hw.anym_latch) {
/* clear interrupt to de-assert int line */
status = lis2dh->hw_tf->read_reg(dev,
cfg->hw.anym_on_int1 ?
LIS2DH_REG_INT1_SRC :
LIS2DH_REG_INT2_SRC,
&reg_val);
if (status < 0) {
LOG_ERR("clearing interrupt 2 failed: %d", status);
return;
}
}
if (likely(lis2dh->handler_anymotion != NULL) &&
(reg_val >> LIS2DH_INT_CFG_MODE_SHIFT)) {
lis2dh->handler_anymotion(dev, lis2dh->trig_anymotion);
LOG_DBG("@tick=%u int2_src=0x%x", k_cycle_get_32(), reg_val);
}
/* read click interrupt */
status = lis2dh->hw_tf->read_reg(dev, LIS2DH_REG_CLICK_SRC,
&reg_val);
if (status < 0) {
LOG_ERR("clearing interrupt 2 failed: %d", status);
return;
}
if (likely(lis2dh->handler_tap != NULL) &&
(reg_val & LIS2DH_CLICK_SRC_SCLICK)) {
lis2dh->handler_tap(dev, lis2dh->trig_tap);
LOG_DBG("@tick=%u click_src=0x%x", k_cycle_get_32(), reg_val);
}
/* Reactivate level triggered interrupt if handler did not
* disable itself
*/
if (lis2dh->handler_anymotion || lis2dh->handler_tap) {
setup_int2(dev, true);
}
return;
}
}
#ifdef CONFIG_LIS2DH_TRIGGER_OWN_THREAD
static void lis2dh_thread(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p2);
ARG_UNUSED(p3);
struct lis2dh_data *lis2dh = p1;
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];
lis2dh->dev = dev;
#if defined(CONFIG_LIS2DH_TRIGGER_OWN_THREAD)
k_sem_init(&lis2dh->gpio_sem, 0, K_SEM_MAX_LIMIT);
k_thread_create(&lis2dh->thread, lis2dh->thread_stack, CONFIG_LIS2DH_THREAD_STACK_SIZE,
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
/*
* Setup INT1 (for DRDY) if defined in DT
*/
/* setup data ready gpio interrupt */
if (!gpio_is_ready_dt(&cfg->gpio_drdy)) {
/* API may return false even when ptr is NULL */
if (cfg->gpio_drdy.port != NULL) {
LOG_ERR("device %s is not ready", cfg->gpio_drdy.port->name);
return -ENODEV;
}
LOG_DBG("gpio_drdy not defined in DT");
status = 0;
goto check_gpio_int;
}
/* data ready int1 gpio configuration */
status = gpio_pin_configure_dt(&cfg->gpio_drdy, GPIO_INPUT);
if (status < 0) {
LOG_ERR("Could not configure %s.%02u",
cfg->gpio_drdy.port->name, cfg->gpio_drdy.pin);
return status;
}
gpio_init_callback(&lis2dh->gpio_int1_cb,
lis2dh_gpio_int1_callback,
BIT(cfg->gpio_drdy.pin));
status = gpio_add_callback(cfg->gpio_drdy.port, &lis2dh->gpio_int1_cb);
if (status < 0) {
LOG_ERR("Could not add gpio int1 callback");
return status;
}
LOG_INF("%s: int1 on %s.%02u", dev->name,
cfg->gpio_drdy.port->name,
cfg->gpio_drdy.pin);
check_gpio_int:
/*
* Setup Interrupt (for Any Motion) if defined in DT
*/
/* setup any motion gpio interrupt */
if (!gpio_is_ready_dt(&cfg->gpio_int)) {
/* API may return false even when ptr is NULL */
if (cfg->gpio_int.port != NULL) {
LOG_ERR("device %s is not ready", cfg->gpio_int.port->name);
return -ENODEV;
}
LOG_DBG("gpio_int not defined in DT");
status = 0;
goto end;
}
/* any motion int2 gpio configuration */
status = gpio_pin_configure_dt(&cfg->gpio_int, GPIO_INPUT);
if (status < 0) {
LOG_ERR("Could not configure %s.%02u",
cfg->gpio_int.port->name, cfg->gpio_int.pin);
return status;
}
gpio_init_callback(&lis2dh->gpio_int2_cb,
lis2dh_gpio_int2_callback,
BIT(cfg->gpio_int.pin));
/* callback is going to be enabled by trigger setting function */
status = gpio_add_callback(cfg->gpio_int.port, &lis2dh->gpio_int2_cb);
if (status < 0) {
LOG_ERR("Could not add gpio int2 callback (%d)", status);
return status;
}
LOG_INF("%s: int2 on %s.%02u", dev->name,
cfg->gpio_int.port->name,
cfg->gpio_int.pin);
/* disable interrupt in case of warm (re)boot */
status = lis2dh->hw_tf->write_reg(dev,
cfg->hw.anym_on_int1 ?
LIS2DH_REG_INT1_CFG :
LIS2DH_REG_INT2_CFG,
0);
if (status < 0) {
LOG_ERR("Interrupt disable reg write failed (%d)", status);
return status;
}
status = lis2dh->hw_tf->write_reg(dev,
LIS2DH_REG_CFG_CLICK,
0);
if (status < 0) {
LOG_ERR("Interrupt disable reg write failed (%d)", status);
return status;
}
(void)memset(raw, 0, sizeof(raw));
status = lis2dh->hw_tf->write_data(dev,
cfg->hw.anym_on_int1 ?
LIS2DH_REG_INT1_THS :
LIS2DH_REG_INT2_THS,
raw, sizeof(raw));
if (status < 0) {
LOG_ERR("Burst write to THS failed (%d)", status);
return status;
}
if (cfg->hw.anym_latch) {
/* latch line interrupt */
status = lis2dh->hw_tf->write_reg(dev,
LIS2DH_REG_CTRL5,
cfg->hw.anym_on_int1 ?
LIS2DH_EN_LIR_INT1 :
LIS2DH_EN_LIR_INT2);
}
if (status < 0) {
LOG_ERR("enable reg write failed (%d)", status);
return status;
}
end:
return status;
}