410 lines
9.2 KiB
C
410 lines
9.2 KiB
C
/*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Würth Elektronic WSEN-ITDS 3-axis accel sensor driver
|
|
*
|
|
* Copyright (c) 2020 Linumiz
|
|
* Author: Saravanan Sekar <saravanan@linumiz.com>
|
|
*/
|
|
|
|
#include <init.h>
|
|
#include <drivers/sensor.h>
|
|
#include <sys/byteorder.h>
|
|
#include <kernel.h>
|
|
#include <sys/__assert.h>
|
|
#include <logging/log.h>
|
|
#include "itds.h"
|
|
|
|
#define DT_DRV_COMPAT we_wsen_itds
|
|
#define ITDS_TEMP_CONST 62500
|
|
|
|
LOG_MODULE_REGISTER(ITDS, CONFIG_SENSOR_LOG_LEVEL);
|
|
|
|
static const struct itds_odr itds_odr_map[ITDS_ODR_MAX] = {
|
|
{0}, {1, 600}, {12, 500}, {25}, {50}, {100}, {200},
|
|
{400}, {800}, {1600}
|
|
};
|
|
|
|
static const unsigned int itds_sensitivity_scale[][ITDS_ACCL_RANGE_END] = {
|
|
{976, 1952, 3904, 7808},
|
|
|
|
/* high performance mode */
|
|
{244, 488, 976, 1952}
|
|
};
|
|
|
|
static int itds_get_odr_for_index(const struct device *dev,
|
|
enum itds_odr_const idx,
|
|
uint16_t *freq, uint16_t *mfreq)
|
|
{
|
|
struct itds_device_data *ddata = dev->data;
|
|
int start, end;
|
|
bool hp_mode;
|
|
|
|
hp_mode = !!(ddata->op_mode & ITDS_OP_MODE_HIGH_PERF);
|
|
if (hp_mode) {
|
|
start = ITDS_ODR_12_5;
|
|
end = ITDS_ODR_1600;
|
|
} else {
|
|
start = ITDS_ODR_1_6;
|
|
end = ITDS_ODR_200;
|
|
}
|
|
|
|
if (idx < start || idx > end) {
|
|
LOG_ERR("invalid odr for the operating mode");
|
|
return -EINVAL;
|
|
}
|
|
|
|
*freq = itds_odr_map[idx].freq;
|
|
*mfreq = itds_odr_map[idx].mfreq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int itds_accl_odr_set(const struct device *dev, uint16_t freq,
|
|
uint16_t mfreq)
|
|
{
|
|
struct itds_device_data *ddata = dev->data;
|
|
const struct itds_device_config *cfg = dev->config;
|
|
int start, end, i;
|
|
bool hp_mode;
|
|
|
|
hp_mode = !!(ddata->op_mode & ITDS_OP_MODE_HIGH_PERF);
|
|
if (hp_mode) {
|
|
start = ITDS_ODR_12_5;
|
|
end = ITDS_ODR_1600;
|
|
} else {
|
|
start = ITDS_ODR_1_6;
|
|
end = ITDS_ODR_200;
|
|
}
|
|
|
|
for (i = start; i <= end; i++) {
|
|
if ((freq == itds_odr_map[i].freq) &&
|
|
(mfreq == itds_odr_map[i].mfreq)) {
|
|
|
|
return i2c_reg_update_byte(ddata->i2c, cfg->i2c_addr,
|
|
ITDS_REG_CTRL1, ITDS_MASK_ODR, i << 4);
|
|
}
|
|
}
|
|
|
|
LOG_ERR("invalid odr, not in range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int itds_accl_range_set(const struct device *dev, int32_t range)
|
|
{
|
|
struct itds_device_data *ddata = dev->data;
|
|
const struct itds_device_config *cfg = dev->config;
|
|
int i, ret;
|
|
bool hp_mode;
|
|
|
|
for (i = 0; i < ITDS_ACCL_RANGE_END; i++) {
|
|
if (range <= (2 << i)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ITDS_ACCL_RANGE_END) {
|
|
LOG_ERR("Accl out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = i2c_reg_update_byte(ddata->i2c, cfg->i2c_addr, ITDS_REG_CTRL6,
|
|
ITDS_MASK_SCALE, i << 4);
|
|
if (ret) {
|
|
LOG_ERR("Accl set full scale failed %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
hp_mode = !!(ddata->op_mode & ITDS_OP_MODE_HIGH_PERF);
|
|
ddata->scale = itds_sensitivity_scale[hp_mode][i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int itds_attr_set(const struct device *dev, enum sensor_channel chan,
|
|
enum sensor_attribute attr,
|
|
const struct sensor_value *val)
|
|
{
|
|
if (chan != SENSOR_CHAN_ACCEL_X &&
|
|
chan != SENSOR_CHAN_ACCEL_Y &&
|
|
chan != SENSOR_CHAN_ACCEL_Z &&
|
|
chan != SENSOR_CHAN_ACCEL_XYZ) {
|
|
LOG_ERR("attr_set() not supported on this channel.");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (attr) {
|
|
case SENSOR_ATTR_FULL_SCALE:
|
|
return itds_accl_range_set(dev, sensor_ms2_to_g(val));
|
|
|
|
case SENSOR_ATTR_SAMPLING_FREQUENCY:
|
|
return itds_accl_odr_set(dev, val->val1, val->val2 / 1000);
|
|
|
|
default:
|
|
LOG_ERR("Accel attribute not supported.");
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
static int itds_fetch_temprature(struct itds_device_data *ddata,
|
|
const struct itds_device_config *cfg)
|
|
{
|
|
uint8_t rval;
|
|
int16_t temp_raw = 0;
|
|
int ret;
|
|
|
|
ret = i2c_reg_read_byte(ddata->i2c, cfg->i2c_addr,
|
|
ITDS_REG_STATUS_DETECT, &rval);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (!(rval & ITDS_EVENT_DRDY_T)) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
ret = i2c_burst_read(ddata->i2c, cfg->i2c_addr, ITDS_REG_TEMP_L,
|
|
(uint8_t *)&temp_raw, sizeof(uint16_t));
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ddata->temprature = sys_le16_to_cpu(temp_raw);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int itds_fetch_accel(struct itds_device_data *ddata,
|
|
const struct itds_device_config *cfg)
|
|
{
|
|
size_t i, ret;
|
|
uint8_t rval;
|
|
|
|
ret = i2c_reg_read_byte(ddata->i2c, cfg->i2c_addr,
|
|
ITDS_REG_STATUS, &rval);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (!(rval & ITDS_EVENT_DRDY)) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
ret = i2c_burst_read(ddata->i2c, cfg->i2c_addr, ITDS_REG_X_OUT_L,
|
|
(uint8_t *)ddata->samples,
|
|
sizeof(uint16_t) * ITDS_SAMPLE_SIZE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* convert samples to cpu endianness */
|
|
for (i = 0; i < ITDS_SAMPLE_SIZE; i += 2) {
|
|
int16_t *sample = (int16_t *) &ddata->samples[i];
|
|
|
|
*sample = sys_le16_to_cpu(*sample);
|
|
if (ddata->op_mode & ITDS_OP_MODE_NORMAL ||
|
|
ddata->op_mode & ITDS_OP_MODE_HIGH_PERF) {
|
|
*sample = *sample >> 2;
|
|
} else {
|
|
*sample = *sample >> 4;
|
|
}
|
|
LOG_DBG("itds sample %d %X\n", i, *sample);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int itds_sample_fetch(const struct device *dev,
|
|
enum sensor_channel chan)
|
|
{
|
|
struct itds_device_data *ddata = dev->data;
|
|
const struct itds_device_config *cfg = dev->config;
|
|
|
|
switch (chan) {
|
|
case SENSOR_CHAN_ACCEL_XYZ:
|
|
case SENSOR_CHAN_ACCEL_X:
|
|
case SENSOR_CHAN_ACCEL_Y:
|
|
case SENSOR_CHAN_ACCEL_Z:
|
|
return itds_fetch_accel(ddata, cfg);
|
|
|
|
case SENSOR_CHAN_DIE_TEMP:
|
|
return itds_fetch_temprature(ddata, cfg);
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static inline void itds_accl_channel_get(const struct device *dev,
|
|
enum sensor_channel chan,
|
|
struct sensor_value *val)
|
|
{
|
|
int i;
|
|
struct itds_device_data *ddata = dev->data;
|
|
uint8_t ofs_start, ofs_stop;
|
|
|
|
switch (chan) {
|
|
case SENSOR_CHAN_ACCEL_X:
|
|
ofs_start = ofs_stop = 0U;
|
|
break;
|
|
case SENSOR_CHAN_ACCEL_Y:
|
|
ofs_start = ofs_stop = 1U;
|
|
break;
|
|
case SENSOR_CHAN_ACCEL_Z:
|
|
ofs_start = ofs_stop = 2U;
|
|
break;
|
|
default:
|
|
ofs_start = 0U; ofs_stop = 2U;
|
|
break;
|
|
}
|
|
|
|
for (i = ofs_start; i <= ofs_stop ; i++, val++) {
|
|
int64_t dval;
|
|
|
|
/* Sensitivity is exposed in ug/LSB */
|
|
/* Convert to m/s^2 */
|
|
dval = (int64_t)((ddata->samples[i] * ddata->scale * SENSOR_G) /
|
|
1000000LL);
|
|
val->val1 = (int32_t)(dval / 1000000);
|
|
val->val2 = (int32_t)(dval % 1000000);
|
|
}
|
|
}
|
|
|
|
static int itds_temp_channel_get(const struct device *dev,
|
|
struct sensor_value *val)
|
|
{
|
|
int32_t temp_processed;
|
|
struct itds_device_data *ddata = dev->data;
|
|
|
|
temp_processed = (ddata->temprature >> 4) * ITDS_TEMP_CONST;
|
|
|
|
val->val1 = ITDS_TEMP_OFFSET;
|
|
val->val2 = temp_processed;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int itds_channel_get(const struct device *dev,
|
|
enum sensor_channel chan,
|
|
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:
|
|
itds_accl_channel_get(dev, chan, val);
|
|
return 0;
|
|
|
|
case SENSOR_CHAN_DIE_TEMP:
|
|
return itds_temp_channel_get(dev, val);
|
|
|
|
default:
|
|
LOG_ERR("Channel not supported.");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int itds_init(const struct device *dev)
|
|
{
|
|
struct itds_device_data *ddata = dev->data;
|
|
const struct itds_device_config *cfg = dev->config;
|
|
int ret;
|
|
uint16_t freq, mfreq;
|
|
uint8_t rval;
|
|
|
|
ddata->i2c = device_get_binding(cfg->bus_name);
|
|
if (!ddata->i2c) {
|
|
LOG_ERR("I2C controller not found: %s.", cfg->bus_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = i2c_reg_read_byte(ddata->i2c, cfg->i2c_addr,
|
|
ITDS_REG_DEV_ID, &rval);
|
|
if (ret) {
|
|
LOG_ERR("device init fail: %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (rval != ITDS_DEVICE_ID) {
|
|
LOG_ERR("device ID mismatch: %x", rval);
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_reg_update_byte(ddata->i2c, cfg->i2c_addr, ITDS_REG_CTRL2,
|
|
ITDS_MASK_BDU_INC_ADD, ITDS_MASK_BDU_INC_ADD);
|
|
if (ret) {
|
|
LOG_ERR("unable to set block data update %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_reg_write_byte(ddata->i2c, cfg->i2c_addr,
|
|
ITDS_REG_WAKEUP_EVENT, 0);
|
|
if (ret) {
|
|
LOG_ERR("disable wakeup event fail %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_reg_update_byte(ddata->i2c, cfg->i2c_addr, ITDS_REG_CTRL1,
|
|
ITDS_MASK_MODE, 1 << cfg->def_op_mode);
|
|
if (ret) {
|
|
LOG_ERR("set operating mode fail %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
ddata->op_mode = 1 << cfg->def_op_mode;
|
|
|
|
ret = itds_get_odr_for_index(dev, cfg->def_odr, &freq, &mfreq);
|
|
if (ret) {
|
|
LOG_ERR("odr not in range for operating mode %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = itds_accl_odr_set(dev, freq, mfreq);
|
|
if (ret) {
|
|
LOG_ERR("odr not in range for operating mode %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_ITDS_TRIGGER
|
|
ret = itds_trigger_mode_init(dev);
|
|
if (ret) {
|
|
LOG_ERR("trigger mode init failed %d", ret);
|
|
return ret;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static const struct sensor_driver_api itds_api = {
|
|
.attr_set = itds_attr_set,
|
|
#ifdef CONFIG_ITDS_TRIGGER
|
|
.trigger_set = itds_trigger_set,
|
|
#endif
|
|
.sample_fetch = itds_sample_fetch,
|
|
.channel_get = itds_channel_get,
|
|
};
|
|
|
|
#define WSEN_ITDS_INIT(idx) \
|
|
\
|
|
static struct itds_device_data itds_data_##idx; \
|
|
\
|
|
static const struct itds_device_config itds_config_##idx = { \
|
|
.i2c_addr = DT_INST_REG_ADDR(idx), \
|
|
.bus_name = DT_INST_BUS_LABEL(idx), \
|
|
.gpio_port = DT_INST_GPIO_LABEL(idx, int_gpios), \
|
|
.int_pin = DT_INST_GPIO_PIN(idx, int_gpios), \
|
|
.int_flags = DT_INST_GPIO_FLAGS(idx, int_gpios), \
|
|
.def_odr = DT_ENUM_IDX(DT_DRV_INST(idx), odr), \
|
|
.def_op_mode = DT_ENUM_IDX(DT_DRV_INST(idx), op_mode), \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(idx, itds_init, device_pm_control_nop, \
|
|
&itds_data_##idx, &itds_config_##idx, \
|
|
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \
|
|
&itds_api); \
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(WSEN_ITDS_INIT)
|