sensor: lis2dh: Add support for lis2dh accelerometer

* lis2dh 3 axis accelerometer support on SPI and I2C bus
 * data ready and anymotion type of trigger support

 Tested on Dynastream module D52QD2M4IA-A using SPI

Origin: based on Zephyr lis3dh driver

Change-Id: I2c4e9418b87f09c957bba8f73522bd78830bc809
Signed-off-by: Roger Lendenmann <roger.lendenmann@intel.com>
This commit is contained in:
Roger Lendenmann 2017-05-04 13:14:23 +02:00 committed by Anas Nashif
commit 0bc4a88bc7
7 changed files with 1366 additions and 0 deletions

View file

@ -70,6 +70,8 @@ source "drivers/sensor/hts221/Kconfig"
source "drivers/sensor/isl29035/Kconfig"
source "drivers/sensor/lis2dh/Kconfig"
source "drivers/sensor/lis3dh/Kconfig"
source "drivers/sensor/lis3mdl/Kconfig"

View file

@ -15,6 +15,7 @@ obj-$(CONFIG_HMC5883L) += hmc5883l/
obj-$(CONFIG_HP206C) += hp206c/
obj-$(CONFIG_HTS221) += hts221/
obj-$(CONFIG_ISL29035) += isl29035/
obj-$(CONFIG_LIS2DH) += lis2dh/
obj-$(CONFIG_LIS3DH) += lis3dh/
obj-$(CONFIG_LIS3MDL) += lis3mdl/
obj-$(CONFIG_LPS25HB) += lps25hb/

View file

@ -0,0 +1,241 @@
# Kconfig - LIS2DH Three Axis Accelerometer configuration options
#
# Copyright (c) 2017 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
menuconfig LIS2DH
bool
prompt "LIS2DH Three Axis Accelerometer"
depends on SENSOR && (I2C || SPI)
default n
help
Enable driver for LIS2DH SPI/I2C-based triaxial accelerometer sensor.
config LIS2DH_NAME
string "Driver name"
default "LIS2DH"
depends on LIS2DH
help
Device name with which the LIS2DH sensor is identified.
choice
prompt "Sensor Bus Type"
depends on LIS2DH
default LIS2DH_BUS_SPI
help
Specify bus type, SPI versus I2C, to which the sensor is attached.
config LIS2DH_BUS_SPI
bool "SPI bus"
depends on SPI
help
Use a SPI master to communicate with accelerometer (SPI slave)
config LIS2DH_BUS_I2C
bool "I2C bus"
depends on I2C
help
Use an I2C master to communicate with accelerometer (I2C slave)
endchoice
config LIS2DH_SPI_MASTER_DEV_NAME
string "SPI master device name"
depends on LIS2DH && LIS2DH_BUS_SPI
default "SPI_0"
help
Specify the device name of the SPI master device to which LIS2DH is
connected.
config LIS2DH_SPI_SS_1
int "LIS2DH SPI slave 1 select number"
depends on LIS2DH && LIS2DH_BUS_SPI
range 1 4
default 1
help
SPI slave select line to use to talk to LIS2DH sensor.
config LIS2DH_SPI_FREQUENCY
int "SPI clock frequency"
depends on LIS2DH && LIS2DH_BUS_SPI
range 125000 10000000
default 4000000
help
SPI clock frequency to use. Make sure the SPI master hardware supports
this frequency.
config LIS2DH_I2C_ADDR
hex "LIS2DH I2C address"
depends on LIS2DH && LIS2DH_BUS_I2C
default 0x18
help
I2C address of the LIS2DH sensor.
0x18: Choose this option if the SDO pin is pulled to GND.
0x19: Choose this option if the SDO pin is pulled to VDDIO.
config LIS2DH_I2C_MASTER_DEV_NAME
string
prompt "I2C master where LIS2DH is connected"
depends on LIS2DH && LIS2DH_BUS_I2C
default "I2C_0"
help
Specify the device name of the I2C master device to which LIS2DH is
connected.
choice
prompt "Trigger mode"
depends on LIS2DH
default LIS2DH_TRIGGER_GLOBAL_THREAD
help
Specify the type of triggering to be used by the driver.
config LIS2DH_TRIGGER_NONE
bool "No trigger"
config LIS2DH_TRIGGER_GLOBAL_THREAD
bool "Use global thread"
depends on GPIO
select LIS2DH_TRIGGER
config LIS2DH_TRIGGER_OWN_THREAD
bool "Use own thread"
depends on GPIO
select LIS2DH_TRIGGER
endchoice
config LIS2DH_TRIGGER
bool
depends on LIS2DH
config LIS2DH_GPIO_DEV_NAME
string "GPIO device"
default "GPIO_0"
depends on LIS2DH && LIS2DH_TRIGGER
help
The device name of the GPIO device to which the LIS2DH interrupt pins
are connected.
config LIS2DH_INT1_GPIO_PIN
int "Interrupt 1 GPIO pin number"
range 0 254
default 25
depends on LIS2DH && LIS2DH_TRIGGER
help
The number of the GPIO on which the interrupt 1 signal from the LIS2DH
chip will be received.
config LIS2DH_INT2_GPIO_PIN
int "Interrupt 2 GPIO pin number"
range 0 254
default 26
depends on LIS2DH && LIS2DH_TRIGGER
help
The number of the GPIO on which the interrupt 2 signal from the LIS2DH
chip will be received.
config LIS2DH_THREAD_PRIORITY
int "Thread priority"
depends on LIS2DH && LIS2DH_TRIGGER_OWN_THREAD
default 10
help
Priority of thread used by the driver to handle interrupts.
config LIS2DH_THREAD_STACK_SIZE
int "Thread stack size"
depends on LIS2DH && LIS2DH_TRIGGER_OWN_THREAD
default 1024
help
Stack size of thread used by the driver to handle interrupts.
choice
prompt "Acceleration measurement range"
depends on LIS2DH
default LIS2DH_ACCEL_RANGE_RUNTIME
help
Initial measurement full scale range for acceleration values.
config LIS2DH_ACCEL_RANGE_RUNTIME
bool "Set at runtime"
config LIS2DH_ACCEL_RANGE_2G
bool "+/-2g"
config LIS2DH_ACCEL_RANGE_4G
bool "+/-4g"
config LIS2DH_ACCEL_RANGE_8G
bool "+/-8g"
config LIS2DH_ACCEL_RANGE_16G
bool "+/-16g"
endchoice
choice
prompt "Power mode"
depends on LIS2DH
default LIS2DH_POWER_MODE_NORMAL
help
Choose between normal or low power operation mode for chip at init.
config LIS2DH_POWER_MODE_NORMAL
bool "normal"
config LIS2DH_POWER_MODE_LOW
bool "low"
endchoice
choice
prompt "Output data rate frequency"
depends on LIS2DH
default LIS2DH_ODR_RUNTIME
help
Initial data rate frequency of acceleration data at initialisation.
Supported values:
1Hz, 10Hz, 25Hz, 50Hz, 100Hz, 200Hz, 400Hz in all power modes
1620Hz, 5376Hz in low power mode only
1344Hz in normal power mode
config LIS2DH_ODR_RUNTIME
bool "Set at runtime"
config LIS2DH_ODR_1
bool "1Hz"
config LIS2DH_ODR_2
bool "10Hz"
config LIS2DH_ODR_3
bool "25Hz"
config LIS2DH_ODR_4
bool "50Hz"
config LIS2DH_ODR_5
bool "100Hz"
config LIS2DH_ODR_6
bool "200Hz"
config LIS2DH_ODR_7
bool "400Hz"
config LIS2DH_ODR_8
bool "1.6KHz"
depends on LIS2DH_POWER_MODE_LOW
config LIS2DH_ODR_9_NORMAL
bool "1.25KHz"
depends on LIS2DH_POWER_MODE_NORMAL
config LIS2DH_ODR_9_LOW
bool "5KHz"
depends on LIS2DH_POWER_MODE_LOW
endchoice

View file

@ -0,0 +1,2 @@
obj-$(CONFIG_LIS2DH) += lis2dh.o
obj-$(CONFIG_LIS2DH_TRIGGER) += lis2dh_trigger.o

View file

@ -0,0 +1,346 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lis2dh.h"
#include <init.h>
#include <misc/byteorder.h>
#include <misc/__assert.h>
#if defined(CONFIG_LIS2DH_TRIGGER) || defined(CONFIG_LIS2DH_ACCEL_RANGE_RUNTIME)
int lis2dh_reg_field_update(struct device *bus, u8_t reg_addr,
u8_t pos, u8_t mask, u8_t val)
{
int status;
u8_t old_val;
/* just to remove gcc warning */
old_val = 0;
status = lis2dh_reg_read_byte(bus, reg_addr, &old_val);
if (status < 0) {
return status;
}
return lis2dh_reg_write_byte(bus, reg_addr,
(old_val & ~mask) | ((val << pos) & mask));
}
#endif
static void lis2dh_convert(s16_t raw_val, u16_t scale,
struct sensor_value *val)
{
s32_t converted_val;
/*
* maximum converted value we can get is: max(raw_val) * max(scale)
* max(raw_val) = +/- 2^15
* max(scale) = 4785
* max(converted_val) = 156794880 which is less than 2^31
*/
converted_val = raw_val * scale;
val->val1 = converted_val / 1000000;
val->val2 = converted_val % 1000000;
/* normalize val to make sure val->val2 is positive */
if (val->val2 < 0) {
val->val1 -= 1;
val->val2 += 1000000;
}
}
static int lis2dh_channel_get(struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
struct lis2dh_data *lis2dh = dev->driver_data;
int ofs_start;
int ofs_end;
int i;
switch (chan) {
case SENSOR_CHAN_ACCEL_X:
ofs_start = ofs_end = 0;
break;
case SENSOR_CHAN_ACCEL_Y:
ofs_start = ofs_end = 1;
break;
case SENSOR_CHAN_ACCEL_Z:
ofs_start = ofs_end = 2;
break;
case SENSOR_CHAN_ACCEL_XYZ:
ofs_start = 0;
ofs_end = 2;
break;
default:
return -ENOTSUP;
}
for (i = ofs_start; i <= ofs_end; i++, val++) {
lis2dh_convert(lis2dh->sample.xyz[i], lis2dh->scale, val);
}
return 0;
}
static int lis2dh_sample_fetch(struct device *dev, enum sensor_channel chan)
{
struct lis2dh_data *lis2dh = dev->driver_data;
size_t i;
int status;
__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL ||
chan == SENSOR_CHAN_ACCEL_XYZ);
/*
* since status and all accel data register addresses are consecutive,
* a burst read can be used to read all the samples
*/
status = lis2dh_burst_read(lis2dh->bus, LIS2DH_REG_STATUS,
lis2dh->sample.raw,
sizeof(lis2dh->sample.raw));
if (status < 0) {
SYS_LOG_WRN("Could not read accel axis data");
return status;
}
for (i = 0; i < (3 * sizeof(s16_t)); i += sizeof(s16_t)) {
s16_t *sample =
(s16_t *)&lis2dh->sample.raw[LIS2DH_DATA_OFS + 1 + i];
*sample = sys_le16_to_cpu(*sample);
}
SYS_LOG_INF("status=0x%x x=%d y=%d z=%d", lis2dh->sample.status,
lis2dh->sample.xyz[0], lis2dh->sample.xyz[1],
lis2dh->sample.xyz[2]);
if (lis2dh->sample.status & LIS2DH_STATUS_OVR_MASK) {
return -EBADMSG;
} else if (lis2dh->sample.status & LIS2DH_STATUS_DRDY_MASK) {
return 0;
}
return -ENODATA;
}
#ifdef CONFIG_LIS2DH_ODR_RUNTIME
/* 1620 & 5376 are low power only */
static const u16_t lis2dh_odr_map[] = {0, 1, 10, 25, 50, 100, 200, 400, 1620,
1344, 5376};
static int lis2dh_freq_to_odr_val(u16_t freq)
{
size_t i;
/* An ODR of 0 Hz is not allowed */
if (freq == 0) {
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(lis2dh_odr_map); i++) {
if (freq == lis2dh_odr_map[i]) {
return i;
}
}
return -EINVAL;
}
static int lis2dh_acc_odr_set(struct device *dev, u16_t freq)
{
struct lis2dh_data *lis2dh = dev->driver_data;
int odr;
int status;
u8_t value;
odr = lis2dh_freq_to_odr_val(freq);
if (odr < 0) {
return odr;
}
status = lis2dh_reg_read_byte(lis2dh->bus, LIS2DH_REG_CTRL1, &value);
if (status < 0) {
return status;
}
/* some odr values cannot be set in certain power modes */
if ((value & LIS2DH_LP_EN_BIT) == 0 && odr == LIS2DH_ODR_8) {
return -ENOTSUP;
}
/* adjust odr index for LP enabled mode, see table above */
if ((value & LIS2DH_LP_EN_BIT) == 1 && (odr == LIS2DH_ODR_9 + 1)) {
odr--;
}
return lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_CTRL1,
(value & ~LIS2DH_ODR_MASK) |
LIS2DH_ODR_RATE(odr));
}
#endif
#ifdef CONFIG_LIS2DH_ACCEL_RANGE_RUNTIME
static const union {
u32_t word_le32;
u8_t fs_values[4];
} lis2dh_acc_range_map = { .fs_values = {2, 4, 8, 16} };
static int lis2dh_range_to_reg_val(u16_t range)
{
int i;
u32_t range_map;
range_map = sys_le32_to_cpu(lis2dh_acc_range_map.word_le32);
for (i = 0; range_map; i++, range_map >>= 1) {
if (range == (range_map & 0xff)) {
return i;
}
}
return -EINVAL;
}
static int lis2dh_acc_range_set(struct device *dev, s32_t range)
{
struct lis2dh_data *lis2dh = dev->driver_data;
int fs;
fs = lis2dh_range_to_reg_val(range);
if (fs < 0) {
return fs;
}
lis2dh->scale = LIS2DH_ACCEL_SCALE(range);
return lis2dh_reg_field_update(lis2dh->bus, LIS2DH_REG_CTRL4,
LIS2DH_FS_SHIFT,
LIS2DH_FS_MASK,
fs);
}
#endif
static int lis2dh_acc_config(struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
switch (attr) {
#ifdef CONFIG_LIS2DH_ACCEL_RANGE_RUNTIME
case SENSOR_ATTR_FULL_SCALE:
return lis2dh_acc_range_set(dev, sensor_ms2_to_g(val));
#endif
#ifdef CONFIG_LIS2DH_ODR_RUNTIME
case SENSOR_ATTR_SAMPLING_FREQUENCY:
return lis2dh_acc_odr_set(dev, val->val1);
#endif
#if defined(CONFIG_LIS2DH_TRIGGER)
case SENSOR_ATTR_SLOPE_TH:
case SENSOR_ATTR_SLOPE_DUR:
return lis2dh_acc_slope_config(dev, attr, val);
#endif
default:
SYS_LOG_DBG("Accel attribute not supported.");
return -ENOTSUP;
}
return 0;
}
static int lis2dh_attr_set(struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
switch (chan) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
return lis2dh_acc_config(dev, chan, attr, val);
default:
SYS_LOG_WRN("attr_set() not supported on this channel.");
return -ENOTSUP;
}
return 0;
}
static const struct sensor_driver_api lis2dh_driver_api = {
.attr_set = lis2dh_attr_set,
#if CONFIG_LIS2DH_TRIGGER
.trigger_set = lis2dh_trigger_set,
#endif
.sample_fetch = lis2dh_sample_fetch,
.channel_get = lis2dh_channel_get,
};
int lis2dh_init(struct device *dev)
{
struct lis2dh_data *lis2dh = dev->driver_data;
int status;
u8_t raw[LIS2DH_DATA_OFS + 6];
lis2dh->bus = device_get_binding(LIS2DH_BUS_DEV_NAME);
if (lis2dh->bus == NULL) {
SYS_LOG_ERR("Could not get pointer to %s device",
LIS2DH_BUS_DEV_NAME);
return -EINVAL;
}
/* configure bus, e.g. spi clock and format */
status = lis2dh_bus_configure(lis2dh->bus);
if (status < 0) {
SYS_LOG_ERR("Failed to configure bus (spi, i2c)");
return status;
}
/* Initialize control register ctrl1 to ctrl 6 to default boot values
* to avoid warm start/reset issues as the accelerometer has no reset
* pin. Register values are retained if power is not removed.
* Default values see LIS2DH documentation page 30, chapter 6.
*/
memset(raw, 0, sizeof(raw));
raw[LIS2DH_DATA_OFS] = LIS2DH_ACCEL_EN_BITS;
status = lis2dh_burst_write(lis2dh->bus, LIS2DH_REG_CTRL1, raw,
sizeof(raw));
if (status < 0) {
SYS_LOG_ERR("Failed to reset ctrl registers.");
return status;
}
/* set full scale range and store it for later conversion */
lis2dh->scale = LIS2DH_ACCEL_SCALE(1 << (LIS2DH_FS_IDX + 1));
status = lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_CTRL4,
LIS2DH_FS_BITS);
if (status < 0) {
SYS_LOG_ERR("Failed to set full scale ctrl register.");
return status;
}
#ifdef CONFIG_LIS2DH_TRIGGER
status = lis2dh_init_interrupt(dev);
if (status < 0) {
SYS_LOG_ERR("Failed to initialize interrupts.");
return status;
}
#endif
dev->driver_api = &lis2dh_driver_api;
SYS_LOG_INF("bus=%s fs=%d, odr=0x%x lp_en=0x%x scale=%d",
LIS2DH_BUS_DEV_NAME, 1 << (LIS2DH_FS_IDX + 1),
LIS2DH_ODR_IDX, (u8_t)LIS2DH_LP_EN_BIT, lis2dh->scale);
/* enable accel measurements and set power mode and data rate */
return lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_CTRL1,
LIS2DH_ACCEL_EN_BITS | LIS2DH_LP_EN_BIT |
LIS2DH_ODR_BITS);
}
static struct lis2dh_data lis2dh_driver;
DEVICE_INIT(lis2dh, CONFIG_LIS2DH_NAME, lis2dh_init, &lis2dh_driver,
NULL, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY);

View file

@ -0,0 +1,341 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __SENSOR_LIS2DH_H__
#define __SENSOR_LIS2DH_H__
#include <kernel.h>
#include <device.h>
#include <misc/util.h>
#include <stdint.h>
#include <gpio.h>
#include <sensor.h>
#include <string.h>
#if defined(CONFIG_LIS2DH_BUS_SPI)
#include <spi.h>
#define LIS2DH_BUS_ADDRESS CONFIG_LIS2DH_SPI_SS_1
#define LIS2DH_SPI_READ_BIT BIT(7)
#define LIS2DH_SPI_AUTOINC_ADDR BIT(6)
#define LIS2DH_SPI_ADDR_MASK BIT_MASK(6)
/* LIS2DH supports only SPI mode 0, word size 8 bits, MSB first */
#define LIS2DH_SPI_CFG 0x80
#define LIS2DH_BUS_DEV_NAME CONFIG_LIS2DH_SPI_MASTER_DEV_NAME
#elif defined(CONFIG_LIS2DH_BUS_I2C)
#include <i2c.h>
#define LIS2DH_BUS_ADDRESS CONFIG_LIS2DH_I2C_ADDR
#define LIS2DH_BUS_DEV_NAME CONFIG_LIS2DH_I2C_MASTER_DEV_NAME
#endif
#define LIS2DH_AUTOINCREMENT_ADDR BIT(7)
#define LIS2DH_REG_CTRL1 0x20
#define LIS2DH_ACCEL_XYZ_SHIFT 0
#define LIS2DH_ACCEL_X_EN_BIT BIT(0)
#define LIS2DH_ACCEL_Y_EN_BIT BIT(1)
#define LIS2DH_ACCEL_Z_EN_BIT BIT(2)
#define LIS2DH_ACCEL_EN_BITS (LIS2DH_ACCEL_X_EN_BIT | \
LIS2DH_ACCEL_Y_EN_BIT | \
LIS2DH_ACCEL_Z_EN_BIT)
#define LIS2DH_ACCEL_XYZ_MASK BIT_MASK(3)
#if defined(CONFIG_LIS2DH_POWER_MODE_LOW)
#define LIS2DH_LP_EN_BIT BIT(3)
#elif defined(CONFIG_LIS2DH_POWER_MODE_NORMAL)
#define LIS2DH_LP_EN_BIT 0
#endif
#define LIS2DH_ODR_1 1
#define LIS2DH_ODR_2 2
#define LIS2DH_ODR_3 3
#define LIS2DH_ODR_4 4
#define LIS2DH_ODR_5 5
#define LIS2DH_ODR_6 6
#define LIS2DH_ODR_7 7
#define LIS2DH_ODR_8 8
#define LIS2DH_ODR_9 9
#if defined(CONFIG_LIS2DH_ODR_1)
#define LIS2DH_ODR_IDX LIS2DH_ODR_1
#elif defined(CONFIG_LIS2DH_ODR_2)
#define LIS2DH_ODR_IDX LIS2DH_ODR_2
#elif defined(CONFIG_LIS2DH_ODR_3)
#define LIS2DH_ODR_IDX LIS2DH_ODR_3
#elif defined(CONFIG_LIS2DH_ODR_4) || defined(CONFIG_LIS2DH_ODR_RUNTIME)
#define LIS2DH_ODR_IDX LIS2DH_ODR_4
#elif defined(CONFIG_LIS2DH_ODR_5)
#define LIS2DH_ODR_IDX LIS2DH_ODR_5
#elif defined(CONFIG_LIS2DH_ODR_6)
#define LIS2DH_ODR_IDX LIS2DH_ODR_6
#elif defined(CONFIG_LIS2DH_ODR_7)
#define LIS2DH_ODR_IDX LIS2DH_ODR_7
#elif defined(CONFIG_LIS2DH_ODR_8)
#define LIS2DH_ODR_IDX LIS2DH_ODR_8
#elif defined(CONFIG_LIS2DH_ODR_9_NORMAL) || defined(CONFIG_LIS2DH_ODR_9_LOW)
#define LIS2DH_ODR_IDX LIS2DH_ODR_9
#endif
#define LIS2DH_ODR_SHIFT 4
#define LIS2DH_ODR_RATE(r) ((r) << LIS2DH_ODR_SHIFT)
#define LIS2DH_ODR_BITS (LIS2DH_ODR_RATE(LIS2DH_ODR_IDX))
#define LIS2DH_ODR_MASK (BIT_MASK(4) << LIS2DH_ODR_SHIFT)
#define LIS2DH_REG_CTRL2 0x21
#define LIS2DH_HPIS2_EN_BIT BIT(1)
#define LIS2DH_FDS_EN_BIT BIT(3)
#define LIS2DH_REG_CTRL3 0x22
#define LIS2DH_EN_DRDY1_INT1_SHIFT 4
#define LIS2DH_EN_DRDY1_INT1 BIT(LIS2DH_EN_DRDY1_INT1_SHIFT)
#define LIS2DH_REG_CTRL4 0x23
#define LIS2DH_FS_SHIFT 4
#define LIS2DH_FS_MASK (BIT_MASK(2) << LIS2DH_FS_SHIFT)
#if defined(CONFIG_LIS2DH_ACCEL_RANGE_2G) ||\
defined(CONFIG_LIS2DH_ACCEL_RANGE_RUNTIME)
#define LIS2DH_FS_IDX 0
#elif defined(CONFIG_LIS2DH_ACCEL_RANGE_4G)
#define LIS2DH_FS_IDX 1
#elif defined(CONFIG_LIS2DH_ACCEL_RANGE_8G)
#define LIS2DH_FS_IDX 2
#elif defined(CONFIG_LIS2DH_ACCEL_RANGE_16G)
#define LIS2DH_FS_IDX 3
#endif
#define LIS2DH_FS_SELECT(fs) ((fs) << LIS2DH_FS_SHIFT)
#define LIS2DH_FS_BITS (LIS2DH_FS_SELECT(LIS2DH_FS_IDX))
#define LIS2DH_ACCEL_SCALE(range_g) ((SENSOR_G * 2 * (range_g)) / 65636LL)
#define LIS2DH_REG_CTRL5 0x24
#define LIS2DH_LIR_INT2_SHIFT 1
#define LIS2DH_EN_LIR_INT2 BIT(LIS2DH_LIR_INT2_SHIFT)
#define LIS2DH_REG_CTRL6 0x25
#define LIS2DH_EN_INT2_INT2_SHIFT 5
#define LIS2DH_EN_INT2_INT2 BIT(LIS2DH_EN_INT2_INT2_SHIFT)
#define LIS2DH_REG_REFERENCE 0x26
#define LIS2DH_REG_STATUS 0x27
#define LIS2DH_STATUS_ZYZ_OVR BIT(7)
#define LIS2DH_STATUS_Z_OVR BIT(6)
#define LIS2DH_STATUS_Y_OVR BIT(5)
#define LIS2DH_STATUS_X_OVR BIT(4)
#define LIS2DH_STATUS_OVR_MASK (BIT_MASK(4) << 4)
#define LIS2DH_STATUS_ZYX_DRDY BIT(3)
#define LIS2DH_STATUS_Z_DRDY BIT(2)
#define LIS2DH_STATUS_Y_DRDY BIT(1)
#define LIS2DH_STATUS_X_DRDY BIT(0)
#define LIS2DH_STATUS_DRDY_MASK BIT_MASK(4)
#define LIS2DH_REG_ACCEL_X_LSB 0x28
#define LIS2DH_REG_ACCEL_Y_LSB 0x2A
#define LIS2DH_REG_ACCEL_Z_LSB 0x2C
#define LIS2DH_REG_ACCEL_X_MSB 0x29
#define LIS2DH_REG_ACCEL_Y_MSB 0x2B
#define LIS2DH_REG_ACCEL_Z_MSB 0x2D
#define LIS2DH_REG_INT1_CFG 0x30
#define LIS2DH_REG_INT2_CFG 0x34
#define LIS2DH_AOI_CFG BIT(7)
#define LIS2DH_INT_CFG_ZHIE_ZUPE BIT(5)
#define LIS2DH_INT_CFG_ZLIE_ZDOWNE BIT(4)
#define LIS2DH_INT_CFG_YHIE_YUPE BIT(3)
#define LIS2DH_INT_CFG_YLIE_YDOWNE BIT(2)
#define LIS2DH_INT_CFG_XHIE_XUPE BIT(1)
#define LIS2DH_INT_CFG_XLIE_XDOWNE BIT(0)
#define LIS2DH_REG_INT2_SRC 0x35
#define LIS2DH_REG_INT2_THS 0x36
#define LIS2DH_REG_INT2_DUR 0x37
/* sample buffer size includes status register */
#if defined(CONFIG_LIS2DH_BUS_SPI)
#define LIS2DH_BUF_SZ 8
#define LIS2DH_DATA_OFS 1
#else
#define LIS2DH_BUF_SZ 7
#define LIS2DH_DATA_OFS 0
#endif
union lis2dh_sample {
u8_t raw[LIS2DH_BUF_SZ];
struct {
#if defined(CONFIG_LIS2DH_BUS_SPI)
u8_t dummy;
#endif
u8_t status;
s16_t xyz[3];
} __packed;
};
struct lis2dh_data {
struct device *bus;
union lis2dh_sample sample;
/* current scaling factor, in micro m/s^2 / lsb */
u16_t scale;
#ifdef CONFIG_LIS2DH_TRIGGER
struct device *gpio;
struct gpio_callback gpio_int1_cb;
struct gpio_callback gpio_int2_cb;
sensor_trigger_handler_t handler_drdy;
sensor_trigger_handler_t handler_anymotion;
atomic_t trig_flags;
enum sensor_channel chan_drdy;
#if defined(CONFIG_LIS2DH_TRIGGER_OWN_THREAD)
char __stack thread_stack[CONFIG_LIS2DH_THREAD_STACK_SIZE];
struct k_sem gpio_sem;
#elif defined(CONFIG_LIS2DH_TRIGGER_GLOBAL_THREAD)
struct k_work work;
struct device *dev;
#endif
#endif /* CONFIG_LIS2DH_TRIGGER */
};
#define SYS_LOG_DOMAIN "lis2dh"
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_SENSOR_LEVEL
#include <logging/sys_log.h>
static inline int lis2dh_burst_read(struct device *bus, u8_t start_addr,
u8_t *buf, u8_t num_bytes)
{
#if defined(CONFIG_LIS2DH_BUS_SPI)
int status;
u8_t tx_buf = LIS2DH_SPI_READ_BIT | LIS2DH_SPI_AUTOINC_ADDR |
start_addr;
status = spi_slave_select(bus, LIS2DH_BUS_ADDRESS);
status = spi_transceive(bus, &tx_buf, 1, buf, num_bytes);
SYS_LOG_DBG("tx=0x%x num=0x%x, status=%d", tx_buf,
num_bytes, status);
return status;
#elif defined(CONFIG_LIS2DH_BUS_I2C)
return i2c_burst_read(bus, LIS2DH_BUS_ADDRESS, start_addr, buf,
num_bytes);
#else
return -ENODEV;
#endif
}
static inline int lis2dh_reg_read_byte(struct device *bus, u8_t reg_addr,
u8_t *value)
{
#if defined(CONFIG_LIS2DH_BUS_SPI)
int status;
u8_t tx_buf = LIS2DH_SPI_READ_BIT | reg_addr;
u8_t rx_buf[2];
status = spi_slave_select(bus, LIS2DH_BUS_ADDRESS);
status = spi_transceive(bus, &tx_buf, 1, rx_buf, 2);
if (status == 0) {
*value = rx_buf[1];
}
SYS_LOG_DBG("tx=0x%x rx1=0x%x", tx_buf, rx_buf[1]);
return status;
#elif defined(CONFIG_LIS2DH_BUS_I2C)
return i2c_reg_read_byte(bus, LIS2DH_BUS_ADDRESS, reg_addr, value);
#else
return -ENODEV;
#endif
}
static inline int lis2dh_burst_write(struct device *bus, u8_t start_addr,
u8_t *buf, u8_t num_bytes)
{
#if defined(CONFIG_LIS2DH_BUS_SPI)
int status;
u8_t dummy;
buf[0] = LIS2DH_SPI_AUTOINC_ADDR | start_addr;
status = spi_slave_select(bus, LIS2DH_BUS_ADDRESS);
status = spi_transceive(bus, buf, num_bytes, &dummy, 0);
SYS_LOG_DBG("tx=0x%x num=0x%x, status=%d", buf[0],
num_bytes, status);
return status;
#elif defined(CONFIG_LIS2DH_BUS_I2C)
return i2c_burst_write(bus, LIS2DH_BUS_ADDRESS, start_addr, buf,
num_bytes);
#else
return -ENODEV;
#endif
}
static inline int lis2dh_reg_write_byte(struct device *bus, u8_t reg_addr,
u8_t value)
{
#if defined(CONFIG_LIS2DH_BUS_SPI)
u8_t tx_buf[2] = { reg_addr & LIS2DH_SPI_ADDR_MASK, value };
u8_t dummy;
spi_slave_select(bus, LIS2DH_BUS_ADDRESS);
SYS_LOG_DBG("tx0=0x%x tx1=0x%x", tx_buf[0], tx_buf[1]);
return spi_transceive(bus, tx_buf, 2, &dummy, 0);
#elif defined(CONFIG_LIS2DH_BUS_I2C)
u8_t tx_buf[2] = {reg_addr, value};
return i2c_write(bus, tx_buf, sizeof(tx_buf), LIS2DH_BUS_ADDRESS);
#else
return -ENODEV;
#endif
}
static inline int lis2dh_bus_configure(struct device *bus)
{
#if defined(CONFIG_LIS2DH_BUS_SPI)
struct spi_config config = {
.config = LIS2DH_SPI_CFG,
.max_sys_freq = CONFIG_LIS2DH_SPI_FREQUENCY,
};
return spi_configure(bus, &config);
#elif defined(CONFIG_LIS2DH_BUS_I2C)
return 0;
#else
return -ENODEV;
#endif
}
#ifdef CONFIG_LIS2DH_TRIGGER
int lis2dh_trigger_set(struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
int lis2dh_init_interrupt(struct device *dev);
int lis2dh_reg_field_update(struct device *bus, u8_t reg_addr,
u8_t pos, u8_t mask, u8_t val);
int lis2dh_acc_slope_config(struct device *dev, enum sensor_attribute attr,
const struct sensor_value *val);
#endif
#endif /* __SENSOR_LIS2DH__ */

View file

@ -0,0 +1,433 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lis2dh.h"
#include <misc/util.h>
#include <kernel.h>
#define START_TRIG_INT1 BIT(0)
#define START_TRIG_INT2 BIT(1)
#define TRIGGED_INT1 BIT(4)
#define TRIGGED_INT2 BIT(5)
static int lis2dh_trigger_drdy_set(struct device *dev, enum sensor_channel chan,
sensor_trigger_handler_t handler)
{
struct lis2dh_data *lis2dh = dev->driver_data;
int status;
gpio_pin_disable_callback(lis2dh->gpio, CONFIG_LIS2DH_INT1_GPIO_PIN);
/* cancel potentially pending trigger */
atomic_clear_bit(&lis2dh->trig_flags, TRIGGED_INT1);
status = lis2dh_reg_field_update(lis2dh->bus, LIS2DH_REG_CTRL3,
LIS2DH_EN_DRDY1_INT1_SHIFT,
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 lis2dh_data *lis2dh)
{
int status;
u8_t raw[LIS2DH_BUF_SZ];
u8_t ctrl1 = 0;
/* power down temporarly to align interrupt & data output sampling */
status = lis2dh_reg_read_byte(lis2dh->bus, LIS2DH_REG_CTRL1, &ctrl1);
if (unlikely(status < 0)) {
return status;
}
status = lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_CTRL1,
ctrl1 & ~LIS2DH_ODR_MASK);
if (unlikely(status < 0)) {
return status;
}
SYS_LOG_DBG("ctrl1=0x%x @tick=%u", ctrl1, k_cycle_get_32());
/* empty output data */
status = lis2dh_burst_read(lis2dh->bus, LIS2DH_REG_STATUS, raw,
sizeof(raw));
if (unlikely(status < 0)) {
return status;
}
gpio_pin_enable_callback(lis2dh->gpio, CONFIG_LIS2DH_INT1_GPIO_PIN);
/* re-enable output sampling */
status = lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_CTRL1, ctrl1);
if (unlikely(status < 0)) {
return status;
}
return lis2dh_reg_field_update(lis2dh->bus, LIS2DH_REG_CTRL3,
LIS2DH_EN_DRDY1_INT1_SHIFT,
LIS2DH_EN_DRDY1_INT1, 1);
}
#define LIS2DH_ANYM_CFG (LIS2DH_INT_CFG_ZHIE_ZUPE | LIS2DH_INT_CFG_YHIE_YUPE |\
LIS2DH_INT_CFG_XHIE_XUPE)
static int lis2dh_trigger_anym_set(struct device *dev,
sensor_trigger_handler_t handler)
{
struct lis2dh_data *lis2dh = dev->driver_data;
int status;
u8_t reg_val;
gpio_pin_disable_callback(lis2dh->gpio, CONFIG_LIS2DH_INT2_GPIO_PIN);
/* cancel potentially pending trigger */
atomic_clear_bit(&lis2dh->trig_flags, TRIGGED_INT2);
/* disable all interrupt 2 events */
status = lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_INT2_CFG, 0);
/* make sure any pending interrupt is cleared */
status = lis2dh_reg_read_byte(lis2dh->bus, LIS2DH_REG_INT2_SRC,
&reg_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 lis2dh_data *lis2dh)
{
int status;
status = gpio_pin_enable_callback(lis2dh->gpio,
CONFIG_LIS2DH_INT2_GPIO_PIN);
if (unlikely(status < 0)) {
SYS_LOG_ERR("enable callback failed err=%d", status);
}
return lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_INT2_CFG,
LIS2DH_ANYM_CFG);
}
int lis2dh_trigger_set(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);
} else if (trig->type == SENSOR_TRIG_DELTA) {
return lis2dh_trigger_anym_set(dev, handler);
}
return -ENOTSUP;
}
int lis2dh_acc_slope_config(struct device *dev, enum sensor_attribute attr,
const struct sensor_value *val)
{
struct lis2dh_data *lis2dh = dev->driver_data;
int status;
if (attr == SENSOR_ATTR_SLOPE_TH) {
u8_t range_g, reg_val;
u32_t slope_th_ums2;
status = lis2dh_reg_read_byte(lis2dh->bus, 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;
SYS_LOG_INF("int2_ths=0x%x range_g=%d ums2=%u", reg_val,
range_g, slope_th_ums2 - 1);
status = lis2dh_reg_write_byte(lis2dh->bus, 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;
}
SYS_LOG_INF("int2_dur=0x%x", val->val1);
status = lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_INT2_DUR,
val->val1);
}
return status;
}
static void lis2dh_gpio_int1_callback(struct device *dev,
struct gpio_callback *cb, u32_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(struct device *dev,
struct gpio_callback *cb, u32_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(void *arg)
{
struct device *dev = arg;
struct lis2dh_data *lis2dh = dev->driver_data;
if (unlikely(atomic_test_and_clear_bit(&lis2dh->trig_flags,
START_TRIG_INT1))) {
int status = lis2dh_start_trigger_int1(lis2dh);
if (unlikely(status < 0)) {
SYS_LOG_ERR("lis2dh_start_trigger_int1: %d", status);
}
return;
}
if (unlikely(atomic_test_and_clear_bit(&lis2dh->trig_flags,
START_TRIG_INT2))) {
int status = lis2dh_start_trigger_int2(lis2dh);
if (unlikely(status < 0)) {
SYS_LOG_ERR("lis2dh_start_trigger_int2: %d", status);
}
return;
}
if (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 (atomic_test_and_clear_bit(&lis2dh->trig_flags,
TRIGGED_INT2)) {
struct sensor_trigger anym_trigger = {
.type = SENSOR_TRIG_DELTA,
.chan = lis2dh->chan_drdy,
};
u8_t reg_val;
/* clear interrupt 2 to de-assert int2 line */
lis2dh_reg_read_byte(lis2dh->bus, LIS2DH_REG_INT2_SRC,
&reg_val);
if (likely(lis2dh->handler_anymotion != NULL)) {
lis2dh->handler_anymotion(dev, &anym_trigger);
}
SYS_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(void *arg1, void *unused2, void *unused3)
{
struct device *dev = arg1;
struct lis2dh_data *lis2dh = dev->driver_data;
ARG_UNUSED(unused2);
ARG_UNUSED(unused3);
while (1) {
k_sem_take(&lis2dh->gpio_sem, K_FOREVER);
lis2dh_thread_cb(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
#define LIS2DH_INT1_CFG (GPIO_DIR_IN | GPIO_INT |\
GPIO_INT_EDGE | GPIO_INT_ACTIVE_HIGH)
#define LIS2DH_INT2_CFG (GPIO_DIR_IN | GPIO_INT |\
GPIO_INT_EDGE | GPIO_INT_ACTIVE_HIGH)
int lis2dh_init_interrupt(struct device *dev)
{
struct lis2dh_data *lis2dh = dev->driver_data;
int status;
u8_t raw[LIS2DH_DATA_OFS + 2];
/* setup data ready gpio interrupt */
lis2dh->gpio = device_get_binding(CONFIG_LIS2DH_GPIO_DEV_NAME);
if (lis2dh->gpio == NULL) {
SYS_LOG_ERR("Cannot get pointer to %s device",
CONFIG_LIS2DH_GPIO_DEV_NAME);
return -EINVAL;
}
/* data ready int1 gpio configuration */
status = gpio_pin_configure(lis2dh->gpio, CONFIG_LIS2DH_INT1_GPIO_PIN,
LIS2DH_INT1_CFG);
if (status < 0) {
SYS_LOG_ERR("Could not configure gpio %d",
CONFIG_LIS2DH_INT1_GPIO_PIN);
return status;
}
gpio_init_callback(&lis2dh->gpio_int1_cb,
lis2dh_gpio_int1_callback,
BIT(CONFIG_LIS2DH_INT1_GPIO_PIN));
status = gpio_add_callback(lis2dh->gpio, &lis2dh->gpio_int1_cb);
if (status < 0) {
SYS_LOG_ERR("Could not add gpio int1 callback");
return status;
}
/* any motion int2 gpio configuration */
status = gpio_pin_configure(lis2dh->gpio, CONFIG_LIS2DH_INT2_GPIO_PIN,
LIS2DH_INT2_CFG);
if (status < 0) {
SYS_LOG_ERR("Could not configure gpio %d",
CONFIG_LIS2DH_INT2_GPIO_PIN);
return status;
}
gpio_init_callback(&lis2dh->gpio_int2_cb,
lis2dh_gpio_int2_callback,
BIT(CONFIG_LIS2DH_INT2_GPIO_PIN));
/* callback is going to be enabled by trigger setting function */
status = gpio_add_callback(lis2dh->gpio, &lis2dh->gpio_int2_cb);
if (status < 0) {
SYS_LOG_ERR("Could not add gpio int2 callback (%d)", status);
return status;
}
#if defined(CONFIG_LIS2DH_TRIGGER_OWN_THREAD)
k_sem_init(&lis2dh->gpio_sem, 0, UINT_MAX);
k_thread_spawn(lis2dh->thread_stack, CONFIG_LIS2DH_THREAD_STACK_SIZE,
(k_thread_entry_t)lis2dh_thread, dev, NULL, NULL,
K_PRIO_COOP(CONFIG_LIS2DH_THREAD_PRIORITY), 0, 0);
#elif defined(CONFIG_LIS2DH_TRIGGER_GLOBAL_THREAD)
lis2dh->work.handler = lis2dh_work_cb;
lis2dh->dev = dev;
#endif
/* disable interrupt 2 in case of warm (re)boot */
status = lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_INT2_CFG, 0);
if (status < 0) {
SYS_LOG_ERR("Interrupt 2 disable reg write failed (%d)",
status);
return status;
}
memset(raw, 0, sizeof(raw));
status = lis2dh_burst_write(lis2dh->bus, LIS2DH_REG_INT2_THS, raw,
sizeof(raw));
if (status < 0) {
SYS_LOG_ERR("Burst write to INT2 THS failed (%d)", status);
return status;
}
/* latch int2 line interrupt */
status = lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_CTRL5,
LIS2DH_EN_LIR_INT2);
if (status < 0) {
SYS_LOG_ERR("INT2 latch enable reg write failed (%d)", status);
return status;
}
SYS_LOG_INF("int1 on pin=%d cfg=0x%x, int2 on pin=%d cfg=0x%x",
CONFIG_LIS2DH_INT1_GPIO_PIN, LIS2DH_INT1_CFG,
CONFIG_LIS2DH_INT2_GPIO_PIN, LIS2DH_INT2_CFG);
/* enable interrupt 2 on int2 line */
return lis2dh_reg_write_byte(lis2dh->bus, LIS2DH_REG_CTRL6,
LIS2DH_EN_INT2_INT2);
}