iio: accel: adxl313: add activity sensing

Add support for configuring an activity detection threshold. Extend the
interrupt handler to process activity-related interrupts, and provide
functions to set the threshold as well as to enable or disable activity
sensing. Additionally, introduce a virtual channel that represents the
logical AND of the x, y, and z axes in the IIO channel.

This patch serves as a preparatory step; some definitions and functions
introduced here are intended to be extended later to support inactivity
detection.

Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://patch.msgid.link/20250702230819.19353-5-l.rubusch@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
Lothar Rubusch
2025-07-02 23:08:15 +00:00
committed by Jonathan Cameron
parent ff8093fa6b
commit 385eb69ee6
+306
View File
@@ -13,8 +13,10 @@
#include <linux/overflow.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/units.h>
#include <linux/iio/buffer.h>
#include <linux/iio/events.h>
#include <linux/iio/kfifo_buf.h>
#include "adxl313.h"
@@ -25,6 +27,21 @@
#define ADXL313_REG_XYZ_BASE ADXL313_REG_DATA_AXIS(0)
#define ADXL313_ACT_XYZ_EN GENMASK(6, 4)
/* activity/inactivity */
enum adxl313_activity_type {
ADXL313_ACTIVITY,
};
static const unsigned int adxl313_act_int_reg[] = {
[ADXL313_ACTIVITY] = ADXL313_INT_ACTIVITY,
};
static const unsigned int adxl313_act_thresh_reg[] = {
[ADXL313_ACTIVITY] = ADXL313_REG_THRESH_ACT,
};
static const struct regmap_range adxl312_readable_reg_range[] = {
regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_DEVID0),
regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
@@ -227,6 +244,15 @@ static const int adxl313_odr_freqs[][2] = {
}, \
}
static const struct iio_event_spec adxl313_activity_events[] = {
{
.type = IIO_EV_TYPE_MAG,
.dir = IIO_EV_DIR_RISING,
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
},
};
enum adxl313_chans {
chan_x, chan_y, chan_z,
};
@@ -235,6 +261,14 @@ static const struct iio_chan_spec adxl313_channels[] = {
ADXL313_ACCEL_CHANNEL(0, chan_x, X),
ADXL313_ACCEL_CHANNEL(1, chan_y, Y),
ADXL313_ACCEL_CHANNEL(2, chan_z, Z),
{
.type = IIO_ACCEL,
.modified = 1,
.channel2 = IIO_MOD_X_OR_Y_OR_Z,
.scan_index = -1, /* Fake channel for axis OR'ing */
.event_spec = adxl313_activity_events,
.num_event_specs = ARRAY_SIZE(adxl313_activity_events),
},
};
static const unsigned long adxl313_scan_masks[] = {
@@ -297,6 +331,81 @@ static int adxl313_read_freq_avail(struct iio_dev *indio_dev,
}
}
static int adxl313_is_act_inact_en(struct adxl313_data *data,
enum adxl313_activity_type type)
{
unsigned int axis_ctrl;
unsigned int regval;
int ret;
ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &axis_ctrl);
if (ret)
return ret;
/* Check if axis for activity are enabled */
switch (type) {
case ADXL313_ACTIVITY:
if (!FIELD_GET(ADXL313_ACT_XYZ_EN, axis_ctrl))
return false;
break;
default:
return -EINVAL;
}
/* Check if specific interrupt is enabled */
ret = regmap_read(data->regmap, ADXL313_REG_INT_ENABLE, &regval);
if (ret)
return ret;
return adxl313_act_int_reg[type] & regval;
}
static int adxl313_set_act_inact_en(struct adxl313_data *data,
enum adxl313_activity_type type,
bool cmd_en)
{
unsigned int axis_ctrl;
unsigned int threshold;
int ret;
if (cmd_en) {
/* When turning on, check if threshold is valid */
ret = regmap_read(data->regmap, adxl313_act_thresh_reg[type],
&threshold);
if (ret)
return ret;
if (!threshold) /* Just ignore the command if threshold is 0 */
return 0;
}
/* Start modifying configuration registers */
ret = adxl313_set_measure_en(data, false);
if (ret)
return ret;
/* Enable axis according to the command */
switch (type) {
case ADXL313_ACTIVITY:
axis_ctrl = ADXL313_ACT_XYZ_EN;
break;
default:
return -EINVAL;
}
ret = regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL,
axis_ctrl, cmd_en);
if (ret)
return ret;
/* Enable the interrupt line, according to the command */
ret = regmap_assign_bits(data->regmap, ADXL313_REG_INT_ENABLE,
adxl313_act_int_reg[type], cmd_en);
if (ret)
return ret;
return adxl313_set_measure_en(data, true);
}
static int adxl313_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -370,6 +479,157 @@ static int adxl313_write_raw(struct iio_dev *indio_dev,
}
}
static int adxl313_read_mag_config(struct adxl313_data *data,
enum iio_event_direction dir,
enum adxl313_activity_type type_act)
{
switch (dir) {
case IIO_EV_DIR_RISING:
return !!adxl313_is_act_inact_en(data, type_act);
default:
return -EINVAL;
}
}
static int adxl313_write_mag_config(struct adxl313_data *data,
enum iio_event_direction dir,
enum adxl313_activity_type type_act,
bool state)
{
switch (dir) {
case IIO_EV_DIR_RISING:
return adxl313_set_act_inact_en(data, type_act, state);
default:
return -EINVAL;
}
}
static int adxl313_read_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir)
{
struct adxl313_data *data = iio_priv(indio_dev);
switch (type) {
case IIO_EV_TYPE_MAG:
return adxl313_read_mag_config(data, dir,
ADXL313_ACTIVITY);
default:
return -EINVAL;
}
}
static int adxl313_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
bool state)
{
struct adxl313_data *data = iio_priv(indio_dev);
switch (type) {
case IIO_EV_TYPE_MAG:
return adxl313_write_mag_config(data, dir,
ADXL313_ACTIVITY,
state);
default:
return -EINVAL;
}
}
static int adxl313_read_mag_value(struct adxl313_data *data,
enum iio_event_direction dir,
enum iio_event_info info,
enum adxl313_activity_type type_act,
int *val, int *val2)
{
unsigned int threshold;
int ret;
switch (info) {
case IIO_EV_INFO_VALUE:
switch (dir) {
case IIO_EV_DIR_RISING:
ret = regmap_read(data->regmap,
adxl313_act_thresh_reg[type_act],
&threshold);
if (ret)
return ret;
*val = threshold * 15625;
*val2 = MICRO;
return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static int adxl313_write_mag_value(struct adxl313_data *data,
enum iio_event_direction dir,
enum iio_event_info info,
enum adxl313_activity_type type_act,
int val, int val2)
{
unsigned int regval;
switch (info) {
case IIO_EV_INFO_VALUE:
/* Scale factor 15.625 mg/LSB */
regval = DIV_ROUND_CLOSEST(MICRO * val + val2, 15625);
switch (dir) {
case IIO_EV_DIR_RISING:
return regmap_write(data->regmap,
adxl313_act_thresh_reg[type_act],
regval);
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static int adxl313_read_event_value(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
enum iio_event_info info,
int *val, int *val2)
{
struct adxl313_data *data = iio_priv(indio_dev);
switch (type) {
case IIO_EV_TYPE_MAG:
return adxl313_read_mag_value(data, dir, info,
ADXL313_ACTIVITY,
val, val2);
default:
return -EINVAL;
}
}
static int adxl313_write_event_value(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
enum iio_event_info info,
int val, int val2)
{
struct adxl313_data *data = iio_priv(indio_dev);
switch (type) {
case IIO_EV_TYPE_MAG:
return adxl313_write_mag_value(data, dir, info,
ADXL313_ACTIVITY,
val, val2);
default:
return -EINVAL;
}
}
static int adxl313_set_watermark(struct iio_dev *indio_dev, unsigned int value)
{
struct adxl313_data *data = iio_priv(indio_dev);
@@ -508,6 +768,25 @@ static int adxl313_fifo_push(struct iio_dev *indio_dev, int samples)
return 0;
}
static int adxl313_push_events(struct iio_dev *indio_dev, int int_stat)
{
s64 ts = iio_get_time_ns(indio_dev);
int ret = -ENOENT;
if (FIELD_GET(ADXL313_INT_ACTIVITY, int_stat)) {
ret = iio_push_event(indio_dev,
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
IIO_MOD_X_OR_Y_OR_Z,
IIO_EV_TYPE_MAG,
IIO_EV_DIR_RISING),
ts);
if (ret)
return ret;
}
return ret;
}
static irqreturn_t adxl313_irq_handler(int irq, void *p)
{
struct iio_dev *indio_dev = p;
@@ -517,6 +796,16 @@ static irqreturn_t adxl313_irq_handler(int irq, void *p)
if (regmap_read(data->regmap, ADXL313_REG_INT_SOURCE, &int_stat))
return IRQ_NONE;
/*
* In cases of sensor events not handled (still not implemented) by
* this driver, the FIFO needs to be drained to become operational
* again. In general the sensor configuration only should issue events
* which were configured by this driver. Anyway a miss-configuration
* easily might end up in a hanging sensor FIFO.
*/
if (adxl313_push_events(indio_dev, int_stat))
goto err_reset_fifo;
if (FIELD_GET(ADXL313_INT_WATERMARK, int_stat)) {
samples = adxl313_get_samples(data);
if (samples < 0)
@@ -550,6 +839,10 @@ static int adxl313_reg_access(struct iio_dev *indio_dev, unsigned int reg,
static const struct iio_info adxl313_info = {
.read_raw = adxl313_read_raw,
.write_raw = adxl313_write_raw,
.read_event_config = adxl313_read_event_config,
.write_event_config = adxl313_write_event_config,
.read_event_value = adxl313_read_event_value,
.write_event_value = adxl313_write_event_value,
.read_avail = adxl313_read_freq_avail,
.hwfifo_set_watermark = adxl313_set_watermark,
.debugfs_reg_access = &adxl313_reg_access,
@@ -687,6 +980,19 @@ int adxl313_core_probe(struct device *dev,
if (ret)
return ret;
/*
* Reset or configure the registers with reasonable default
* values. As having 0 in most cases may result in undesirable
* behavior if the interrupts are enabled.
*/
ret = regmap_write(data->regmap, ADXL313_REG_ACT_INACT_CTL, 0x00);
if (ret)
return ret;
ret = regmap_write(data->regmap, ADXL313_REG_THRESH_ACT, 0x52);
if (ret)
return ret;
ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
&adxl313_buffer_ops);
if (ret)