drivers: sensors: add MC3419 accel sensor support

add basic sensor support for 3-axis accelerometer, currently
this driver support data acquisition and motion detection
features.

Signed-off-by: Karthikeyan Krishnasamy <karthikeyan@linumiz.com>
This commit is contained in:
Karthikeyan Krishnasamy 2023-08-19 12:59:26 +05:30 committed by Maureen Helm
parent 18d6919092
commit f5ed51c179
8 changed files with 675 additions and 0 deletions

View file

@ -89,6 +89,7 @@ add_subdirectory_ifdef(CONFIG_MAX31865 max31865)
add_subdirectory_ifdef(CONFIG_MAX31875 max31875)
add_subdirectory_ifdef(CONFIG_MAX44009 max44009)
add_subdirectory_ifdef(CONFIG_MAX6675 max6675)
add_subdirectory_ifdef(CONFIG_MC3419 mc3419)
add_subdirectory_ifdef(CONFIG_MCP9600 mcp9600)
add_subdirectory_ifdef(CONFIG_MCP970X mcp970x)
add_subdirectory_ifdef(CONFIG_MCP9808 mcp9808)

View file

@ -145,6 +145,7 @@ source "drivers/sensor/max31865/Kconfig"
source "drivers/sensor/max31875/Kconfig"
source "drivers/sensor/max44009/Kconfig"
source "drivers/sensor/max6675/Kconfig"
source "drivers/sensor/mc3419/Kconfig"
source "drivers/sensor/mchp_tach_xec/Kconfig"
source "drivers/sensor/mcp9600/Kconfig"
source "drivers/sensor/mcp970x/Kconfig"

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2023 Linumiz
zephyr_library()
zephyr_library_sources(mc3419.c)
zephyr_library_sources_ifdef(CONFIG_MC3419_TRIGGER mc3419_trigger.c)

View file

@ -0,0 +1,48 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2023 Linumiz
menuconfig MC3419
bool "MC3419 acclerometer driver"
default y
depends on DT_HAS_MEMSIC_MC3419_ENABLED
select I2C if $(dt_compat_on_bus,$(DT_COMPAT_MEMSIC_MC3419),i2c)
help
Enable driver for MC3419 acclerometer.
if MC3419
config MC3419_TRIGGER
bool "Trigger mode"
if MC3419_TRIGGER
config MC3419_TRIGGER_OWN_THREAD
bool "Use own thread"
help
Enable trigger to run in own thread. By
default it is global thread mode.
config MC3419_THREAD_PRIORITY
int "Own thread priority"
depends on MC3419_TRIGGER_OWN_THREAD
default 10
config MC3419_THREAD_STACK_SIZE
int "Own thread stask size"
depends on MC3419_TRIGGER_OWN_THREAD
default 1024
endif # MC3419_TRIGGER
config MC3419_DECIMATION_RATE
int "Enable decimation rate"
range 0 15
default 15
help
This helps in producing slower interrupt. Internal Data
Rate is divide by this decimation rate. If decimation rate
is 0 then internal data rate is equal to output data rate,
then it produce interrupt floods.
endif # MC3419

View file

@ -0,0 +1,314 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2023 Linumiz
*/
#define DT_DRV_COMPAT memsic_mc3419
#include <zephyr/init.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "mc3419.h"
LOG_MODULE_REGISTER(MC3419, CONFIG_SENSOR_LOG_LEVEL);
static const uint16_t mc3419_accel_sense_map[] = {1, 2, 4, 8, 6};
static struct mc3419_odr_map odr_map_table[] = {
{25}, {50}, {62, 500}, {100},
{125}, {250}, {500}, {1000}
};
static int mc3419_get_odr_value(uint16_t freq, uint16_t m_freq)
{
for (int i = 0; i < ARRAY_SIZE(odr_map_table); i++) {
if (odr_map_table[i].freq == freq &&
odr_map_table[i].mfreq == m_freq) {
return i;
}
}
return -EINVAL;
}
static inline int mc3419_set_op_mode(const struct mc3419_config *cfg,
enum mc3419_op_mode mode)
{
return i2c_reg_write_byte_dt(&cfg->i2c, MC3419_REG_OP_MODE, mode);
}
static int mc3419_sample_fetch(const struct device *dev,
enum sensor_channel chan)
{
int ret = 0;
const struct mc3419_config *cfg = dev->config;
struct mc3419_driver_data *data = dev->data;
k_sem_take(&data->sem, K_FOREVER);
ret = i2c_burst_read_dt(&cfg->i2c, MC3419_REG_XOUT_L,
(uint8_t *)data->samples,
MC3419_SAMPLE_READ_SIZE);
k_sem_give(&data->sem);
return ret;
}
static int mc3419_to_sensor_value(double sensitivity, int16_t *raw_data,
struct sensor_value *val)
{
double value = sys_le16_to_cpu(*raw_data);
value *= sensitivity * SENSOR_GRAVITY_DOUBLE / 1000;
return sensor_value_from_double(val, value);
}
static int mc3419_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
int ret = 0;
struct mc3419_driver_data *data = dev->data;
k_sem_take(&data->sem, K_FOREVER);
switch (chan) {
case SENSOR_CHAN_ACCEL_X:
ret = mc3419_to_sensor_value(data->sensitivity, &data->samples[0], val);
break;
case SENSOR_CHAN_ACCEL_Y:
ret = mc3419_to_sensor_value(data->sensitivity, &data->samples[1], val);
break;
case SENSOR_CHAN_ACCEL_Z:
ret = mc3419_to_sensor_value(data->sensitivity, &data->samples[2], val);
break;
case SENSOR_CHAN_ACCEL_XYZ:
ret = mc3419_to_sensor_value(data->sensitivity, &data->samples[0], &val[0]);
ret |= mc3419_to_sensor_value(data->sensitivity, &data->samples[1], &val[1]);
ret |= mc3419_to_sensor_value(data->sensitivity, &data->samples[2], &val[2]);
break;
default:
LOG_ERR("Unsupported channel");
ret = -ENOTSUP;
}
k_sem_give(&data->sem);
return ret;
}
static int mc3419_set_accel_range(const struct device *dev, uint8_t range)
{
int ret = 0;
const struct mc3419_config *cfg = dev->config;
struct mc3419_driver_data *data = dev->data;
if (range >= MC3419_ACCL_RANGE_END) {
LOG_ERR("Accel resolution is out of range");
return -EINVAL;
}
ret = i2c_reg_update_byte_dt(&cfg->i2c, MC3419_REG_RANGE_SELECT_CTRL,
MC3419_RANGE_MASK, range << 4);
if (ret < 0) {
LOG_ERR("Failed to set resolution (%d)", ret);
return ret;
}
data->sensitivity = (double)(mc3419_accel_sense_map[range] *
SENSOR_GRAIN_VALUE);
return 0;
}
static int mc3419_set_odr(const struct device *dev,
const struct sensor_value *val)
{
int ret = 0;
int data_rate = 0;
const struct mc3419_config *cfg = dev->config;
ret = mc3419_get_odr_value(val->val1, val->val2);
if (ret < 0) {
LOG_ERR("Failed to get odr value from odr map (%d)", ret);
return ret;
}
data_rate = MC3419_BASE_ODR_VAL + ret;
ret = i2c_reg_write_byte_dt(&cfg->i2c, MC3419_REG_SAMPLE_RATE,
data_rate);
if (ret < 0) {
LOG_ERR("Failed to set ODR (%d)", ret);
return ret;
}
LOG_DBG("Set ODR Rate to 0x%x", data_rate);
ret = i2c_reg_write_byte_dt(&cfg->i2c, MC3419_REG_SAMPLE_RATE_2,
CONFIG_MC3419_DECIMATION_RATE);
if (ret < 0) {
LOG_ERR("Failed to set decimation rate (%d)", ret);
return ret;
}
return 0;
}
#if defined(CONFIG_MC3419_TRIGGER)
static int mc3419_set_anymotion_threshold(const struct device *dev,
const struct sensor_value *val)
{
int ret = 0;
const struct mc3419_config *cfg = dev->config;
uint8_t buf[3] = {0};
if (val->val1 > MC3419_ANY_MOTION_THRESH_MAX) {
return -EINVAL;
}
buf[0] = MC3419_REG_ANY_MOTION_THRES;
sys_put_le16((uint16_t)val->val1, &buf[1]);
ret = i2c_write_dt(&cfg->i2c, buf, sizeof(buf));
if (ret < 0) {
LOG_ERR("Failed to set anymotion threshold (%d)", ret);
return ret;
}
return 0;
}
static int mc3419_trigger_set(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
int ret = 0;
const struct mc3419_config *cfg = dev->config;
struct mc3419_driver_data *data = dev->data;
k_sem_take(&data->sem, K_FOREVER);
ret = mc3419_set_op_mode(cfg, MC3419_MODE_STANDBY);
if (ret < 0) {
goto exit;
}
ret = mc3419_configure_trigger(dev, trig, handler);
if (ret < 0) {
LOG_ERR("Failed to set trigger (%d)", ret);
}
exit:
mc3419_set_op_mode(cfg, MC3419_MODE_WAKE);
k_sem_give(&data->sem);
return ret;
}
#endif
static int mc3419_attr_set(const struct device *dev,
enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
int ret = 0;
struct mc3419_driver_data *data = dev->data;
if (chan != SENSOR_CHAN_ACCEL_X &&
chan != SENSOR_CHAN_ACCEL_Y &&
chan != SENSOR_CHAN_ACCEL_Z &&
chan != SENSOR_CHAN_ACCEL_XYZ) {
LOG_ERR("Not supported on this channel.");
return -ENOTSUP;
}
k_sem_take(&data->sem, K_FOREVER);
ret = mc3419_set_op_mode(dev->config, MC3419_MODE_STANDBY);
if (ret < 0) {
goto exit;
}
switch (attr) {
case SENSOR_ATTR_FULL_SCALE:
ret = mc3419_set_accel_range(dev, val->val1);
break;
case SENSOR_ATTR_SAMPLING_FREQUENCY:
ret = mc3419_set_odr(dev, val);
break;
#if defined(CONFIG_MC3419_TRIGGER)
case SENSOR_ATTR_SLOPE_TH:
ret = mc3419_set_anymotion_threshold(dev, val);
break;
#endif
default:
LOG_ERR("ACCEL attribute is not supported");
ret = -EINVAL;
}
exit:
mc3419_set_op_mode(dev->config, MC3419_MODE_WAKE);
k_sem_give(&data->sem);
return ret;
}
static int mc3419_init(const struct device *dev)
{
int ret = 0;
struct mc3419_driver_data *data = dev->data;
const struct mc3419_config *cfg = dev->config;
if (!(i2c_is_ready_dt(&cfg->i2c))) {
LOG_ERR("Bus device is not ready");
return -ENODEV;
}
k_sem_init(&data->sem, 1, 1);
#if defined(CONFIG_MC3419_TRIGGER)
ret = mc3419_trigger_init(dev);
if (ret < 0) {
LOG_ERR("Could not initialize interrupts");
return ret;
}
#endif
/* Leave the sensor in default power on state, will be
* enabled by configure attr or setting trigger.
*/
LOG_INF("MC3419 Initialized");
return ret;
}
static const struct sensor_driver_api mc3419_api = {
.attr_set = mc3419_attr_set,
#if defined(CONFIG_MC3419_TRIGGER)
.trigger_set = mc3419_trigger_set,
#endif
.sample_fetch = mc3419_sample_fetch,
.channel_get = mc3419_channel_get,
};
#if defined(CONFIG_MC3419_TRIGGER)
#define MC3419_CFG_IRQ(idx) \
.int_gpio = GPIO_DT_SPEC_INST_GET_OR(idx, int_gpios, { 0 }), \
.int_cfg = DT_INST_PROP(idx, int_pin2),
#else
#define MC3419_CFG_IRQ(idx)
#endif
#define MC3419_DEFINE(idx) \
static const struct mc3419_config mc3419_config_##idx = { \
.i2c = I2C_DT_SPEC_INST_GET(idx), \
MC3419_CFG_IRQ(idx) \
}; \
static struct mc3419_driver_data mc3419_data_##idx; \
SENSOR_DEVICE_DT_INST_DEFINE(idx, \
mc3419_init, NULL, \
&mc3419_data_##idx, \
&mc3419_config_##idx, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&mc3419_api);
DT_INST_FOREACH_STATUS_OKAY(MC3419_DEFINE)

View file

@ -0,0 +1,102 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2023 Linumiz
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_MC3419_H_
#define ZEPHYR_DRIVERS_SENSOR_MC3419_H_
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
/* Registers */
#define MC3419_REG_INT_CTRL 0x06
#define MC3419_REG_OP_MODE 0x07
#define MC3419_REG_SAMPLE_RATE 0x08
#define MC3419_REG_MOTION_CTRL 0x09
#define MC3419_REG_XOUT_L 0x0D
#define MC3419_REG_YOUT_L 0x0F
#define MC3419_REG_ZOUT_L 0x11
#define MC3419_REG_STATUS 0x13
#define MC3419_REG_INT_STATUS 0x14
#define MC3419_REG_RANGE_SELECT_CTRL 0x20
#define MC3419_REG_SAMPLE_RATE_2 0x30
#define MC3419_REG_COMM_CTRL 0x31
#define MC3419_REG_ANY_MOTION_THRES 0x43
#define MC3419_RANGE_MASK GENMASK(6, 4)
#define MC3419_DATA_READY_MASK BIT(7)
#define MC3419_ANY_MOTION_MASK BIT(2)
#define MC3419_INT_CLEAR 0x00
#define MC3419_INT_ROUTE 0x10
#define MC3419_ANY_MOTION_THRESH_MAX 0x7FFF
#define MC3419_SAMPLE_SIZE 3
#define MC3419_SAMPLE_READ_SIZE (MC3419_SAMPLE_SIZE * (sizeof(int16_t)))
#define SENSOR_GRAIN_VALUE (61LL / 1000.0)
#define SENSOR_GRAVITY_DOUBLE (SENSOR_G / 1000000.0)
#define MC3419_BASE_ODR_VAL 0x10
#define MC3419_TRIG_DATA_READY 0
#define MC3419_TRIG_ANY_MOTION 1
#define MC3419_TRIG_SIZE 2
enum mc3419_op_mode {
MC3419_MODE_STANDBY = 0x00,
MC3419_MODE_WAKE = 0x01
};
struct mc3419_odr_map {
int16_t freq;
int16_t mfreq;
};
enum mc3419_accl_range {
MC3419_ACCl_RANGE_2G,
MC3419_ACCl_RANGE_4G,
MC3419_ACCl_RANGE_8G,
MC3419_ACCl_RANGE_16G,
MC3419_ACCl_RANGE_12G,
MC3419_ACCL_RANGE_END
};
struct mc3419_config {
struct i2c_dt_spec i2c;
#if defined(CONFIG_MC3419_TRIGGER)
struct gpio_dt_spec int_gpio;
bool int_cfg;
#endif
};
struct mc3419_driver_data {
double sensitivity;
struct k_sem sem;
int16_t samples[MC3419_SAMPLE_SIZE];
#if defined(CONFIG_MC3419_TRIGGER)
const struct device *gpio_dev;
struct gpio_callback gpio_cb;
sensor_trigger_handler_t handler[MC3419_TRIG_SIZE];
const struct sensor_trigger *trigger[MC3419_TRIG_SIZE];
#if defined(CONFIG_MC3419_TRIGGER_OWN_THREAD)
K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_MC3419_THREAD_STACK_SIZE);
struct k_thread thread;
struct k_sem trig_sem;
#else
struct k_work work;
#endif
#endif
};
#if defined(CONFIG_MC3419_TRIGGER)
int mc3419_trigger_init(const struct device *dev);
int mc3419_configure_trigger(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
#endif
#endif

View file

@ -0,0 +1,178 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2023 Linumiz
*/
#include "mc3419.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(MC3419, CONFIG_SENSOR_LOG_LEVEL);
static void mc3419_gpio_callback(const struct device *dev,
struct gpio_callback *cb,
uint32_t pin_mask)
{
struct mc3419_driver_data *data = CONTAINER_OF(cb,
struct mc3419_driver_data, gpio_cb);
const struct mc3419_config *cfg = data->gpio_dev->config;
if ((pin_mask & BIT(cfg->int_gpio.pin)) == 0U) {
return;
}
#if defined(CONFIG_MC3419_TRIGGER_OWN_THREAD)
k_sem_give(&data->trig_sem);
#else
k_work_submit(&data->work);
#endif
}
static void mc3419_process_int(const struct device *dev)
{
int ret = 0;
const struct mc3419_config *cfg = dev->config;
const struct mc3419_driver_data *data = dev->data;
uint8_t int_source = 0;
ret = i2c_reg_read_byte_dt(&cfg->i2c, MC3419_REG_INT_STATUS, &int_source);
if (ret < 0) {
goto exit;
}
if (int_source & MC3419_DATA_READY_MASK) {
if (data->handler[MC3419_TRIG_DATA_READY]) {
data->handler[MC3419_TRIG_DATA_READY](dev,
data->trigger[MC3419_TRIG_DATA_READY]);
}
}
if (int_source & MC3419_ANY_MOTION_MASK) {
if (data->handler[MC3419_TRIG_ANY_MOTION]) {
data->handler[MC3419_TRIG_ANY_MOTION](dev,
data->trigger[MC3419_TRIG_ANY_MOTION]);
}
}
exit:
ret = i2c_reg_write_byte_dt(&cfg->i2c, MC3419_REG_INT_STATUS,
MC3419_INT_CLEAR);
if (ret < 0) {
LOG_ERR("Failed to clear interrupt (%d)", ret);
}
}
#if defined(CONFIG_MC3419_TRIGGER_OWN_THREAD)
static void mc3419_thread(struct mc3419_driver_data *data)
{
while (1) {
k_sem_take(&data->trig_sem, K_FOREVER);
mc3419_process_int(data->gpio_dev);
}
}
#else
static void mc3419_work_cb(struct k_work *work)
{
struct mc3419_driver_data *data = CONTAINER_OF(work,
struct mc3419_driver_data, work);
mc3419_process_int(data->gpio_dev);
}
#endif
int mc3419_configure_trigger(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
int ret = 0;
uint8_t buf = 0;
const struct mc3419_config *cfg = dev->config;
struct mc3419_driver_data *data = dev->data;
if (!(trig->type & SENSOR_TRIG_DATA_READY) &&
!(trig->type & SENSOR_TRIG_MOTION)) {
LOG_ERR("Unsupported sensor trigger");
return -ENOTSUP;
}
if (trig->type & SENSOR_TRIG_DATA_READY) {
data->handler[MC3419_TRIG_DATA_READY] = handler;
data->trigger[MC3419_TRIG_DATA_READY] = trig;
buf |= MC3419_DATA_READY_MASK;
}
if (trig->type & SENSOR_TRIG_MOTION) {
uint8_t int_mask = MC3419_ANY_MOTION_MASK;
buf |= MC3419_ANY_MOTION_MASK;
data->handler[MC3419_TRIG_ANY_MOTION] = handler;
data->trigger[MC3419_TRIG_ANY_MOTION] = trig;
ret = i2c_reg_update_byte_dt(&cfg->i2c, MC3419_REG_MOTION_CTRL,
int_mask, handler ? int_mask : 0);
if (ret < 0) {
LOG_ERR("Failed to configure motion interrupt (%d)", ret);
return ret;
}
}
ret = i2c_reg_update_byte_dt(&cfg->i2c, MC3419_REG_INT_CTRL,
buf, buf);
if (ret < 0) {
LOG_ERR("Failed to configure interrupt (%d)", ret);
return ret;
}
gpio_pin_interrupt_configure_dt(&cfg->int_gpio, GPIO_INT_EDGE_FALLING);
return ret;
}
int mc3419_trigger_init(const struct device *dev)
{
int ret = 0;
struct mc3419_driver_data *data = dev->data;
const struct mc3419_config *cfg = dev->config;
if (!gpio_is_ready_dt(&cfg->int_gpio)) {
LOG_ERR("GPIO port %s not ready", cfg->int_gpio.port->name);
return -ENODEV;
}
ret = gpio_pin_configure_dt(&cfg->int_gpio, GPIO_INPUT);
if (ret < 0) {
LOG_ERR("Failed to configure interrupt gpio");
return ret;
}
data->gpio_dev = dev;
#if defined(CONFIG_MC3419_TRIGGER_OWN_THREAD)
k_sem_init(&data->trig_sem, 0, 1);
k_thread_create(&data->thread, data->thread_stack,
CONFIG_MC3419_THREAD_STACK_SIZE,
(k_thread_entry_t)mc3419_thread, data, NULL,
NULL, K_PRIO_COOP(CONFIG_MC3419_THREAD_PRIORITY), 0,
K_NO_WAIT);
#else
k_work_init(&data->work, mc3419_work_cb);
#endif
gpio_init_callback(&data->gpio_cb, mc3419_gpio_callback,
BIT(cfg->int_gpio.pin));
ret = gpio_add_callback(cfg->int_gpio.port, &data->gpio_cb);
if (ret < 0) {
LOG_ERR("Failed to set int callback");
return ret;
}
if (cfg->int_cfg) {
ret = i2c_reg_write_byte_dt(&cfg->i2c, MC3419_REG_COMM_CTRL,
MC3419_INT_ROUTE);
if (ret < 0) {
LOG_ERR("Failed to route the interrupt to INT2 pin (%d)", ret);
return ret;
}
}
return 0;
}

View file

@ -0,0 +1,23 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2023 Linumiz
description: MC3419 3-axis accel sensor
compatible: "memsic,mc3419"
include: [sensor-device.yaml, i2c-device.yaml]
properties:
int-gpios:
type: phandle-array
description: |
This property specifies the connection for INT, this pin
defaults to active low when sample produce interrupt.
int-pin2:
type: boolean
description: |
This property is used for interrupt routing.The sensor
has two interrupt pins.By default the interrupt are routed
to interrupt pin 1, by enabled this property interrupt are
routed to interrupt pin 2.