* [PATCH v7 01/11] iio: accel: adxl345: introduce adxl345_push_event function
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-21 22:06 ` [PATCH v7 02/11] iio: accel: adxl345: add single tap feature Lothar Rubusch
` (10 subsequent siblings)
11 siblings, 0 replies; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Move the fifo handling into a separate function. This is a preparation
for a generic handling of the interrupt status register results. The
function is supposed to handle particular sensor events, and later to
forward them to the iio channel. This is needed to read out the interrupt
status register.
The function shall return occurring errors, if any, or 0 in case of
handled events or read fifo content. Thus migrate fifo read-out and push
fifo content to iio channels into this function to be built up with
additional event handling.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 28 +++++++++++++++++++---------
1 file changed, 19 insertions(+), 9 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 1b4d9e60c87d..a98fb7fc748e 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -416,6 +416,23 @@ static int adxl345_fifo_push(struct iio_dev *indio_dev,
return 0;
}
+static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int samples;
+
+ if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
+ samples = adxl345_get_samples(st);
+ if (samples < 0)
+ return -EINVAL;
+
+ if (adxl345_fifo_push(indio_dev, samples) < 0)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/**
* adxl345_irq_handler() - Handle irqs of the ADXL345.
* @irq: The irq being handled.
@@ -428,19 +445,12 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
struct iio_dev *indio_dev = p;
struct adxl345_state *st = iio_priv(indio_dev);
int int_stat;
- int samples;
if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat))
return IRQ_NONE;
- if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
- samples = adxl345_get_samples(st);
- if (samples < 0)
- goto err;
-
- if (adxl345_fifo_push(indio_dev, samples) < 0)
- goto err;
- }
+ if (adxl345_push_event(indio_dev, int_stat))
+ goto err;
if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat))
goto err;
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v7 02/11] iio: accel: adxl345: add single tap feature
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-04-21 22:06 ` [PATCH v7 01/11] iio: accel: adxl345: introduce adxl345_push_event function Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-21 22:06 ` [PATCH v7 03/11] iio: accel: adxl345: add double " Lothar Rubusch
` (9 subsequent siblings)
11 siblings, 0 replies; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add the single tap feature with a threshold in 62.5mg/LSB points and a
scaled duration in us. Keep singletap threshold in regmap cache but
the scaled value of duration in us as member variable.
Both use IIO channels for individual enable of the x/y/z axis. Initializes
threshold and duration with reasonable content. When an interrupt is
caught it will be pushed to the according IIO channel.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 372 ++++++++++++++++++++++++++++++-
1 file changed, 369 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index a98fb7fc748e..ccb25c35ac07 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -8,6 +8,7 @@
*/
#include <linux/bitfield.h>
+#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/property.h>
@@ -17,6 +18,7 @@
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
#include <linux/iio/kfifo_buf.h>
#include "adxl345.h"
@@ -31,6 +33,29 @@
#define ADXL345_INT1 0
#define ADXL345_INT2 1
+#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
+
+#define ADXL345_TAP_Z_EN BIT(0)
+#define ADXL345_TAP_Y_EN BIT(1)
+#define ADXL345_TAP_X_EN BIT(2)
+
+/* single/double tap */
+enum adxl345_tap_type {
+ ADXL345_SINGLE_TAP,
+};
+
+static const unsigned int adxl345_tap_int_reg[] = {
+ [ADXL345_SINGLE_TAP] = ADXL345_INT_SINGLE_TAP,
+};
+
+enum adxl345_tap_time_type {
+ ADXL345_TAP_TIME_DUR,
+};
+
+static const unsigned int adxl345_tap_time_reg[] = {
+ [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
+};
+
struct adxl345_state {
const struct adxl345_chip_info *info;
struct regmap *regmap;
@@ -38,9 +63,23 @@ struct adxl345_state {
int irq;
u8 watermark;
u8 fifo_mode;
+
+ u32 tap_duration_us;
+
__le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
};
+static struct iio_event_spec adxl345_events[] = {
+ {
+ /* single tap */
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_SINGLETAP,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_TIMEOUT),
+ },
+};
+
#define ADXL345_CHANNEL(index, reg, axis) { \
.type = IIO_ACCEL, \
.modified = 1, \
@@ -57,6 +96,8 @@ struct adxl345_state {
.storagebits = 16, \
.endianness = IIO_LE, \
}, \
+ .event_spec = adxl345_events, \
+ .num_event_specs = ARRAY_SIZE(adxl345_events), \
}
enum adxl345_chans {
@@ -113,6 +154,157 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
}
+/* tap */
+
+static int _adxl345_set_tap_int(struct adxl345_state *st,
+ enum adxl345_tap_type type, bool state)
+{
+ unsigned int int_map = 0x00;
+ unsigned int tap_threshold;
+ bool axis_valid;
+ bool singletap_args_valid = false;
+ bool en = false;
+ u32 axis_ctrl;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ axis_valid = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) > 0;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP, &tap_threshold);
+ if (ret)
+ return ret;
+
+ /*
+ * Note: A value of 0 for threshold and/or dur may result in undesirable
+ * behavior if single tap/double tap interrupts are enabled.
+ */
+ singletap_args_valid = tap_threshold > 0 && st->tap_duration_us > 0;
+
+ if (type == ADXL345_SINGLE_TAP)
+ en = axis_valid && singletap_args_valid;
+
+ if (state && en)
+ int_map |= adxl345_tap_int_reg[type];
+
+ return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_tap_int_reg[type], int_map);
+}
+
+static int adxl345_is_tap_en(struct adxl345_state *st,
+ enum iio_modifier axis,
+ enum adxl345_tap_type type, bool *en)
+{
+ unsigned int regval;
+ u32 axis_ctrl;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ /* Verify if axis is enabled for the tap detection. */
+ switch (axis) {
+ case IIO_MOD_X:
+ *en = FIELD_GET(ADXL345_TAP_X_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Y:
+ *en = FIELD_GET(ADXL345_TAP_Y_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Z:
+ *en = FIELD_GET(ADXL345_TAP_Z_EN, axis_ctrl);
+ break;
+ default:
+ *en = false;
+ return -EINVAL;
+ }
+
+ if (*en) {
+ /*
+ * If axis allow for tap detection, verify if the interrupt is
+ * enabled for tap detection.
+ */
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val);
+ if (ret)
+ return ret;
+
+ *en = adxl345_tap_int_reg[type] & regval;
+ }
+
+ return 0;
+}
+
+static int adxl345_set_singletap_en(struct adxl345_state *st,
+ enum iio_modifier axis, bool en)
+{
+ int ret;
+ u32 axis_ctrl;
+
+ switch (axis) {
+ case IIO_MOD_X:
+ axis_ctrl = ADXL345_TAP_X_EN;
+ break;
+ case IIO_MOD_Y:
+ axis_ctrl = ADXL345_TAP_Y_EN;
+ break;
+ case IIO_MOD_Z:
+ axis_ctrl = ADXL345_TAP_Z_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (en)
+ ret = regmap_set_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+ axis_ctrl);
+ else
+ ret = regmap_clear_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+ axis_ctrl);
+ if (ret)
+ return ret;
+
+ return _adxl345_set_tap_int(st, ADXL345_SINGLE_TAP, en);
+}
+
+static int _adxl345_set_tap_time(struct adxl345_state *st,
+ enum adxl345_tap_time_type type, u32 val_us)
+{
+ unsigned int regval;
+
+ switch (type) {
+ case ADXL345_TAP_TIME_DUR:
+ st->tap_duration_us = val_us;
+ break;
+ }
+
+ /*
+ * The scale factor is 1250us / LSB for tap_window_us and tap_latent_us.
+ * For tap_duration_us the scale factor is 625us / LSB.
+ */
+ if (type == ADXL345_TAP_TIME_DUR)
+ regval = DIV_ROUND_CLOSEST(val_us, 625);
+ else
+ regval = DIV_ROUND_CLOSEST(val_us, 1250);
+
+ return regmap_write(st->regmap, adxl345_tap_time_reg[type], regval);
+}
+
+static int adxl345_set_tap_duration(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ /*
+ * Max value is 255 * 625 us = 0.159375 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (val_int || val_fract_us > 159375)
+ return -EINVAL;
+
+ return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_DUR, val_fract_us);
+}
+
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -198,6 +390,131 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
+static int adxl345_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 adxl345_state *st = iio_priv(indio_dev);
+ bool int_en;
+ int ret;
+
+ switch (type) {
+ case IIO_EV_TYPE_GESTURE:
+ switch (dir) {
+ case IIO_EV_DIR_SINGLETAP:
+ ret = adxl345_is_tap_en(st, chan->channel2,
+ ADXL345_SINGLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_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 adxl345_state *st = iio_priv(indio_dev);
+
+ switch (type) {
+ case IIO_EV_TYPE_GESTURE:
+ switch (dir) {
+ case IIO_EV_DIR_SINGLETAP:
+ return adxl345_set_singletap_en(st, chan->channel2, state);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_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 adxl345_state *st = iio_priv(indio_dev);
+ unsigned int tap_threshold;
+ int ret;
+
+ switch (type) {
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /*
+ * The scale factor would be 62.5mg/LSB (i.e. 0xFF = 16g) but
+ * not applied here. In context of this general purpose sensor,
+ * what imports is rather signal intensity than the absolute
+ * measured g value.
+ */
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP,
+ &tap_threshold);
+ if (ret)
+ return ret;
+ *val = sign_extend32(tap_threshold, 7);
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_TIMEOUT:
+ *val = st->tap_duration_us;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_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 adxl345_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ switch (type) {
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP,
+ min(val, 0xFF));
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_INFO_TIMEOUT:
+ ret = adxl345_set_tap_duration(st, val, val2);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return adxl345_set_measure_en(st, true);
+}
+
static int adxl345_reg_access(struct iio_dev *indio_dev, unsigned int reg,
unsigned int writeval, unsigned int *readval)
{
@@ -416,10 +733,23 @@ static int adxl345_fifo_push(struct iio_dev *indio_dev,
return 0;
}
-static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
+static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
+ enum iio_modifier tap_dir)
{
+ s64 ts = iio_get_time_ns(indio_dev);
struct adxl345_state *st = iio_priv(indio_dev);
int samples;
+ int ret = -ENOENT;
+
+ if (FIELD_GET(ADXL345_INT_SINGLE_TAP, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, tap_dir,
+ IIO_EV_TYPE_GESTURE,
+ IIO_EV_DIR_SINGLETAP),
+ ts);
+ if (ret)
+ return ret;
+ }
if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
samples = adxl345_get_samples(st);
@@ -428,9 +758,11 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
if (adxl345_fifo_push(indio_dev, samples) < 0)
return -EINVAL;
+
+ ret = 0;
}
- return 0;
+ return ret;
}
/**
@@ -444,12 +776,33 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
{
struct iio_dev *indio_dev = p;
struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int regval;
+ enum iio_modifier tap_dir = IIO_NO_MOD;
+ u32 axis_ctrl;
int int_stat;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+ if (ret)
+ return IRQ_NONE;
+
+ if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl)) {
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, ®val);
+ if (ret)
+ return IRQ_NONE;
+
+ if (FIELD_GET(ADXL345_TAP_Z_EN, regval))
+ tap_dir = IIO_MOD_Z;
+ else if (FIELD_GET(ADXL345_TAP_Y_EN, regval))
+ tap_dir = IIO_MOD_Y;
+ else if (FIELD_GET(ADXL345_TAP_X_EN, regval))
+ tap_dir = IIO_MOD_X;
+ }
if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat))
return IRQ_NONE;
- if (adxl345_push_event(indio_dev, int_stat))
+ if (adxl345_push_event(indio_dev, int_stat, tap_dir))
goto err;
if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat))
@@ -468,6 +821,10 @@ static const struct iio_info adxl345_info = {
.read_raw = adxl345_read_raw,
.write_raw = adxl345_write_raw,
.write_raw_get_fmt = adxl345_write_raw_get_fmt,
+ .read_event_config = adxl345_read_event_config,
+ .write_event_config = adxl345_write_event_config,
+ .read_event_value = adxl345_read_event_value,
+ .write_event_value = adxl345_write_event_value,
.debugfs_reg_access = &adxl345_reg_access,
.hwfifo_set_watermark = adxl345_set_watermark,
};
@@ -501,6 +858,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
ADXL345_DATA_FORMAT_JUSTIFY |
ADXL345_DATA_FORMAT_FULL_RES |
ADXL345_DATA_FORMAT_SELF_TEST);
+ unsigned int tap_threshold;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
@@ -514,6 +872,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
return -ENODEV;
st->fifo_delay = fifo_delay_default;
+ /* Init with reasonable values */
+ tap_threshold = 48; /* 48 [0x30] -> ~3g */
+ st->tap_duration_us = 16; /* 16 [0x10] -> .010 */
+
indio_dev->name = st->info->name;
indio_dev->info = &adxl345_info;
indio_dev->modes = INDIO_DIRECT_MODE;
@@ -586,6 +948,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold);
+ if (ret)
+ return ret;
+
/* FIFO_STREAM mode is going to be activated later */
ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &adxl345_buffer_ops);
if (ret)
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v7 03/11] iio: accel: adxl345: add double tap feature
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-04-21 22:06 ` [PATCH v7 01/11] iio: accel: adxl345: introduce adxl345_push_event function Lothar Rubusch
2025-04-21 22:06 ` [PATCH v7 02/11] iio: accel: adxl345: add single tap feature Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-21 22:06 ` [PATCH v7 04/11] iio: accel: adxl345: set the tap suppress bit permanently Lothar Rubusch
` (8 subsequent siblings)
11 siblings, 0 replies; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add the double tap feature of the sensor. The interrupt handler needs
to catch and forward the event to the IIO channel. The single tap
implementation now is extended to deal with double tap as well.
Doubletap introduces window and latency times, both in us. Since both
times are scaled, the 8-bit register value is stored in hardware,
where the scaled value in [us] is stored as member variable.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 104 ++++++++++++++++++++++++++++++-
1 file changed, 103 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index ccb25c35ac07..a95f1c218c0c 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -42,17 +42,23 @@
/* single/double tap */
enum adxl345_tap_type {
ADXL345_SINGLE_TAP,
+ ADXL345_DOUBLE_TAP,
};
static const unsigned int adxl345_tap_int_reg[] = {
[ADXL345_SINGLE_TAP] = ADXL345_INT_SINGLE_TAP,
+ [ADXL345_DOUBLE_TAP] = ADXL345_INT_DOUBLE_TAP,
};
enum adxl345_tap_time_type {
+ ADXL345_TAP_TIME_LATENT,
+ ADXL345_TAP_TIME_WINDOW,
ADXL345_TAP_TIME_DUR,
};
static const unsigned int adxl345_tap_time_reg[] = {
+ [ADXL345_TAP_TIME_LATENT] = ADXL345_REG_LATENT,
+ [ADXL345_TAP_TIME_WINDOW] = ADXL345_REG_WINDOW,
[ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
};
@@ -65,6 +71,8 @@ struct adxl345_state {
u8 fifo_mode;
u32 tap_duration_us;
+ u32 tap_latent_us;
+ u32 tap_window_us;
__le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
};
@@ -78,6 +86,14 @@ static struct iio_event_spec adxl345_events[] = {
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_TIMEOUT),
},
+ {
+ /* double tap */
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_DOUBLETAP,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT) |
+ BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
+ },
};
#define ADXL345_CHANNEL(index, reg, axis) { \
@@ -163,6 +179,7 @@ static int _adxl345_set_tap_int(struct adxl345_state *st,
unsigned int tap_threshold;
bool axis_valid;
bool singletap_args_valid = false;
+ bool doubletap_args_valid = false;
bool en = false;
u32 axis_ctrl;
int ret;
@@ -183,8 +200,16 @@ static int _adxl345_set_tap_int(struct adxl345_state *st,
*/
singletap_args_valid = tap_threshold > 0 && st->tap_duration_us > 0;
- if (type == ADXL345_SINGLE_TAP)
+ if (type == ADXL345_SINGLE_TAP) {
en = axis_valid && singletap_args_valid;
+ } else {
+ /* doubletap: Window must be equal or greater than latent! */
+ doubletap_args_valid = st->tap_latent_us > 0 &&
+ st->tap_window_us > 0 &&
+ st->tap_window_us >= st->tap_latent_us;
+
+ en = axis_valid && singletap_args_valid && doubletap_args_valid;
+ }
if (state && en)
int_map |= adxl345_tap_int_reg[type];
@@ -268,12 +293,23 @@ static int adxl345_set_singletap_en(struct adxl345_state *st,
return _adxl345_set_tap_int(st, ADXL345_SINGLE_TAP, en);
}
+static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en)
+{
+ return _adxl345_set_tap_int(st, ADXL345_DOUBLE_TAP, en);
+}
+
static int _adxl345_set_tap_time(struct adxl345_state *st,
enum adxl345_tap_time_type type, u32 val_us)
{
unsigned int regval;
switch (type) {
+ case ADXL345_TAP_TIME_WINDOW:
+ st->tap_window_us = val_us;
+ break;
+ case ADXL345_TAP_TIME_LATENT:
+ st->tap_latent_us = val_us;
+ break;
case ADXL345_TAP_TIME_DUR:
st->tap_duration_us = val_us;
break;
@@ -305,6 +341,34 @@ static int adxl345_set_tap_duration(struct adxl345_state *st, u32 val_int,
return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_DUR, val_fract_us);
}
+static int adxl345_set_tap_window(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ /*
+ * Max value is 255 * 1250 us = 0.318750 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (val_int || val_fract_us > 318750)
+ return -EINVAL;
+
+ return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_WINDOW, val_fract_us);
+}
+
+static int adxl345_set_tap_latent(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ /*
+ * Max value is 255 * 1250 us = 0.318750 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (val_int || val_fract_us > 318750)
+ return -EINVAL;
+
+ return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_LATENT, val_fract_us);
+}
+
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -408,6 +472,12 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
if (ret)
return ret;
return int_en;
+ case IIO_EV_DIR_DOUBLETAP:
+ ret = adxl345_is_tap_en(st, chan->channel2,
+ ADXL345_DOUBLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
default:
return -EINVAL;
}
@@ -429,6 +499,8 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
return adxl345_set_singletap_en(st, chan->channel2, state);
+ case IIO_EV_DIR_DOUBLETAP:
+ return adxl345_set_doubletap_en(st, state);
default:
return -EINVAL;
}
@@ -468,6 +540,14 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
*val = st->tap_duration_us;
*val2 = 1000000;
return IIO_VAL_FRACTIONAL;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ *val = st->tap_window_us;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ *val = st->tap_latent_us;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
@@ -504,6 +584,16 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
if (ret)
return ret;
break;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ ret = adxl345_set_tap_window(st, val, val2);
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ ret = adxl345_set_tap_latent(st, val, val2);
+ if (ret)
+ return ret;
+ break;
default:
return -EINVAL;
}
@@ -751,6 +841,16 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
return ret;
}
+ if (FIELD_GET(ADXL345_INT_DOUBLE_TAP, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, tap_dir,
+ IIO_EV_TYPE_GESTURE,
+ IIO_EV_DIR_DOUBLETAP),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
samples = adxl345_get_samples(st);
if (samples < 0)
@@ -875,6 +975,8 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
/* Init with reasonable values */
tap_threshold = 48; /* 48 [0x30] -> ~3g */
st->tap_duration_us = 16; /* 16 [0x10] -> .010 */
+ st->tap_window_us = 64; /* 64 [0x40] -> .080 */
+ st->tap_latent_us = 16; /* 16 [0x10] -> .020 */
indio_dev->name = st->info->name;
indio_dev->info = &adxl345_info;
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v7 04/11] iio: accel: adxl345: set the tap suppress bit permanently
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (2 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 03/11] iio: accel: adxl345: add double " Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-27 12:21 ` Jonathan Cameron
2025-04-21 22:06 ` [PATCH v7 05/11] iio: accel: adxl345: add freefall feature Lothar Rubusch
` (7 subsequent siblings)
11 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Set the suppress bit feature to the double tap detection, whenever
double tap is enabled. This impedes the suppress bit dangling in any
state, and thus varying in sensitivity for double tap detection.
Any tap event is defined by a rising signal edge above threshold, i.e.
duration time starts counting; and the falling edge under threshold
within duration time, i.e. then the tap event is issued. This means
duration is used individually for each tap event.
For double tap detection after a single tap, a latency time needs to be
specified. Usually tap events, i.e. spikes above and returning below
threshold will be ignored within latency. After latency, the window
time starts counting for a second tap detection which has to happen
within a duration time.
If the suppress bit is not set, spikes within latency time are ignored.
Setting the suppress bit will invalidate the double tap function. The
sensor will thus be able to save the window time for double tap
detection, and follow a more strict definition of what signal qualifies
for a double tap.
In a summary having the suppress bit set, fewer signal spikes will be
considered as double taps. This is an optional add on to double tap,
thus a separate patch.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index a95f1c218c0c..c464c87033fb 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -34,6 +34,8 @@
#define ADXL345_INT2 1
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
+#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
+#define ADXL345_REG_TAP_SUPPRESS BIT(3)
#define ADXL345_TAP_Z_EN BIT(0)
#define ADXL345_TAP_Y_EN BIT(1)
@@ -295,6 +297,18 @@ static int adxl345_set_singletap_en(struct adxl345_state *st,
static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en)
{
+ int ret;
+
+ /*
+ * Generally suppress detection of spikes during the latency period as
+ * double taps here, this is fully optional for double tap detection
+ */
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+ ADXL345_REG_TAP_SUPPRESS_MSK,
+ en ? ADXL345_REG_TAP_SUPPRESS : 0x00);
+ if (ret)
+ return ret;
+
return _adxl345_set_tap_int(st, ADXL345_DOUBLE_TAP, en);
}
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v7 04/11] iio: accel: adxl345: set the tap suppress bit permanently
2025-04-21 22:06 ` [PATCH v7 04/11] iio: accel: adxl345: set the tap suppress bit permanently Lothar Rubusch
@ 2025-04-27 12:21 ` Jonathan Cameron
0 siblings, 0 replies; 32+ messages in thread
From: Jonathan Cameron @ 2025-04-27 12:21 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Mon, 21 Apr 2025 22:06:34 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Set the suppress bit feature to the double tap detection, whenever
> double tap is enabled. This impedes the suppress bit dangling in any
> state, and thus varying in sensitivity for double tap detection.
>
> Any tap event is defined by a rising signal edge above threshold, i.e.
> duration time starts counting; and the falling edge under threshold
> within duration time, i.e. then the tap event is issued. This means
> duration is used individually for each tap event.
>
> For double tap detection after a single tap, a latency time needs to be
> specified. Usually tap events, i.e. spikes above and returning below
> threshold will be ignored within latency. After latency, the window
> time starts counting for a second tap detection which has to happen
> within a duration time.
>
> If the suppress bit is not set, spikes within latency time are ignored.
> Setting the suppress bit will invalidate the double tap function. The
> sensor will thus be able to save the window time for double tap
> detection, and follow a more strict definition of what signal qualifies
> for a double tap.
>
> In a summary having the suppress bit set, fewer signal spikes will be
> considered as double taps. This is an optional add on to double tap,
> thus a separate patch.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
I'm already carrying to this point in the series on my togreg branch.
Jonathan
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH v7 05/11] iio: accel: adxl345: add freefall feature
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (3 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 04/11] iio: accel: adxl345: set the tap suppress bit permanently Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-27 12:37 ` Jonathan Cameron
2025-04-21 22:06 ` [PATCH v7 06/11] iio: accel: adxl345: extend sample frequency adjustments Lothar Rubusch
` (6 subsequent siblings)
11 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add the freefall detection of the sensor together with a threshold and
time parameter. A freefall event is detected if the measuring signal
falls below the threshold.
Introduce a freefall threshold stored in regmap cache, and a freefall
time, having the scaled time value stored as a member variable in the
state instance.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 134 +++++++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index c464c87033fb..f42c861100cd 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -75,6 +75,7 @@ struct adxl345_state {
u32 tap_duration_us;
u32 tap_latent_us;
u32 tap_window_us;
+ u32 ff_time_ms;
__le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
};
@@ -122,10 +123,27 @@ enum adxl345_chans {
chan_x, chan_y, chan_z,
};
+/* free fall */
+static const struct iio_event_spec adxl345_freefall_event_spec = {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+};
+
static const struct iio_chan_spec adxl345_channels[] = {
ADXL345_CHANNEL(0, chan_x, X),
ADXL345_CHANNEL(1, chan_y, Y),
ADXL345_CHANNEL(2, chan_z, Z),
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_AND_Y_AND_Z,
+ .scan_index = -1, /* Fake channel */
+ .event_spec = &adxl345_freefall_event_spec,
+ .num_event_specs = 1,
+ },
};
static const unsigned long adxl345_scan_masks[] = {
@@ -383,6 +401,63 @@ static int adxl345_set_tap_latent(struct adxl345_state *st, u32 val_int,
return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_LATENT, val_fract_us);
}
+/* freefall */
+
+static int adxl345_is_ff_en(struct adxl345_state *st, bool *en)
+{
+ int ret;
+ unsigned int regval;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val);
+ if (ret)
+ return ret;
+
+ *en = FIELD_GET(ADXL345_INT_FREE_FALL, regval) > 0;
+
+ return 0;
+}
+
+static int adxl345_set_ff_en(struct adxl345_state *st, bool cmd_en)
+{
+ unsigned int regval, ff_threshold;
+ bool en;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF, &ff_threshold);
+ if (ret)
+ return ret;
+
+ en = cmd_en && ff_threshold > 0 && st->ff_time_ms > 0;
+
+ regval = en ? ADXL345_INT_FREE_FALL : 0x00;
+
+ return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ ADXL345_INT_FREE_FALL, regval);
+}
+
+static int adxl345_set_ff_time(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ unsigned int regval;
+ int val_ms;
+
+ /*
+ * max value is 255 * 5000 us = 1.275000 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (1000000 * val_int + val_fract_us > 1275000)
+ return -EINVAL;
+
+ val_ms = val_int * 1000 + DIV_ROUND_UP(val_fract_us, 1000);
+ st->ff_time_ms = val_ms;
+
+ regval = DIV_ROUND_CLOSEST(val_ms, 5);
+
+ /* Values between 100ms and 350ms (0x14 to 0x46) are recommended. */
+ return regmap_write(st->regmap, ADXL345_REG_TIME_FF, min(regval, 0xff));
+}
+
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -495,6 +570,11 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG:
+ ret = adxl345_is_ff_en(st, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
default:
return -EINVAL;
}
@@ -518,6 +598,8 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG:
+ return adxl345_set_ff_en(st, state);
default:
return -EINVAL;
}
@@ -532,6 +614,7 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
{
struct adxl345_state *st = iio_priv(indio_dev);
unsigned int tap_threshold;
+ unsigned int ff_threshold;
int ret;
switch (type) {
@@ -565,6 +648,22 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF,
+ &ff_threshold);
+ if (ret)
+ return ret;
+ *val = ff_threshold;
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_PERIOD:
+ *val = st->ff_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -612,6 +711,22 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
return -EINVAL;
}
break;
+ case IIO_EV_TYPE_MAG:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, val);
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_INFO_PERIOD:
+ ret = adxl345_set_ff_time(st, val, val2);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
default:
return -EINVAL;
}
@@ -865,6 +980,17 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
return ret;
}
+ if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
samples = adxl345_get_samples(st);
if (samples < 0)
@@ -973,6 +1099,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
ADXL345_DATA_FORMAT_FULL_RES |
ADXL345_DATA_FORMAT_SELF_TEST);
unsigned int tap_threshold;
+ unsigned int ff_threshold;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
@@ -992,6 +1119,9 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
st->tap_window_us = 64; /* 64 [0x40] -> .080 */
st->tap_latent_us = 16; /* 16 [0x10] -> .020 */
+ ff_threshold = 8; /* 8 [0x08] */
+ st->ff_time_ms = 32; /* 32 [0x20] -> 0.16 */
+
indio_dev->name = st->info->name;
indio_dev->info = &adxl345_info;
indio_dev->modes = INDIO_DIRECT_MODE;
@@ -1068,6 +1198,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, ff_threshold);
+ if (ret)
+ return ret;
+
/* FIFO_STREAM mode is going to be activated later */
ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &adxl345_buffer_ops);
if (ret)
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v7 05/11] iio: accel: adxl345: add freefall feature
2025-04-21 22:06 ` [PATCH v7 05/11] iio: accel: adxl345: add freefall feature Lothar Rubusch
@ 2025-04-27 12:37 ` Jonathan Cameron
0 siblings, 0 replies; 32+ messages in thread
From: Jonathan Cameron @ 2025-04-27 12:37 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Mon, 21 Apr 2025 22:06:35 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Add the freefall detection of the sensor together with a threshold and
> time parameter. A freefall event is detected if the measuring signal
> falls below the threshold.
>
> Introduce a freefall threshold stored in regmap cache, and a freefall
> time, having the scaled time value stored as a member variable in the
> state instance.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
I tweaked as follows to make change in patch 9 simpler.
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 8fcb8ade049b..1f4fc512e05f 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -123,13 +123,15 @@ enum adxl345_chans {
chan_x, chan_y, chan_z,
};
-/* free fall */
-static const struct iio_event_spec adxl345_freefall_event_spec = {
- .type = IIO_EV_TYPE_MAG,
- .dir = IIO_EV_DIR_FALLING,
- .mask_separate = BIT(IIO_EV_INFO_ENABLE),
- .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+static const struct iio_event_spec adxl345_all_event_spec[] = {
+ /* free fall */
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_PERIOD),
+ },
};
static const struct iio_chan_spec adxl345_channels[] = {
@@ -141,8 +143,8 @@ static const struct iio_chan_spec adxl345_channels[] = {
.modified = 1,
.channel2 = IIO_MOD_X_AND_Y_AND_Z,
.scan_index = -1, /* Fake channel */
- .event_spec = &adxl345_freefall_event_spec,
- .num_event_specs = 1,
+ .event_spec = adxl345_all_event_spec,
+ .num_event_specs = ARRAY_SIZE(adxl345_all_event_spec),
},
};
> ---
> drivers/iio/accel/adxl345_core.c | 134 +++++++++++++++++++++++++++++++
> 1 file changed, 134 insertions(+)
>
> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> index c464c87033fb..f42c861100cd 100644
> --- a/drivers/iio/accel/adxl345_core.c
> +++ b/drivers/iio/accel/adxl345_core.c
> @@ -75,6 +75,7 @@ struct adxl345_state {
> u32 tap_duration_us;
> u32 tap_latent_us;
> u32 tap_window_us;
> + u32 ff_time_ms;
>
> __le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
> };
> @@ -122,10 +123,27 @@ enum adxl345_chans {
> chan_x, chan_y, chan_z,
> };
>
> +/* free fall */
> +static const struct iio_event_spec adxl345_freefall_event_spec = {
> + .type = IIO_EV_TYPE_MAG,
> + .dir = IIO_EV_DIR_FALLING,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_PERIOD),
> +};
> +
> static const struct iio_chan_spec adxl345_channels[] = {
> ADXL345_CHANNEL(0, chan_x, X),
> ADXL345_CHANNEL(1, chan_y, Y),
> ADXL345_CHANNEL(2, chan_z, Z),
> + {
> + .type = IIO_ACCEL,
> + .modified = 1,
> + .channel2 = IIO_MOD_X_AND_Y_AND_Z,
> + .scan_index = -1, /* Fake channel */
> + .event_spec = &adxl345_freefall_event_spec,
> + .num_event_specs = 1,
> + },
> };
>
> static const unsigned long adxl345_scan_masks[] = {
> @@ -383,6 +401,63 @@ static int adxl345_set_tap_latent(struct adxl345_state *st, u32 val_int,
> return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_LATENT, val_fract_us);
> }
>
> +/* freefall */
> +
> +static int adxl345_is_ff_en(struct adxl345_state *st, bool *en)
> +{
> + int ret;
> + unsigned int regval;
> +
> + ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val);
> + if (ret)
> + return ret;
> +
> + *en = FIELD_GET(ADXL345_INT_FREE_FALL, regval) > 0;
> +
> + return 0;
> +}
> +
> +static int adxl345_set_ff_en(struct adxl345_state *st, bool cmd_en)
> +{
> + unsigned int regval, ff_threshold;
> + bool en;
> + int ret;
> +
> + ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF, &ff_threshold);
> + if (ret)
> + return ret;
> +
> + en = cmd_en && ff_threshold > 0 && st->ff_time_ms > 0;
> +
> + regval = en ? ADXL345_INT_FREE_FALL : 0x00;
> +
> + return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
> + ADXL345_INT_FREE_FALL, regval);
> +}
> +
> +static int adxl345_set_ff_time(struct adxl345_state *st, u32 val_int,
> + u32 val_fract_us)
> +{
> + unsigned int regval;
> + int val_ms;
> +
> + /*
> + * max value is 255 * 5000 us = 1.275000 seconds
> + *
> + * Note: the scaling is similar to the scaling in the ADXL380
> + */
> + if (1000000 * val_int + val_fract_us > 1275000)
> + return -EINVAL;
> +
> + val_ms = val_int * 1000 + DIV_ROUND_UP(val_fract_us, 1000);
> + st->ff_time_ms = val_ms;
> +
> + regval = DIV_ROUND_CLOSEST(val_ms, 5);
> +
> + /* Values between 100ms and 350ms (0x14 to 0x46) are recommended. */
> + return regmap_write(st->regmap, ADXL345_REG_TIME_FF, min(regval, 0xff));
> +}
> +
> static int adxl345_read_raw(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> int *val, int *val2, long mask)
> @@ -495,6 +570,11 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> default:
> return -EINVAL;
> }
> + case IIO_EV_TYPE_MAG:
> + ret = adxl345_is_ff_en(st, &int_en);
> + if (ret)
> + return ret;
> + return int_en;
> default:
> return -EINVAL;
> }
> @@ -518,6 +598,8 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
> default:
> return -EINVAL;
> }
> + case IIO_EV_TYPE_MAG:
> + return adxl345_set_ff_en(st, state);
> default:
> return -EINVAL;
> }
> @@ -532,6 +614,7 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
> {
> struct adxl345_state *st = iio_priv(indio_dev);
> unsigned int tap_threshold;
> + unsigned int ff_threshold;
> int ret;
>
> switch (type) {
> @@ -565,6 +648,22 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
> default:
> return -EINVAL;
> }
> + case IIO_EV_TYPE_MAG:
> + switch (info) {
> + case IIO_EV_INFO_VALUE:
> + ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF,
> + &ff_threshold);
> + if (ret)
> + return ret;
> + *val = ff_threshold;
> + return IIO_VAL_INT;
> + case IIO_EV_INFO_PERIOD:
> + *val = st->ff_time_ms;
> + *val2 = 1000;
> + return IIO_VAL_FRACTIONAL;
> + default:
> + return -EINVAL;
> + }
> default:
> return -EINVAL;
> }
> @@ -612,6 +711,22 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
> return -EINVAL;
> }
> break;
> + case IIO_EV_TYPE_MAG:
> + switch (info) {
> + case IIO_EV_INFO_VALUE:
> + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, val);
> + if (ret)
> + return ret;
> + break;
> + case IIO_EV_INFO_PERIOD:
> + ret = adxl345_set_ff_time(st, val, val2);
> + if (ret)
> + return ret;
> + break;
> + default:
> + return -EINVAL;
> + }
> + break;
> default:
> return -EINVAL;
> }
> @@ -865,6 +980,17 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
> return ret;
> }
>
> + if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) {
> + ret = iio_push_event(indio_dev,
> + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
> + IIO_MOD_X_AND_Y_AND_Z,
> + IIO_EV_TYPE_MAG,
> + IIO_EV_DIR_FALLING),
> + ts);
> + if (ret)
> + return ret;
> + }
> +
> if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
> samples = adxl345_get_samples(st);
> if (samples < 0)
> @@ -973,6 +1099,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
> ADXL345_DATA_FORMAT_FULL_RES |
> ADXL345_DATA_FORMAT_SELF_TEST);
> unsigned int tap_threshold;
> + unsigned int ff_threshold;
> int ret;
>
> indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> @@ -992,6 +1119,9 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
> st->tap_window_us = 64; /* 64 [0x40] -> .080 */
> st->tap_latent_us = 16; /* 16 [0x10] -> .020 */
>
> + ff_threshold = 8; /* 8 [0x08] */
> + st->ff_time_ms = 32; /* 32 [0x20] -> 0.16 */
> +
> indio_dev->name = st->info->name;
> indio_dev->info = &adxl345_info;
> indio_dev->modes = INDIO_DIRECT_MODE;
> @@ -1068,6 +1198,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
> if (ret)
> return ret;
>
> + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, ff_threshold);
> + if (ret)
> + return ret;
> +
> /* FIFO_STREAM mode is going to be activated later */
> ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &adxl345_buffer_ops);
> if (ret)
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v7 06/11] iio: accel: adxl345: extend sample frequency adjustments
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (4 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 05/11] iio: accel: adxl345: add freefall feature Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-21 22:06 ` [PATCH v7 07/11] iio: accel: adxl345: add g-range configuration Lothar Rubusch
` (5 subsequent siblings)
11 siblings, 0 replies; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Introduce enums and functions to work with the sample frequency
adjustments. Let the sample frequency adjust via IIO and configure
a reasonable default.
Replace the old static sample frequency handling. During adjustment of
bw registers, measuring is disabled and afterwards enabled again.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345.h | 2 +-
drivers/iio/accel/adxl345_core.c | 150 ++++++++++++++++++++++++-------
2 files changed, 118 insertions(+), 34 deletions(-)
diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h
index 7d482dd595fa..6c1f96406136 100644
--- a/drivers/iio/accel/adxl345.h
+++ b/drivers/iio/accel/adxl345.h
@@ -69,7 +69,7 @@
* BW_RATE bits - Bandwidth and output data rate. The default value is
* 0x0A, which translates to a 100 Hz output data rate
*/
-#define ADXL345_BW_RATE GENMASK(3, 0)
+#define ADXL345_BW_RATE_MSK GENMASK(3, 0)
#define ADXL345_BW_LOW_POWER BIT(4)
#define ADXL345_BASE_RATE_NANO_HZ 97656250LL
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index f42c861100cd..7ed7ee3a8358 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -64,6 +64,45 @@ static const unsigned int adxl345_tap_time_reg[] = {
[ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
};
+enum adxl345_odr {
+ ADXL345_ODR_0P10HZ = 0,
+ ADXL345_ODR_0P20HZ,
+ ADXL345_ODR_0P39HZ,
+ ADXL345_ODR_0P78HZ,
+ ADXL345_ODR_1P56HZ,
+ ADXL345_ODR_3P13HZ,
+ ADXL345_ODR_6P25HZ,
+ ADXL345_ODR_12P50HZ,
+ ADXL345_ODR_25HZ,
+ ADXL345_ODR_50HZ,
+ ADXL345_ODR_100HZ,
+ ADXL345_ODR_200HZ,
+ ADXL345_ODR_400HZ,
+ ADXL345_ODR_800HZ,
+ ADXL345_ODR_1600HZ,
+ ADXL345_ODR_3200HZ,
+};
+
+/* Certain features recommend 12.5 Hz - 400 Hz ODR */
+static const int adxl345_odr_tbl[][2] = {
+ [ADXL345_ODR_0P10HZ] = { 0, 97000 },
+ [ADXL345_ODR_0P20HZ] = { 0, 195000 },
+ [ADXL345_ODR_0P39HZ] = { 0, 390000 },
+ [ADXL345_ODR_0P78HZ] = { 0, 781000 },
+ [ADXL345_ODR_1P56HZ] = { 1, 562000 },
+ [ADXL345_ODR_3P13HZ] = { 3, 125000 },
+ [ADXL345_ODR_6P25HZ] = { 6, 250000 },
+ [ADXL345_ODR_12P50HZ] = { 12, 500000 },
+ [ADXL345_ODR_25HZ] = { 25, 0 },
+ [ADXL345_ODR_50HZ] = { 50, 0 },
+ [ADXL345_ODR_100HZ] = { 100, 0 },
+ [ADXL345_ODR_200HZ] = { 200, 0 },
+ [ADXL345_ODR_400HZ] = { 400, 0 },
+ [ADXL345_ODR_800HZ] = { 800, 0 },
+ [ADXL345_ODR_1600HZ] = { 1600, 0 },
+ [ADXL345_ODR_3200HZ] = { 3200, 0 },
+};
+
struct adxl345_state {
const struct adxl345_chip_info *info;
struct regmap *regmap;
@@ -108,6 +147,7 @@ static struct iio_event_spec adxl345_events[] = {
BIT(IIO_CHAN_INFO_CALIBBIAS), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.scan_index = (index), \
.scan_type = { \
.sign = 's', \
@@ -458,14 +498,53 @@ static int adxl345_set_ff_time(struct adxl345_state *st, u32 val_int,
return regmap_write(st->regmap, ADXL345_REG_TIME_FF, min(regval, 0xff));
}
+static int adxl345_find_odr(struct adxl345_state *st, int val,
+ int val2, enum adxl345_odr *odr)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(adxl345_odr_tbl); i++) {
+ if (val == adxl345_odr_tbl[i][0] &&
+ val2 == adxl345_odr_tbl[i][1]) {
+ *odr = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
+{
+ return regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE,
+ ADXL345_BW_RATE_MSK,
+ FIELD_PREP(ADXL345_BW_RATE_MSK, odr));
+}
+
+static int adxl345_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = (int *)adxl345_odr_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(adxl345_odr_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ }
+
+ return -EINVAL;
+}
+
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct adxl345_state *st = iio_priv(indio_dev);
__le16 accel;
- long long samp_freq_nhz;
unsigned int regval;
+ enum adxl345_odr odr;
int ret;
switch (mask) {
@@ -503,12 +582,10 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val);
if (ret)
return ret;
-
- samp_freq_nhz = ADXL345_BASE_RATE_NANO_HZ <<
- (regval & ADXL345_BW_RATE);
- *val = div_s64_rem(samp_freq_nhz, NANOHZ_PER_HZ, val2);
-
- return IIO_VAL_INT_PLUS_NANO;
+ odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval);
+ *val = adxl345_odr_tbl[odr][0];
+ *val2 = adxl345_odr_tbl[odr][1];
+ return IIO_VAL_INT_PLUS_MICRO;
}
return -EINVAL;
@@ -519,7 +596,12 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
int val, int val2, long mask)
{
struct adxl345_state *st = iio_priv(indio_dev);
- s64 n;
+ enum adxl345_odr odr;
+ int ret;
+
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
switch (mask) {
case IIO_CHAN_INFO_CALIBBIAS:
@@ -527,20 +609,26 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
* 8-bit resolution at +/- 2g, that is 4x accel data scale
* factor
*/
- return regmap_write(st->regmap,
- ADXL345_REG_OFS_AXIS(chan->address),
- val / 4);
+ ret = regmap_write(st->regmap,
+ ADXL345_REG_OFS_AXIS(chan->address),
+ val / 4);
+ if (ret)
+ return ret;
+ break;
case IIO_CHAN_INFO_SAMP_FREQ:
- n = div_s64(val * NANOHZ_PER_HZ + val2,
- ADXL345_BASE_RATE_NANO_HZ);
+ ret = adxl345_find_odr(st, val, val2, &odr);
+ if (ret)
+ return ret;
- return regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE,
- ADXL345_BW_RATE,
- clamp_val(ilog2(n), 0,
- ADXL345_BW_RATE));
+ ret = adxl345_set_odr(st, odr);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
}
- return -EINVAL;
+ return adxl345_set_measure_en(st, true);
}
static int adxl345_read_event_config(struct iio_dev *indio_dev,
@@ -769,7 +857,7 @@ static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_CALIBBIAS:
return IIO_VAL_INT;
case IIO_CHAN_INFO_SAMP_FREQ:
- return IIO_VAL_INT_PLUS_NANO;
+ return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
@@ -782,19 +870,6 @@ static void adxl345_powerdown(void *ptr)
adxl345_set_measure_en(st, false);
}
-static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
-"0.09765625 0.1953125 0.390625 0.78125 1.5625 3.125 6.25 12.5 25 50 100 200 400 800 1600 3200"
-);
-
-static struct attribute *adxl345_attrs[] = {
- &iio_const_attr_sampling_frequency_available.dev_attr.attr,
- NULL
-};
-
-static const struct attribute_group adxl345_attrs_group = {
- .attrs = adxl345_attrs,
-};
-
static int adxl345_set_fifo(struct adxl345_state *st)
{
unsigned int intio;
@@ -1057,9 +1132,9 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
}
static const struct iio_info adxl345_info = {
- .attrs = &adxl345_attrs_group,
.read_raw = adxl345_read_raw,
.write_raw = adxl345_write_raw,
+ .read_avail = adxl345_read_avail,
.write_raw_get_fmt = adxl345_write_raw_get_fmt,
.read_event_config = adxl345_read_event_config,
.write_event_config = adxl345_write_event_config,
@@ -1129,6 +1204,15 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
indio_dev->num_channels = ARRAY_SIZE(adxl345_channels);
indio_dev->available_scan_masks = adxl345_scan_masks;
+ /*
+ * Using I2C at 100kHz would limit the maximum ODR to 200Hz, operation
+ * at an output rate above the recommended maximum may result in
+ * undesired behavior.
+ */
+ ret = adxl345_set_odr(st, ADXL345_ODR_200HZ);
+ if (ret)
+ return ret;
+
/* Reset interrupts at start up */
ret = regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
if (ret)
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v7 07/11] iio: accel: adxl345: add g-range configuration
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (5 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 06/11] iio: accel: adxl345: extend sample frequency adjustments Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-27 12:37 ` Jonathan Cameron
2025-04-21 22:06 ` [PATCH v7 08/11] iio: accel: adxl345: add activity event feature Lothar Rubusch
` (4 subsequent siblings)
11 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Introduce a mechanism to be able to configure and work with the available
g-ranges keeping the precision of 13 digits.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 90 ++++++++++++++++++++++++++++++--
1 file changed, 87 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 7ed7ee3a8358..80b5b8402ced 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -83,6 +83,13 @@ enum adxl345_odr {
ADXL345_ODR_3200HZ,
};
+enum adxl345_range {
+ ADXL345_2G_RANGE = 0,
+ ADXL345_4G_RANGE,
+ ADXL345_8G_RANGE,
+ ADXL345_16G_RANGE,
+};
+
/* Certain features recommend 12.5 Hz - 400 Hz ODR */
static const int adxl345_odr_tbl[][2] = {
[ADXL345_ODR_0P10HZ] = { 0, 97000 },
@@ -103,6 +110,33 @@ static const int adxl345_odr_tbl[][2] = {
[ADXL345_ODR_3200HZ] = { 3200, 0 },
};
+/*
+ * Full resolution frequency table:
+ * (g * 2 * 9.80665) / (2^(resolution) - 1)
+ *
+ * resolution := 13 (full)
+ * g := 2|4|8|16
+ *
+ * 2g at 13bit: 0.004789
+ * 4g at 13bit: 0.009578
+ * 8g at 13bit: 0.019156
+ * 16g at 16bit: 0.038312
+ */
+static const int adxl345_fullres_range_tbl[][2] = {
+ [ADXL345_2G_RANGE] = { 0, 4789 },
+ [ADXL345_4G_RANGE] = { 0, 9578 },
+ [ADXL345_8G_RANGE] = { 0, 19156 },
+ [ADXL345_16G_RANGE] = { 0, 38312 },
+};
+
+/* scaling */
+static const int adxl345_range_factor_tbl[] = {
+ [ADXL345_2G_RANGE] = 1,
+ [ADXL345_4G_RANGE] = 2,
+ [ADXL345_8G_RANGE] = 4,
+ [ADXL345_16G_RANGE] = 8,
+};
+
struct adxl345_state {
const struct adxl345_chip_info *info;
struct regmap *regmap;
@@ -147,7 +181,8 @@ static struct iio_event_spec adxl345_events[] = {
BIT(IIO_CHAN_INFO_CALIBBIAS), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
- .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.scan_index = (index), \
.scan_type = { \
.sign = 's', \
@@ -521,12 +556,40 @@ static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
FIELD_PREP(ADXL345_BW_RATE_MSK, odr));
}
+static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
+ enum adxl345_range *range)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(adxl345_fullres_range_tbl); i++) {
+ if (val == adxl345_fullres_range_tbl[i][0] &&
+ val2 == adxl345_fullres_range_tbl[i][1]) {
+ *range = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
+{
+ return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
+ ADXL345_DATA_FORMAT_RANGE,
+ FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
+}
+
static int adxl345_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type,
int *length, long mask)
{
switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (int *)adxl345_fullres_range_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(adxl345_fullres_range_tbl) * 2;
+ return IIO_AVAIL_LIST;
case IIO_CHAN_INFO_SAMP_FREQ:
*vals = (int *)adxl345_odr_tbl;
*type = IIO_VAL_INT_PLUS_MICRO;
@@ -545,6 +608,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
__le16 accel;
unsigned int regval;
enum adxl345_odr odr;
+ enum adxl345_range range;
int ret;
switch (mask) {
@@ -563,8 +627,12 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
*val = sign_extend32(le16_to_cpu(accel), 12);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- *val = 0;
- *val2 = st->info->uscale;
+ ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val);
+ if (ret)
+ return ret;
+ range = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
+ *val = adxl345_fullres_range_tbl[range][0];
+ *val2 = adxl345_fullres_range_tbl[range][1];
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_CALIBBIAS:
ret = regmap_read(st->regmap,
@@ -596,6 +664,7 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
int val, int val2, long mask)
{
struct adxl345_state *st = iio_priv(indio_dev);
+ enum adxl345_range range;
enum adxl345_odr odr;
int ret;
@@ -624,6 +693,15 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
if (ret)
return ret;
break;
+ case IIO_CHAN_INFO_SCALE:
+ ret = adxl345_find_range(st, val, val2, &range);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_range(st, range);
+ if (ret)
+ return ret;
+ break;
default:
return -EINVAL;
}
@@ -856,6 +934,8 @@ static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_CALIBBIAS:
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_SAMP_FREQ:
return IIO_VAL_INT_PLUS_MICRO;
default:
@@ -1213,6 +1293,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ ret = adxl345_set_range(st, ADXL345_16G_RANGE);
+ if (ret)
+ return ret;
+
/* Reset interrupts at start up */
ret = regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
if (ret)
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v7 07/11] iio: accel: adxl345: add g-range configuration
2025-04-21 22:06 ` [PATCH v7 07/11] iio: accel: adxl345: add g-range configuration Lothar Rubusch
@ 2025-04-27 12:37 ` Jonathan Cameron
0 siblings, 0 replies; 32+ messages in thread
From: Jonathan Cameron @ 2025-04-27 12:37 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Mon, 21 Apr 2025 22:06:37 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Introduce a mechanism to be able to configure and work with the available
> g-ranges keeping the precision of 13 digits.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> +
> +/* scaling */
> +static const int adxl345_range_factor_tbl[] = {
> + [ADXL345_2G_RANGE] = 1,
> + [ADXL345_4G_RANGE] = 2,
> + [ADXL345_8G_RANGE] = 4,
> + [ADXL345_16G_RANGE] = 8,
This isn't used for a few more patches... I moved it whilst applying
> +};
> +
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (6 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 07/11] iio: accel: adxl345: add g-range configuration Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-27 12:47 ` Jonathan Cameron
2025-04-21 22:06 ` [PATCH v7 09/11] iio: accel: adxl345: add inactivity feature Lothar Rubusch
` (3 subsequent siblings)
11 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Make the sensor detect and issue interrupts at activity. Activity
events are configured by a threshold stored in regmap cache. Initialize
the activity threshold register to a reasonable default value in probe.
The value is taken from the older ADXL345 input driver, to provide a
similar behavior. Reset the activity/inactivity direction enabling
register in probe. Reset and initialization shall bring the sensor in a
defined initial state to prevent dangling settings when warm restarting
the sensor.
Activity, ODR configuration together with the range setting prepare the
activity/inactivity hystersesis setup, implemented in a follow up patch.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
1 file changed, 214 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 80b5b8402ced..680981609d83 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -36,11 +36,16 @@
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
#define ADXL345_REG_TAP_SUPPRESS BIT(3)
+#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
#define ADXL345_TAP_Z_EN BIT(0)
#define ADXL345_TAP_Y_EN BIT(1)
#define ADXL345_TAP_X_EN BIT(2)
+#define ADXL345_ACT_Z_EN BIT(4)
+#define ADXL345_ACT_Y_EN BIT(5)
+#define ADXL345_ACT_X_EN BIT(6)
+
/* single/double tap */
enum adxl345_tap_type {
ADXL345_SINGLE_TAP,
@@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
[ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
};
+/* activity/inactivity */
+enum adxl345_activity_type {
+ ADXL345_ACTIVITY,
+};
+
+static const unsigned int adxl345_act_int_reg[] = {
+ [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
+};
+
+static const unsigned int adxl345_act_thresh_reg[] = {
+ [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
+};
+
enum adxl345_odr {
ADXL345_ODR_0P10HZ = 0,
ADXL345_ODR_0P20HZ,
@@ -154,6 +172,13 @@ struct adxl345_state {
};
static struct iio_event_spec adxl345_events[] = {
+ {
+ /* activity */
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+ },
{
/* single tap */
.type = IIO_EV_TYPE_GESTURE,
@@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
}
+/* act/inact */
+
+static int adxl345_is_act_inact_en(struct adxl345_state *st,
+ enum iio_modifier axis,
+ enum adxl345_activity_type type, bool *en)
+{
+ unsigned int regval;
+ u32 axis_ctrl;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ if (type == ADXL345_ACTIVITY) {
+ switch (axis) {
+ case IIO_MOD_X:
+ *en = FIELD_GET(ADXL345_ACT_X_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Y:
+ *en = FIELD_GET(ADXL345_ACT_Y_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Z:
+ *en = FIELD_GET(ADXL345_ACT_Z_EN, axis_ctrl);
+ break;
+ default:
+ *en = false;
+ return -EINVAL;
+ }
+ }
+
+ if (*en) {
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val);
+ if (ret)
+ return ret;
+
+ *en = adxl345_act_int_reg[type] & regval;
+ }
+
+ return 0;
+}
+
+static int adxl345_set_act_inact_en(struct adxl345_state *st,
+ enum iio_modifier axis,
+ enum adxl345_activity_type type,
+ bool cmd_en)
+{
+ bool en;
+ unsigned int threshold;
+ u32 axis_ctrl = 0;
+ int ret;
+
+ if (type == ADXL345_ACTIVITY) {
+ switch (axis) {
+ case IIO_MOD_X:
+ axis_ctrl = ADXL345_ACT_X_EN;
+ break;
+ case IIO_MOD_Y:
+ axis_ctrl = ADXL345_ACT_Y_EN;
+ break;
+ case IIO_MOD_Z:
+ axis_ctrl = ADXL345_ACT_Z_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (cmd_en)
+ ret = regmap_set_bits(st->regmap,
+ ADXL345_REG_ACT_INACT_CTRL, axis_ctrl);
+ else
+ ret = regmap_clear_bits(st->regmap,
+ ADXL345_REG_ACT_INACT_CTRL, axis_ctrl);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, adxl345_act_thresh_reg[type], &threshold);
+ if (ret)
+ return ret;
+
+ en = false;
+
+ if (type == ADXL345_ACTIVITY) {
+ en = FIELD_GET(ADXL345_REG_ACT_AXIS_MSK, axis_ctrl) &&
+ threshold;
+ }
+
+ return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_act_int_reg[type],
+ en ? adxl345_act_int_reg[type] : 0);
+}
+
/* tap */
static int _adxl345_set_tap_int(struct adxl345_state *st,
@@ -719,6 +837,18 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
int ret;
switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = adxl345_is_act_inact_en(st, chan->channel2,
+ ADXL345_ACTIVITY,
+ &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ default:
+ return -EINVAL;
+ }
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -755,6 +885,14 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
struct adxl345_state *st = iio_priv(indio_dev);
switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl345_set_act_inact_en(st, chan->channel2,
+ ADXL345_ACTIVITY, state);
+ default:
+ return -EINVAL;
+ }
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -779,11 +917,31 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
int *val, int *val2)
{
struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int act_threshold;
unsigned int tap_threshold;
unsigned int ff_threshold;
int ret;
switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ &act_threshold);
+ if (ret)
+ return ret;
+
+ *val = act_threshold;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -850,6 +1008,25 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
return ret;
switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ val);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -1108,7 +1285,8 @@ static int adxl345_fifo_push(struct iio_dev *indio_dev,
}
static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
- enum iio_modifier tap_dir)
+ enum iio_modifier tap_dir,
+ enum iio_modifier act_dir)
{
s64 ts = iio_get_time_ns(indio_dev);
struct adxl345_state *st = iio_priv(indio_dev);
@@ -1135,6 +1313,16 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
return ret;
}
+ if (FIELD_GET(ADXL345_INT_ACTIVITY, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) {
ret = iio_push_event(indio_dev,
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
@@ -1173,6 +1361,7 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
struct adxl345_state *st = iio_priv(indio_dev);
unsigned int regval;
enum iio_modifier tap_dir = IIO_NO_MOD;
+ enum iio_modifier act_dir = IIO_NO_MOD;
u32 axis_ctrl;
int int_stat;
int ret;
@@ -1181,7 +1370,8 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
if (ret)
return IRQ_NONE;
- if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl)) {
+ if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) ||
+ FIELD_GET(ADXL345_REG_ACT_AXIS_MSK, axis_ctrl)) {
ret = regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, ®val);
if (ret)
return IRQ_NONE;
@@ -1192,12 +1382,19 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
tap_dir = IIO_MOD_Y;
else if (FIELD_GET(ADXL345_TAP_X_EN, regval))
tap_dir = IIO_MOD_X;
+
+ if (FIELD_GET(ADXL345_ACT_Z_EN, regval))
+ act_dir = IIO_MOD_Z;
+ else if (FIELD_GET(ADXL345_ACT_Y_EN, regval))
+ act_dir = IIO_MOD_Y;
+ else if (FIELD_GET(ADXL345_ACT_X_EN, regval))
+ act_dir = IIO_MOD_X;
}
if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat))
return IRQ_NONE;
- if (adxl345_push_event(indio_dev, int_stat, tap_dir))
+ if (adxl345_push_event(indio_dev, int_stat, tap_dir, act_dir))
goto err;
if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat))
@@ -1362,6 +1559,20 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ /*
+ * Initialization with reasonable values to simplify operation
+ * of the sensor. The default values are partly taken from the
+ * older input driver for the ADXL345, and partly based on
+ * recommendations in the datasheet.
+ */
+ ret = regmap_write(st->regmap, ADXL345_REG_ACT_INACT_CTRL, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_ACT, 6);
+ if (ret)
+ return ret;
+
ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold);
if (ret)
return ret;
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-04-21 22:06 ` [PATCH v7 08/11] iio: accel: adxl345: add activity event feature Lothar Rubusch
@ 2025-04-27 12:47 ` Jonathan Cameron
2025-04-30 22:53 ` Lothar Rubusch
0 siblings, 1 reply; 32+ messages in thread
From: Jonathan Cameron @ 2025-04-27 12:47 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Mon, 21 Apr 2025 22:06:38 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Make the sensor detect and issue interrupts at activity. Activity
> events are configured by a threshold stored in regmap cache. Initialize
> the activity threshold register to a reasonable default value in probe.
> The value is taken from the older ADXL345 input driver, to provide a
> similar behavior. Reset the activity/inactivity direction enabling
> register in probe. Reset and initialization shall bring the sensor in a
> defined initial state to prevent dangling settings when warm restarting
> the sensor.
>
> Activity, ODR configuration together with the range setting prepare the
> activity/inactivity hystersesis setup, implemented in a follow up patch.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> ---
> drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> 1 file changed, 214 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> index 80b5b8402ced..680981609d83 100644
> --- a/drivers/iio/accel/adxl345_core.c
> +++ b/drivers/iio/accel/adxl345_core.c
> @@ -36,11 +36,16 @@
> #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
>
> #define ADXL345_TAP_Z_EN BIT(0)
> #define ADXL345_TAP_Y_EN BIT(1)
> #define ADXL345_TAP_X_EN BIT(2)
>
> +#define ADXL345_ACT_Z_EN BIT(4)
> +#define ADXL345_ACT_Y_EN BIT(5)
> +#define ADXL345_ACT_X_EN BIT(6)
> +
> /* single/double tap */
> enum adxl345_tap_type {
> ADXL345_SINGLE_TAP,
> @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> };
>
> +/* activity/inactivity */
> +enum adxl345_activity_type {
> + ADXL345_ACTIVITY,
> +};
> +
> +static const unsigned int adxl345_act_int_reg[] = {
> + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> +};
> +
> +static const unsigned int adxl345_act_thresh_reg[] = {
> + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> +};
> +
> enum adxl345_odr {
> ADXL345_ODR_0P10HZ = 0,
> ADXL345_ODR_0P20HZ,
> @@ -154,6 +172,13 @@ struct adxl345_state {
> };
>
> static struct iio_event_spec adxl345_events[] = {
> + {
> + /* activity */
> + .type = IIO_EV_TYPE_THRESH,
Is this a threshold, or a magnitude? I'd expect an activity detector
to be magnitude as it doesn't care which way up the sensor is.
> + .dir = IIO_EV_DIR_RISING,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> + },
> {
> /* single tap */
> .type = IIO_EV_TYPE_GESTURE,
> @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> }
>
Jonathan
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-04-27 12:47 ` Jonathan Cameron
@ 2025-04-30 22:53 ` Lothar Rubusch
2025-05-04 10:29 ` Jonathan Cameron
0 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-30 22:53 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
Hi Jonathan - Hi IIO list,
Please, find some (many) questions inlined down below. Appologies for
the separate
channels last time and not right away fixing them up as array. I did
not want to make extra work.
On Sun, Apr 27, 2025 at 2:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Mon, 21 Apr 2025 22:06:38 +0000
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > Make the sensor detect and issue interrupts at activity. Activity
> > events are configured by a threshold stored in regmap cache. Initialize
> > the activity threshold register to a reasonable default value in probe.
> > The value is taken from the older ADXL345 input driver, to provide a
> > similar behavior. Reset the activity/inactivity direction enabling
> > register in probe. Reset and initialization shall bring the sensor in a
> > defined initial state to prevent dangling settings when warm restarting
> > the sensor.
> >
> > Activity, ODR configuration together with the range setting prepare the
> > activity/inactivity hystersesis setup, implemented in a follow up patch.
> >
> > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > ---
> > drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> > 1 file changed, 214 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > index 80b5b8402ced..680981609d83 100644
> > --- a/drivers/iio/accel/adxl345_core.c
> > +++ b/drivers/iio/accel/adxl345_core.c
> > @@ -36,11 +36,16 @@
> > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> >
> > #define ADXL345_TAP_Z_EN BIT(0)
> > #define ADXL345_TAP_Y_EN BIT(1)
> > #define ADXL345_TAP_X_EN BIT(2)
> >
> > +#define ADXL345_ACT_Z_EN BIT(4)
> > +#define ADXL345_ACT_Y_EN BIT(5)
> > +#define ADXL345_ACT_X_EN BIT(6)
> > +
> > /* single/double tap */
> > enum adxl345_tap_type {
> > ADXL345_SINGLE_TAP,
> > @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> > [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> > };
> >
> > +/* activity/inactivity */
> > +enum adxl345_activity_type {
> > + ADXL345_ACTIVITY,
> > +};
> > +
> > +static const unsigned int adxl345_act_int_reg[] = {
> > + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> > +};
> > +
> > +static const unsigned int adxl345_act_thresh_reg[] = {
> > + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> > +};
> > +
> > enum adxl345_odr {
> > ADXL345_ODR_0P10HZ = 0,
> > ADXL345_ODR_0P20HZ,
> > @@ -154,6 +172,13 @@ struct adxl345_state {
> > };
> >
> > static struct iio_event_spec adxl345_events[] = {
> > + {
> > + /* activity */
> > + .type = IIO_EV_TYPE_THRESH,
>
> Is this a threshold, or a magnitude? I'd expect an activity detector
> to be magnitude as it doesn't care which way up the sensor is.
>
This is touching the main points still unclear to me. I tried to put
this into the
following questions. Could you please clarify?
1. Given a measurement "val", and a configured threshold "thr".
A "rising" for IIO_EV_TYPE_THRESH means: val > thr
where a "rising" for IIO_EV_TYPE_MAG means something like: val > |thr|
Q: Do I understand this correctly now?
Q: Is this documented somewhere (especially for reviewing further
EV_TYPE fields)?
Q: I wonder if I missed this for the Tap events. Going by this
definition, then actually the
tap events should be rather MAG events, too. Right?
2. I oriented myself mostly by reading other drivers, for instance the
ADXL367, the ADXL372, or also the more recent ADXL380. I am aware that
there might be differences among different
(Analog) sensors. But all those sensors specify Inactivity (and Activity) as a
IIO_EV_TYPE_THRESH with directions IIO_MOD_X_OR_Y_OR_Z.
Given the above, I implemented Activity and Inactivity events as
IIO_EV_TYPE_THRESH,
now I'm a bit confused.
Q: Why is this different for the ADXL345?
Q: If I implement Activity / Inactivity analogous to the e.g. a
ADXL380, then shouldn't it be IIO_EV_TYPE_THRESH with
IIO_MOD_X_OR_Y_OR_Z? Why not?
3. For the ADXL345, a Freefall signal is on all axis lower than
threshold (magnitude). Thus I push a IIO_MOD_X_AND_Y_AND_Z to a
separate
fake channel. Inactivity will be like Freefall independent of the axis.
The ADXL345 Activity can be configured by axis, as also the event will
respect the axis information.
Q: Setting up the "fake channel" to particuarly push to
IIO_MOD_X_AND_Y_AND_Z, I probably better should also evaluate
IIO_MOD_X_AND_Y_AND_Z in write_event_config(), write_event_value(),
etc. rather than evaluating IIO_MOD_-types as I'm currently
doing?
Q: Activity probably remains in the regular channels for the corresponding axis?
4. I implemented functions like adxl345_write_event_config(),
adxl345_write_event_value() or corresponding
readers, as follows
- THRESH/rising: Activity
- THRESH/falling: Inactivity
- MAG/falling: Freefall
If I change Activity and Inactivity to be both of type MAG, I will end
up with MAG/falling to indicate Freefall or equally Inactivity.
Both on the IIO_MOD_X_AND_Y_AND_Z channel. I admit (ab)using the
IIO_EV_TYPEs to solve my combinatorial issues for event configuration
is probably not as supposed to be.
Given you still ask me to do Inactivity and Freefall as MAG/falling
with IIO_MOD_X_AND_Y_AND_Z. The difference between both IMHO,
is that Activity and Inactivity for the ADXL345 indicate sensor state
changes, where Freefall indicates the particular event. The
sensor is either in "active" or "standby/inactive", where Freefall
just triggers and then retriggers and retriggers...
Q: What is the method to distinguish several similar IIO events, e.g.
to tag them somehow one as Freefall, the other one as Inactivity?
Best,
L
> > + .dir = IIO_EV_DIR_RISING,
> > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> > + },
> > {
> > /* single tap */
> > .type = IIO_EV_TYPE_GESTURE,
> > @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> > }
> >
> Jonathan
>
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-04-30 22:53 ` Lothar Rubusch
@ 2025-05-04 10:29 ` Jonathan Cameron
2025-05-04 17:47 ` Lothar Rubusch
0 siblings, 1 reply; 32+ messages in thread
From: Jonathan Cameron @ 2025-05-04 10:29 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 1 May 2025 00:53:32 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Hi Jonathan - Hi IIO list,
>
> Please, find some (many) questions inlined down below. Appologies for
> the separate
> channels last time and not right away fixing them up as array. I did
> not want to make extra work.
>
> On Sun, Apr 27, 2025 at 2:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Mon, 21 Apr 2025 22:06:38 +0000
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > Make the sensor detect and issue interrupts at activity. Activity
> > > events are configured by a threshold stored in regmap cache. Initialize
> > > the activity threshold register to a reasonable default value in probe.
> > > The value is taken from the older ADXL345 input driver, to provide a
> > > similar behavior. Reset the activity/inactivity direction enabling
> > > register in probe. Reset and initialization shall bring the sensor in a
> > > defined initial state to prevent dangling settings when warm restarting
> > > the sensor.
> > >
> > > Activity, ODR configuration together with the range setting prepare the
> > > activity/inactivity hystersesis setup, implemented in a follow up patch.
> > >
> > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > ---
> > > drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> > > 1 file changed, 214 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > index 80b5b8402ced..680981609d83 100644
> > > --- a/drivers/iio/accel/adxl345_core.c
> > > +++ b/drivers/iio/accel/adxl345_core.c
> > > @@ -36,11 +36,16 @@
> > > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > >
> > > #define ADXL345_TAP_Z_EN BIT(0)
> > > #define ADXL345_TAP_Y_EN BIT(1)
> > > #define ADXL345_TAP_X_EN BIT(2)
> > >
> > > +#define ADXL345_ACT_Z_EN BIT(4)
> > > +#define ADXL345_ACT_Y_EN BIT(5)
> > > +#define ADXL345_ACT_X_EN BIT(6)
> > > +
> > > /* single/double tap */
> > > enum adxl345_tap_type {
> > > ADXL345_SINGLE_TAP,
> > > @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> > > [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> > > };
> > >
> > > +/* activity/inactivity */
> > > +enum adxl345_activity_type {
> > > + ADXL345_ACTIVITY,
> > > +};
> > > +
> > > +static const unsigned int adxl345_act_int_reg[] = {
> > > + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> > > +};
> > > +
> > > +static const unsigned int adxl345_act_thresh_reg[] = {
> > > + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> > > +};
> > > +
> > > enum adxl345_odr {
> > > ADXL345_ODR_0P10HZ = 0,
> > > ADXL345_ODR_0P20HZ,
> > > @@ -154,6 +172,13 @@ struct adxl345_state {
> > > };
> > >
> > > static struct iio_event_spec adxl345_events[] = {
> > > + {
> > > + /* activity */
> > > + .type = IIO_EV_TYPE_THRESH,
> >
> > Is this a threshold, or a magnitude? I'd expect an activity detector
> > to be magnitude as it doesn't care which way up the sensor is.
> >
>
> This is touching the main points still unclear to me. I tried to put
> this into the
> following questions. Could you please clarify?
There are some corners where it gets messy. When I have time
(not for a month or so) I'll try and write some proper docs for this.
>
> 1. Given a measurement "val", and a configured threshold "thr".
> A "rising" for IIO_EV_TYPE_THRESH means: val > thr
> where a "rising" for IIO_EV_TYPE_MAG means something like: val > |thr|
>
> Q: Do I understand this correctly now?
Yes that is the intended difference.
>
> Q: Is this documented somewhere (especially for reviewing further
> EV_TYPE fields)?
Only in the ABI documentation in
Documentation/ABI/testing/sysfs-bus-iio
This is definitely something we should look to improve with some
docs beyond simply what the ABI is. That ABI is focused on
how the interrupt is triggered, not so much on what that means
wrt to freefall etc.
>
> Q: I wonder if I missed this for the Tap events. Going by this
> definition, then actually the
> tap events should be rather MAG events, too. Right?
The tap events have their own type (gesture) because they are way
more complex than a simple threshold whether on magnitude or
the signed value. So those should be fine as type GESTURE.
>
>
> 2. I oriented myself mostly by reading other drivers, for instance the
> ADXL367, the ADXL372, or also the more recent ADXL380. I am aware that
> there might be differences among different
> (Analog) sensors. But all those sensors specify Inactivity (and Activity) as a
> IIO_EV_TYPE_THRESH with directions IIO_MOD_X_OR_Y_OR_Z.
> Given the above, I implemented Activity and Inactivity events as
> IIO_EV_TYPE_THRESH,
> now I'm a bit confused.
Hmm. This is one reason I think we need more documentation as those
seem to be wrong. Clearly the event is a threshold on a magnitude of
the acceleration, not the signed value as it applies in both directions.
>
> Q: Why is this different for the ADXL345?
Because we got it wrong for these others it seems unless they genuinely
have directional events - which typically means separate positive and
negative thresholds. Right now those events are strictly speaking
only apply to positive accelerations.
>
> Q: If I implement Activity / Inactivity analogous to the e.g. a
> ADXL380, then shouldn't it be IIO_EV_TYPE_THRESH with
> IIO_MOD_X_OR_Y_OR_Z? Why not?
>
I think we got it wrong for that part. Going forwards we should work
on getting it (more) correct.
>
> 3. For the ADXL345, a Freefall signal is on all axis lower than
> threshold (magnitude). Thus I push a IIO_MOD_X_AND_Y_AND_Z to a
> separate
> fake channel. Inactivity will be like Freefall independent of the axis.
> The ADXL345 Activity can be configured by axis, as also the event will
> respect the axis information.
>
> Q: Setting up the "fake channel" to particuarly push to
> IIO_MOD_X_AND_Y_AND_Z, I probably better should also evaluate
> IIO_MOD_X_AND_Y_AND_Z in write_event_config(), write_event_value(),
> etc. rather than evaluating IIO_MOD_-types as I'm currently
> doing?
Yes. That sounds correct for events on these 'fake' channels.
The enable and the thresholds should all be on these fake channels
assuming they don't have different thresholds on a per axis basis
(if they do things get tricky to represent).
>
> Q: Activity probably remains in the regular channels for the corresponding axis?
Yes. That is easier to handle as OR of channels is very similar
to separate interrupts etc.
>
>
> 4. I implemented functions like adxl345_write_event_config(),
> adxl345_write_event_value() or corresponding
> readers, as follows
> - THRESH/rising: Activity
> - THRESH/falling: Inactivity
> - MAG/falling: Freefall
>
> If I change Activity and Inactivity to be both of type MAG, I will end
> up with MAG/falling to indicate Freefall or equally Inactivity.
> Both on the IIO_MOD_X_AND_Y_AND_Z channel. I admit (ab)using the
> IIO_EV_TYPEs to solve my combinatorial issues for event configuration
> is probably not as supposed to be.
Ah.. This I'd missed. I'm fairly sure we didn't hit this for (some) previous
inactivity sensors because they were always rate of change based, (AC)
rather than DC. DC is relatively unlikely to be used in practice because
we can't set the threshold as less than 1G because of gravity. It is a
bit odd that the device supports both DC and AC on this detector.
I wonder why.... Might be to enable partial axis monitoring. e.g.
If a device is flat on a table we only look for inactivity on the non
vertical axis when doing DC coupling. (as we have 1g always in the other
axis).
> Given you still ask me to do Inactivity and Freefall as MAG/falling
> with IIO_MOD_X_AND_Y_AND_Z. The difference between both IMHO,
> is that Activity and Inactivity for the ADXL345 indicate sensor state
> changes, where Freefall indicates the particular event. The
> sensor is either in "active" or "standby/inactive", where Freefall
> just triggers and then retriggers and retriggers...
Maybe. The datasheet is annoyingly vague on these but indeed there
is no event for no longer falling.
>
> Q: What is the method to distinguish several similar IIO events, e.g.
> to tag them somehow one as Freefall, the other one as Inactivity?
In general we should not be able to do that. Long ago we made the decision
to have compact event codes so they don't allow for an index on a particular
combination of channel number and modifier. This is mainly because
there is limited purpose. If one event is triggered, then we have
to process anyway so we can just look at the value for 'how far' it was
triggered. I.e. if we thought DC inactivity was triggered, we can just
check free fall as well. (It gets a little more fiddly because of _period
etc which is why they may actually make sense here).
The virtual (combination OR/AND) was added on top of that later and has
made the connection looser.
In theory we could use labels + index for the virtual channels to achieve
separate control attributes and be able to tell which was which but
that would be new ABI. I'm not sure how much use this stuff is already
getting from userspace applications and hence whether this would be
a big problem to get supported.
That would give us something like
iio\:device0/in_accel0_x&y&z_label freefall
iio\:device0/in_accel1_x&y&z_label inactivity
iio\:device0/events/in_accel0_x&y&z_en etc
iio\:device0/events/in_accel1_x&y&z_en etc
I don't like it much because it then doesn't generalize to the case
of multiple sensors on each axis (there are multi range parts that do that).
That case is pretty rare though (I think we only have such sensor supported!)
However, it's currently the only option we have to fully represent this.
An alternative here might be to assess if anyone is really going to use
DC coupled inactivity detection (because of the 1g problem) and hence whether
we want to support that at all?
Yet another alternative might be to configure it purely based on the period
provided. If short use freefall, if long use inactivity. (I don't like this
one though as it doesn't really fit with usecase!)
Sorry for lack of clarity on this. These events are tricky and
it takes me a while to get the whole situation back into my head (and I missing
things like inactivity and freefall being very similar here!)
If you have time to take a look at what userspace is currently doing with
these events (iio_sensor_proxy etc) that might help us decide what works.
Jonathan
>
> Best,
> L
>
> > > + .dir = IIO_EV_DIR_RISING,
> > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> > > + },
> > > {
> > > /* single tap */
> > > .type = IIO_EV_TYPE_GESTURE,
> > > @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> > > }
> > >
> > Jonathan
> >
> >
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-05-04 10:29 ` Jonathan Cameron
@ 2025-05-04 17:47 ` Lothar Rubusch
2025-05-05 12:37 ` Jonathan Cameron
0 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-05-04 17:47 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Sun, May 4, 2025 at 12:29 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Thu, 1 May 2025 00:53:32 +0200
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > Hi Jonathan - Hi IIO list,
> >
> > Please, find some (many) questions inlined down below. Appologies for
> > the separate
> > channels last time and not right away fixing them up as array. I did
> > not want to make extra work.
> >
> > On Sun, Apr 27, 2025 at 2:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > >
> > > On Mon, 21 Apr 2025 22:06:38 +0000
> > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > >
> > > > Make the sensor detect and issue interrupts at activity. Activity
> > > > events are configured by a threshold stored in regmap cache. Initialize
> > > > the activity threshold register to a reasonable default value in probe.
> > > > The value is taken from the older ADXL345 input driver, to provide a
> > > > similar behavior. Reset the activity/inactivity direction enabling
> > > > register in probe. Reset and initialization shall bring the sensor in a
> > > > defined initial state to prevent dangling settings when warm restarting
> > > > the sensor.
> > > >
> > > > Activity, ODR configuration together with the range setting prepare the
> > > > activity/inactivity hystersesis setup, implemented in a follow up patch.
> > > >
> > > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > > ---
> > > > drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> > > > 1 file changed, 214 insertions(+), 3 deletions(-)
> > > >
> > > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > > index 80b5b8402ced..680981609d83 100644
> > > > --- a/drivers/iio/accel/adxl345_core.c
> > > > +++ b/drivers/iio/accel/adxl345_core.c
> > > > @@ -36,11 +36,16 @@
> > > > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > > +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > >
> > > > #define ADXL345_TAP_Z_EN BIT(0)
> > > > #define ADXL345_TAP_Y_EN BIT(1)
> > > > #define ADXL345_TAP_X_EN BIT(2)
> > > >
> > > > +#define ADXL345_ACT_Z_EN BIT(4)
> > > > +#define ADXL345_ACT_Y_EN BIT(5)
> > > > +#define ADXL345_ACT_X_EN BIT(6)
> > > > +
> > > > /* single/double tap */
> > > > enum adxl345_tap_type {
> > > > ADXL345_SINGLE_TAP,
> > > > @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> > > > [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> > > > };
> > > >
> > > > +/* activity/inactivity */
> > > > +enum adxl345_activity_type {
> > > > + ADXL345_ACTIVITY,
> > > > +};
> > > > +
> > > > +static const unsigned int adxl345_act_int_reg[] = {
> > > > + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> > > > +};
> > > > +
> > > > +static const unsigned int adxl345_act_thresh_reg[] = {
> > > > + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> > > > +};
> > > > +
> > > > enum adxl345_odr {
> > > > ADXL345_ODR_0P10HZ = 0,
> > > > ADXL345_ODR_0P20HZ,
> > > > @@ -154,6 +172,13 @@ struct adxl345_state {
> > > > };
> > > >
> > > > static struct iio_event_spec adxl345_events[] = {
> > > > + {
> > > > + /* activity */
> > > > + .type = IIO_EV_TYPE_THRESH,
> > >
> > > Is this a threshold, or a magnitude? I'd expect an activity detector
> > > to be magnitude as it doesn't care which way up the sensor is.
> > >
> >
> > This is touching the main points still unclear to me. I tried to put
> > this into the
> > following questions. Could you please clarify?
>
> There are some corners where it gets messy. When I have time
> (not for a month or so) I'll try and write some proper docs for this.
>
> >
> > 1. Given a measurement "val", and a configured threshold "thr".
> > A "rising" for IIO_EV_TYPE_THRESH means: val > thr
> > where a "rising" for IIO_EV_TYPE_MAG means something like: val > |thr|
> >
> > Q: Do I understand this correctly now?
>
> Yes that is the intended difference.
>
> >
> > Q: Is this documented somewhere (especially for reviewing further
> > EV_TYPE fields)?
>
> Only in the ABI documentation in
> Documentation/ABI/testing/sysfs-bus-iio
> This is definitely something we should look to improve with some
> docs beyond simply what the ABI is. That ABI is focused on
> how the interrupt is triggered, not so much on what that means
> wrt to freefall etc.
>
>
> >
> > Q: I wonder if I missed this for the Tap events. Going by this
> > definition, then actually the
> > tap events should be rather MAG events, too. Right?
>
> The tap events have their own type (gesture) because they are way
> more complex than a simple threshold whether on magnitude or
> the signed value. So those should be fine as type GESTURE.
>
I was aware of that. Actually, the case of GESTURE is a bit
particular. On the one side, I
understand having a distinction between THRESH events and MAG events.
Hence, a classification of the type of event in terms of a measurement
value triggering event condition.
This concept seems actually to be clear.
GESTURE to me then seems a bit like a "wildcard type covering all kind
of tap events". I mean,
saying tap detection, single tap, double tap, tripple tap, and so on
tap go into category GESTURE - naively could also mean, then do a
freefall type as well (?).
[this is rather meant as a bit of a provocative rhetoric question than
a proposal]
> >
> >
> > 2. I oriented myself mostly by reading other drivers, for instance the
> > ADXL367, the ADXL372, or also the more recent ADXL380. I am aware that
> > there might be differences among different
> > (Analog) sensors. But all those sensors specify Inactivity (and Activity) as a
> > IIO_EV_TYPE_THRESH with directions IIO_MOD_X_OR_Y_OR_Z.
> > Given the above, I implemented Activity and Inactivity events as
> > IIO_EV_TYPE_THRESH,
> > now I'm a bit confused.
>
> Hmm. This is one reason I think we need more documentation as those
> seem to be wrong. Clearly the event is a threshold on a magnitude of
> the acceleration, not the signed value as it applies in both directions.
>
> >
> > Q: Why is this different for the ADXL345?
>
> Because we got it wrong for these others it seems unless they genuinely
> have directional events - which typically means separate positive and
> negative thresholds. Right now those events are strictly speaking
> only apply to positive accelerations.
>
> >
> > Q: If I implement Activity / Inactivity analogous to the e.g. a
> > ADXL380, then shouldn't it be IIO_EV_TYPE_THRESH with
> > IIO_MOD_X_OR_Y_OR_Z? Why not?
> >
>
> I think we got it wrong for that part. Going forwards we should work
> on getting it (more) correct.
>
I understand the point better now.
> >
> > 3. For the ADXL345, a Freefall signal is on all axis lower than
> > threshold (magnitude). Thus I push a IIO_MOD_X_AND_Y_AND_Z to a
> > separate
> > fake channel. Inactivity will be like Freefall independent of the axis.
> > The ADXL345 Activity can be configured by axis, as also the event will
> > respect the axis information.
> >
> > Q: Setting up the "fake channel" to particuarly push to
> > IIO_MOD_X_AND_Y_AND_Z, I probably better should also evaluate
> > IIO_MOD_X_AND_Y_AND_Z in write_event_config(), write_event_value(),
> > etc. rather than evaluating IIO_MOD_-types as I'm currently
> > doing?
>
> Yes. That sounds correct for events on these 'fake' channels.
> The enable and the thresholds should all be on these fake channels
> assuming they don't have different thresholds on a per axis basis
> (if they do things get tricky to represent).
>
> >
> > Q: Activity probably remains in the regular channels for the corresponding axis?
>
> Yes. That is easier to handle as OR of channels is very similar
> to separate interrupts etc.
>
I think I should definitely evaluate the IIO_MOD_X_AND_Y_AND_Z here,
to take advantage
of the fake channel.
> >
> >
> > 4. I implemented functions like adxl345_write_event_config(),
> > adxl345_write_event_value() or corresponding
> > readers, as follows
> > - THRESH/rising: Activity
> > - THRESH/falling: Inactivity
> > - MAG/falling: Freefall
> >
> > If I change Activity and Inactivity to be both of type MAG, I will end
> > up with MAG/falling to indicate Freefall or equally Inactivity.
> > Both on the IIO_MOD_X_AND_Y_AND_Z channel. I admit (ab)using the
> > IIO_EV_TYPEs to solve my combinatorial issues for event configuration
> > is probably not as supposed to be.
>
> Ah.. This I'd missed. I'm fairly sure we didn't hit this for (some) previous
> inactivity sensors because they were always rate of change based, (AC)
> rather than DC. DC is relatively unlikely to be used in practice because
> we can't set the threshold as less than 1G because of gravity. It is a
> bit odd that the device supports both DC and AC on this detector.
>
> I wonder why.... Might be to enable partial axis monitoring. e.g.
> If a device is flat on a table we only look for inactivity on the non
> vertical axis when doing DC coupling. (as we have 1g always in the other
> axis).
>
Thank you for clarifying your position in the other mail focussed on
the AC- / DC-coupling
topic. It helped me in better understanding what you actually expect
here. Although I'll
probably need to re-read it some times, before implementing something.
> > Given you still ask me to do Inactivity and Freefall as MAG/falling
> > with IIO_MOD_X_AND_Y_AND_Z. The difference between both IMHO,
> > is that Activity and Inactivity for the ADXL345 indicate sensor state
> > changes, where Freefall indicates the particular event. The
> > sensor is either in "active" or "standby/inactive", where Freefall
> > just triggers and then retriggers and retriggers...
>
> Maybe. The datasheet is annoyingly vague on these but indeed there
> is no event for no longer falling.
>
> >
> > Q: What is the method to distinguish several similar IIO events, e.g.
> > to tag them somehow one as Freefall, the other one as Inactivity?
>
> In general we should not be able to do that. Long ago we made the decision
> to have compact event codes so they don't allow for an index on a particular
> combination of channel number and modifier. This is mainly because
> there is limited purpose. If one event is triggered, then we have
> to process anyway so we can just look at the value for 'how far' it was
> triggered. I.e. if we thought DC inactivity was triggered, we can just
> check free fall as well. (It gets a little more fiddly because of _period
> etc which is why they may actually make sense here).
>
> The virtual (combination OR/AND) was added on top of that later and has
> made the connection looser.
>
> In theory we could use labels + index for the virtual channels to achieve
> separate control attributes and be able to tell which was which but
> that would be new ABI. I'm not sure how much use this stuff is already
> getting from userspace applications and hence whether this would be
> a big problem to get supported.
>
> That would give us something like
>
> iio\:device0/in_accel0_x&y&z_label freefall
> iio\:device0/in_accel1_x&y&z_label inactivity
> iio\:device0/events/in_accel0_x&y&z_en etc
> iio\:device0/events/in_accel1_x&y&z_en etc
>
> I don't like it much because it then doesn't generalize to the case
> of multiple sensors on each axis (there are multi range parts that do that).
> That case is pretty rare though (I think we only have such sensor supported!)
> However, it's currently the only option we have to fully represent this.
>
> An alternative here might be to assess if anyone is really going to use
> DC coupled inactivity detection (because of the 1g problem) and hence whether
> we want to support that at all?
>
> Yet another alternative might be to configure it purely based on the period
> provided. If short use freefall, if long use inactivity. (I don't like this
> one though as it doesn't really fit with usecase!)
>
> Sorry for lack of clarity on this. These events are tricky and
> it takes me a while to get the whole situation back into my head (and I missing
> things like inactivity and freefall being very similar here!)
>
> If you have time to take a look at what userspace is currently doing with
> these events (iio_sensor_proxy etc) that might help us decide what works.
>
Just as a quick response here (or perhaps just to rule it out)..
Actually, I can spot as MAG-similar event types:
- IIO_EV_TYPE_MAG
- IIO_EV_TYPE_MAG_ADAPTIVE
- IIO_EV_TYPE_CHANGE
- IIO_EV_TYPE_MAG_REFERENCED
For instance the last one is only used in a single sensor. Is there a
chance to put, say, freefall into one of the other "MAG-like" sensor
types. Alternatively, what about putting Activity/Inactivity under
say, "MAG_REFERENCED"? This might seem to be a stupid question, since
I can imagine you have a clear definition of those in mind. But if
this was possible. It would solve this problem easily.
If not, then I'll need to think of it and come up with a more
elaborate approach. The label + index approach seems to be a bit
complex. Going somehow by the time constraints in the event.. I need
to play with that in the code to build up an oppinion, I guess.
Best,
L
> Jonathan
>
> >
> > Best,
> > L
> >
> > > > + .dir = IIO_EV_DIR_RISING,
> > > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> > > > + },
> > > > {
> > > > /* single tap */
> > > > .type = IIO_EV_TYPE_GESTURE,
> > > > @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > > return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> > > > }
> > > >
> > > Jonathan
> > >
> > >
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-05-04 17:47 ` Lothar Rubusch
@ 2025-05-05 12:37 ` Jonathan Cameron
2025-05-06 22:37 ` Lothar Rubusch
0 siblings, 1 reply; 32+ messages in thread
From: Jonathan Cameron @ 2025-05-05 12:37 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Sun, 4 May 2025 19:47:55 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> On Sun, May 4, 2025 at 12:29 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Thu, 1 May 2025 00:53:32 +0200
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > Hi Jonathan - Hi IIO list,
> > >
> > > Please, find some (many) questions inlined down below. Appologies for
> > > the separate
> > > channels last time and not right away fixing them up as array. I did
> > > not want to make extra work.
> > >
> > > On Sun, Apr 27, 2025 at 2:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > >
> > > > On Mon, 21 Apr 2025 22:06:38 +0000
> > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > >
> > > > > Make the sensor detect and issue interrupts at activity. Activity
> > > > > events are configured by a threshold stored in regmap cache. Initialize
> > > > > the activity threshold register to a reasonable default value in probe.
> > > > > The value is taken from the older ADXL345 input driver, to provide a
> > > > > similar behavior. Reset the activity/inactivity direction enabling
> > > > > register in probe. Reset and initialization shall bring the sensor in a
> > > > > defined initial state to prevent dangling settings when warm restarting
> > > > > the sensor.
> > > > >
> > > > > Activity, ODR configuration together with the range setting prepare the
> > > > > activity/inactivity hystersesis setup, implemented in a follow up patch.
> > > > >
> > > > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > > > ---
> > > > > drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> > > > > 1 file changed, 214 insertions(+), 3 deletions(-)
> > > > >
> > > > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > > > index 80b5b8402ced..680981609d83 100644
> > > > > --- a/drivers/iio/accel/adxl345_core.c
> > > > > +++ b/drivers/iio/accel/adxl345_core.c
> > > > > @@ -36,11 +36,16 @@
> > > > > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > > > +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > > >
> > > > > #define ADXL345_TAP_Z_EN BIT(0)
> > > > > #define ADXL345_TAP_Y_EN BIT(1)
> > > > > #define ADXL345_TAP_X_EN BIT(2)
> > > > >
> > > > > +#define ADXL345_ACT_Z_EN BIT(4)
> > > > > +#define ADXL345_ACT_Y_EN BIT(5)
> > > > > +#define ADXL345_ACT_X_EN BIT(6)
> > > > > +
> > > > > /* single/double tap */
> > > > > enum adxl345_tap_type {
> > > > > ADXL345_SINGLE_TAP,
> > > > > @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> > > > > [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> > > > > };
> > > > >
> > > > > +/* activity/inactivity */
> > > > > +enum adxl345_activity_type {
> > > > > + ADXL345_ACTIVITY,
> > > > > +};
> > > > > +
> > > > > +static const unsigned int adxl345_act_int_reg[] = {
> > > > > + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> > > > > +};
> > > > > +
> > > > > +static const unsigned int adxl345_act_thresh_reg[] = {
> > > > > + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> > > > > +};
> > > > > +
> > > > > enum adxl345_odr {
> > > > > ADXL345_ODR_0P10HZ = 0,
> > > > > ADXL345_ODR_0P20HZ,
> > > > > @@ -154,6 +172,13 @@ struct adxl345_state {
> > > > > };
> > > > >
> > > > > static struct iio_event_spec adxl345_events[] = {
> > > > > + {
> > > > > + /* activity */
> > > > > + .type = IIO_EV_TYPE_THRESH,
> > > >
> > > > Is this a threshold, or a magnitude? I'd expect an activity detector
> > > > to be magnitude as it doesn't care which way up the sensor is.
> > > >
> > >
> > > This is touching the main points still unclear to me. I tried to put
> > > this into the
> > > following questions. Could you please clarify?
> >
> > There are some corners where it gets messy. When I have time
> > (not for a month or so) I'll try and write some proper docs for this.
> >
> > >
> > > 1. Given a measurement "val", and a configured threshold "thr".
> > > A "rising" for IIO_EV_TYPE_THRESH means: val > thr
> > > where a "rising" for IIO_EV_TYPE_MAG means something like: val > |thr|
> > >
> > > Q: Do I understand this correctly now?
> >
> > Yes that is the intended difference.
> >
> > >
> > > Q: Is this documented somewhere (especially for reviewing further
> > > EV_TYPE fields)?
> >
> > Only in the ABI documentation in
> > Documentation/ABI/testing/sysfs-bus-iio
> > This is definitely something we should look to improve with some
> > docs beyond simply what the ABI is. That ABI is focused on
> > how the interrupt is triggered, not so much on what that means
> > wrt to freefall etc.
> >
> >
> > >
> > > Q: I wonder if I missed this for the Tap events. Going by this
> > > definition, then actually the
> > > tap events should be rather MAG events, too. Right?
> >
> > The tap events have their own type (gesture) because they are way
> > more complex than a simple threshold whether on magnitude or
> > the signed value. So those should be fine as type GESTURE.
> >
>
> I was aware of that. Actually, the case of GESTURE is a bit
> particular. On the one side, I
> understand having a distinction between THRESH events and MAG events.
> Hence, a classification of the type of event in terms of a measurement
> value triggering event condition.
> This concept seems actually to be clear.
>
> GESTURE to me then seems a bit like a "wildcard type covering all kind
> of tap events". I mean,
> saying tap detection, single tap, double tap, tripple tap, and so on
> tap go into category GESTURE - naively could also mean, then do a
> freefall type as well (?).
Nope. Because freefall has a clear definition that aligns with
the events that we have for other types of sensor.
You are right that gesture is a wild card. I resisted it for a long
time but there is just no sane way to handle tap events alongside
the sort of things we get on any other sensor type. Having looked
at a bunch of them they can be anything from straight magnitude thresholds
with time windows to things based on a mixture of jerk (rate of change
of acceleration) and other stuff. The only thing that kind of close
to this is pedometer step events, but we handle those as a counting
channel rather than an event as time of each is less interesting than
how many have happened. However as noted below we do have the CHANGE
type of event specifically to account for those (which is ugly).
>
> [this is rather meant as a bit of a provocative rhetoric question than
> a proposal]
>
> > >
> > >
> > > 2. I oriented myself mostly by reading other drivers, for instance the
> > > ADXL367, the ADXL372, or also the more recent ADXL380. I am aware that
> > > there might be differences among different
> > > (Analog) sensors. But all those sensors specify Inactivity (and Activity) as a
> > > IIO_EV_TYPE_THRESH with directions IIO_MOD_X_OR_Y_OR_Z.
> > > Given the above, I implemented Activity and Inactivity events as
> > > IIO_EV_TYPE_THRESH,
> > > now I'm a bit confused.
> >
> > Hmm. This is one reason I think we need more documentation as those
> > seem to be wrong. Clearly the event is a threshold on a magnitude of
> > the acceleration, not the signed value as it applies in both directions.
> >
> > >
> > > Q: Why is this different for the ADXL345?
> >
> > Because we got it wrong for these others it seems unless they genuinely
> > have directional events - which typically means separate positive and
> > negative thresholds. Right now those events are strictly speaking
> > only apply to positive accelerations.
> >
> > >
> > > Q: If I implement Activity / Inactivity analogous to the e.g. a
> > > ADXL380, then shouldn't it be IIO_EV_TYPE_THRESH with
> > > IIO_MOD_X_OR_Y_OR_Z? Why not?
> > >
> >
> > I think we got it wrong for that part. Going forwards we should work
> > on getting it (more) correct.
> >
>
> I understand the point better now.
>
> > >
> > > 3. For the ADXL345, a Freefall signal is on all axis lower than
> > > threshold (magnitude). Thus I push a IIO_MOD_X_AND_Y_AND_Z to a
> > > separate
> > > fake channel. Inactivity will be like Freefall independent of the axis.
> > > The ADXL345 Activity can be configured by axis, as also the event will
> > > respect the axis information.
> > >
> > > Q: Setting up the "fake channel" to particuarly push to
> > > IIO_MOD_X_AND_Y_AND_Z, I probably better should also evaluate
> > > IIO_MOD_X_AND_Y_AND_Z in write_event_config(), write_event_value(),
> > > etc. rather than evaluating IIO_MOD_-types as I'm currently
> > > doing?
> >
> > Yes. That sounds correct for events on these 'fake' channels.
> > The enable and the thresholds should all be on these fake channels
> > assuming they don't have different thresholds on a per axis basis
> > (if they do things get tricky to represent).
> >
> > >
> > > Q: Activity probably remains in the regular channels for the corresponding axis?
> >
> > Yes. That is easier to handle as OR of channels is very similar
> > to separate interrupts etc.
> >
>
> I think I should definitely evaluate the IIO_MOD_X_AND_Y_AND_Z here,
> to take advantage
> of the fake channel.
>
> > >
> > >
> > > 4. I implemented functions like adxl345_write_event_config(),
> > > adxl345_write_event_value() or corresponding
> > > readers, as follows
> > > - THRESH/rising: Activity
> > > - THRESH/falling: Inactivity
> > > - MAG/falling: Freefall
> > >
> > > If I change Activity and Inactivity to be both of type MAG, I will end
> > > up with MAG/falling to indicate Freefall or equally Inactivity.
> > > Both on the IIO_MOD_X_AND_Y_AND_Z channel. I admit (ab)using the
> > > IIO_EV_TYPEs to solve my combinatorial issues for event configuration
> > > is probably not as supposed to be.
> >
> > Ah.. This I'd missed. I'm fairly sure we didn't hit this for (some) previous
> > inactivity sensors because they were always rate of change based, (AC)
> > rather than DC. DC is relatively unlikely to be used in practice because
> > we can't set the threshold as less than 1G because of gravity. It is a
> > bit odd that the device supports both DC and AC on this detector.
> >
> > I wonder why.... Might be to enable partial axis monitoring. e.g.
> > If a device is flat on a table we only look for inactivity on the non
> > vertical axis when doing DC coupling. (as we have 1g always in the other
> > axis).
> >
>
> Thank you for clarifying your position in the other mail focussed on
> the AC- / DC-coupling
> topic. It helped me in better understanding what you actually expect
> here. Although I'll
> probably need to re-read it some times, before implementing something.
I definitely need to find time to write some docs on this. Mad few
weeks coming up but maybe I'll get some time on a plane or at an airport
to try a first draft.
>
> > > Given you still ask me to do Inactivity and Freefall as MAG/falling
> > > with IIO_MOD_X_AND_Y_AND_Z. The difference between both IMHO,
> > > is that Activity and Inactivity for the ADXL345 indicate sensor state
> > > changes, where Freefall indicates the particular event. The
> > > sensor is either in "active" or "standby/inactive", where Freefall
> > > just triggers and then retriggers and retriggers...
> >
> > Maybe. The datasheet is annoyingly vague on these but indeed there
> > is no event for no longer falling.
> >
> > >
> > > Q: What is the method to distinguish several similar IIO events, e.g.
> > > to tag them somehow one as Freefall, the other one as Inactivity?
> >
> > In general we should not be able to do that. Long ago we made the decision
> > to have compact event codes so they don't allow for an index on a particular
> > combination of channel number and modifier. This is mainly because
> > there is limited purpose. If one event is triggered, then we have
> > to process anyway so we can just look at the value for 'how far' it was
> > triggered. I.e. if we thought DC inactivity was triggered, we can just
> > check free fall as well. (It gets a little more fiddly because of _period
> > etc which is why they may actually make sense here).
> >
> > The virtual (combination OR/AND) was added on top of that later and has
> > made the connection looser.
> >
> > In theory we could use labels + index for the virtual channels to achieve
> > separate control attributes and be able to tell which was which but
> > that would be new ABI. I'm not sure how much use this stuff is already
> > getting from userspace applications and hence whether this would be
> > a big problem to get supported.
> >
> > That would give us something like
> >
> > iio\:device0/in_accel0_x&y&z_label freefall
> > iio\:device0/in_accel1_x&y&z_label inactivity
> > iio\:device0/events/in_accel0_x&y&z_en etc
> > iio\:device0/events/in_accel1_x&y&z_en etc
> >
> > I don't like it much because it then doesn't generalize to the case
> > of multiple sensors on each axis (there are multi range parts that do that).
> > That case is pretty rare though (I think we only have such sensor supported!)
> > However, it's currently the only option we have to fully represent this.
> >
> > An alternative here might be to assess if anyone is really going to use
> > DC coupled inactivity detection (because of the 1g problem) and hence whether
> > we want to support that at all?
> >
> > Yet another alternative might be to configure it purely based on the period
> > provided. If short use freefall, if long use inactivity. (I don't like this
> > one though as it doesn't really fit with usecase!)
> >
> > Sorry for lack of clarity on this. These events are tricky and
> > it takes me a while to get the whole situation back into my head (and I missing
> > things like inactivity and freefall being very similar here!)
> >
> > If you have time to take a look at what userspace is currently doing with
> > these events (iio_sensor_proxy etc) that might help us decide what works.
> >
>
> Just as a quick response here (or perhaps just to rule it out)..
>
> Actually, I can spot as MAG-similar event types:
> - IIO_EV_TYPE_MAG
> - IIO_EV_TYPE_MAG_ADAPTIVE
> - IIO_EV_TYPE_CHANGE
> - IIO_EV_TYPE_MAG_REFERENCED
>
> For instance the last one is only used in a single sensor. Is there a
> chance to put, say, freefall into one of the other "MAG-like" sensor
> types. Alternatively, what about putting Activity/Inactivity under
> say, "MAG_REFERENCED"? This might seem to be a stupid question, since
> I can imagine you have a clear definition of those in mind. But if
> this was possible. It would solve this problem easily.
The ABI docs do provide some definitions of these.
Free fall is definitely straight forward TYPE MAG. It precisely aligns
with that definition as a threshold on the per axis magnitudes.
MAG_ADAPTIVE is meant for a case where the event is on the magnitude
relative to a slow moving adaptive baseline (usually a low pass filtered
version of the signal but can include corrective jumps - IIRC these turn
up for magnetic sensors). This differs from a rate of change threshold
because it's not simply a difference between current and earlier signals
but rather current and some heavily filtered earlier signal.
These matter in cases where we have a slow changing baseline such as
coming into proximity with metal in the environment when using a magnetometer
for orientation detection.
MAG_REFERENCED is a weird one. This was done for a nice IMU that had
the ability to estimate orientation and so remove the acceleration due
to gravity and then apply thresholds to the magnitude of the remaining
accelerations. The AC filtering on your part is is a 'cheap' way
to achieve roughly the equivalent of that for the activity detection at least
where we are removing the 'nothing happening value'.
(it is less clear for the inactivity case though that will still include
g, so maybe it is still somewhat valid).
CHANGE is IIRC only for counting channels (so far anyway).
So none of the more esoteric forms of them fit for this DC coupled
inactivity monitor or freefall. Both of them are the same type of event
just differing in filters applied.
>
> If not, then I'll need to think of it and come up with a more
> elaborate approach. The label + index approach seems to be a bit
> complex. Going somehow by the time constraints in the event.. I need
> to play with that in the code to build up an oppinion, I guess.
The time constraints thing falls down on the basis that it would
be logical to have freefall enabled (for parking any moving parts - those
used to exist mainly to stop hard disks but maybe there are other use cases?)
and inactivity for power saving with a much longer timescale.
Freefall used to be fun because the aim was to get moving parts into
a safe state before the device hit the ground. So that meant if you dropped
a device with a harddisk from higher up, it sometimes had a better chance
of surviving. I'm not sure if anyone cares any more! Will be interesting
to see if that feature goes away on new devices.
Jonathan
>
> Best,
> L
>
> > Jonathan
> >
> > >
> > > Best,
> > > L
> > >
> > > > > + .dir = IIO_EV_DIR_RISING,
> > > > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> > > > > + },
> > > > > {
> > > > > /* single tap */
> > > > > .type = IIO_EV_TYPE_GESTURE,
> > > > > @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > > > return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> > > > > }
> > > > >
> > > > Jonathan
> > > >
> > > >
> >
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-05-05 12:37 ` Jonathan Cameron
@ 2025-05-06 22:37 ` Lothar Rubusch
2025-05-07 19:48 ` Jonathan Cameron
0 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-05-06 22:37 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
Hi Jonathan,
Still some questions and thoughts down below.
On Mon, May 5, 2025 at 2:37 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Sun, 4 May 2025 19:47:55 +0200
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > On Sun, May 4, 2025 at 12:29 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > >
> > > On Thu, 1 May 2025 00:53:32 +0200
> > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > >
> > > > Hi Jonathan - Hi IIO list,
> > > >
> > > > Please, find some (many) questions inlined down below. Appologies for
> > > > the separate
> > > > channels last time and not right away fixing them up as array. I did
> > > > not want to make extra work.
> > > >
> > > > On Sun, Apr 27, 2025 at 2:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > > >
> > > > > On Mon, 21 Apr 2025 22:06:38 +0000
> > > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > > >
> > > > > > Make the sensor detect and issue interrupts at activity. Activity
> > > > > > events are configured by a threshold stored in regmap cache. Initialize
> > > > > > the activity threshold register to a reasonable default value in probe.
> > > > > > The value is taken from the older ADXL345 input driver, to provide a
> > > > > > similar behavior. Reset the activity/inactivity direction enabling
> > > > > > register in probe. Reset and initialization shall bring the sensor in a
> > > > > > defined initial state to prevent dangling settings when warm restarting
> > > > > > the sensor.
> > > > > >
> > > > > > Activity, ODR configuration together with the range setting prepare the
> > > > > > activity/inactivity hystersesis setup, implemented in a follow up patch.
> > > > > >
> > > > > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > > > > ---
> > > > > > drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> > > > > > 1 file changed, 214 insertions(+), 3 deletions(-)
> > > > > >
> > > > > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > > > > index 80b5b8402ced..680981609d83 100644
> > > > > > --- a/drivers/iio/accel/adxl345_core.c
> > > > > > +++ b/drivers/iio/accel/adxl345_core.c
> > > > > > @@ -36,11 +36,16 @@
> > > > > > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > > > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > > > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > > > > +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > > > >
> > > > > > #define ADXL345_TAP_Z_EN BIT(0)
> > > > > > #define ADXL345_TAP_Y_EN BIT(1)
> > > > > > #define ADXL345_TAP_X_EN BIT(2)
> > > > > >
> > > > > > +#define ADXL345_ACT_Z_EN BIT(4)
> > > > > > +#define ADXL345_ACT_Y_EN BIT(5)
> > > > > > +#define ADXL345_ACT_X_EN BIT(6)
> > > > > > +
> > > > > > /* single/double tap */
> > > > > > enum adxl345_tap_type {
> > > > > > ADXL345_SINGLE_TAP,
> > > > > > @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> > > > > > [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> > > > > > };
> > > > > >
> > > > > > +/* activity/inactivity */
> > > > > > +enum adxl345_activity_type {
> > > > > > + ADXL345_ACTIVITY,
> > > > > > +};
> > > > > > +
> > > > > > +static const unsigned int adxl345_act_int_reg[] = {
> > > > > > + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> > > > > > +};
> > > > > > +
> > > > > > +static const unsigned int adxl345_act_thresh_reg[] = {
> > > > > > + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> > > > > > +};
> > > > > > +
> > > > > > enum adxl345_odr {
> > > > > > ADXL345_ODR_0P10HZ = 0,
> > > > > > ADXL345_ODR_0P20HZ,
> > > > > > @@ -154,6 +172,13 @@ struct adxl345_state {
> > > > > > };
> > > > > >
> > > > > > static struct iio_event_spec adxl345_events[] = {
> > > > > > + {
> > > > > > + /* activity */
> > > > > > + .type = IIO_EV_TYPE_THRESH,
> > > > >
> > > > > Is this a threshold, or a magnitude? I'd expect an activity detector
> > > > > to be magnitude as it doesn't care which way up the sensor is.
> > > > >
> > > >
> > > > This is touching the main points still unclear to me. I tried to put
> > > > this into the
> > > > following questions. Could you please clarify?
> > >
> > > There are some corners where it gets messy. When I have time
> > > (not for a month or so) I'll try and write some proper docs for this.
> > >
> > > >
> > > > 1. Given a measurement "val", and a configured threshold "thr".
> > > > A "rising" for IIO_EV_TYPE_THRESH means: val > thr
> > > > where a "rising" for IIO_EV_TYPE_MAG means something like: val > |thr|
> > > >
> > > > Q: Do I understand this correctly now?
> > >
> > > Yes that is the intended difference.
> > >
> > > >
> > > > Q: Is this documented somewhere (especially for reviewing further
> > > > EV_TYPE fields)?
> > >
> > > Only in the ABI documentation in
> > > Documentation/ABI/testing/sysfs-bus-iio
> > > This is definitely something we should look to improve with some
> > > docs beyond simply what the ABI is. That ABI is focused on
> > > how the interrupt is triggered, not so much on what that means
> > > wrt to freefall etc.
> > >
> > >
> > > >
> > > > Q: I wonder if I missed this for the Tap events. Going by this
> > > > definition, then actually the
> > > > tap events should be rather MAG events, too. Right?
> > >
> > > The tap events have their own type (gesture) because they are way
> > > more complex than a simple threshold whether on magnitude or
> > > the signed value. So those should be fine as type GESTURE.
> > >
> >
> > I was aware of that. Actually, the case of GESTURE is a bit
> > particular. On the one side, I
> > understand having a distinction between THRESH events and MAG events.
> > Hence, a classification of the type of event in terms of a measurement
> > value triggering event condition.
> > This concept seems actually to be clear.
> >
> > GESTURE to me then seems a bit like a "wildcard type covering all kind
> > of tap events". I mean,
> > saying tap detection, single tap, double tap, tripple tap, and so on
> > tap go into category GESTURE - naively could also mean, then do a
> > freefall type as well (?).
>
> Nope. Because freefall has a clear definition that aligns with
> the events that we have for other types of sensor.
>
> You are right that gesture is a wild card. I resisted it for a long
> time but there is just no sane way to handle tap events alongside
> the sort of things we get on any other sensor type. Having looked
> at a bunch of them they can be anything from straight magnitude thresholds
> with time windows to things based on a mixture of jerk (rate of change
> of acceleration) and other stuff. The only thing that kind of close
> to this is pedometer step events, but we handle those as a counting
> channel rather than an event as time of each is less interesting than
> how many have happened. However as noted below we do have the CHANGE
> type of event specifically to account for those (which is ugly).
>
> >
> > [this is rather meant as a bit of a provocative rhetoric question than
> > a proposal]
> >
> > > >
> > > >
> > > > 2. I oriented myself mostly by reading other drivers, for instance the
> > > > ADXL367, the ADXL372, or also the more recent ADXL380. I am aware that
> > > > there might be differences among different
> > > > (Analog) sensors. But all those sensors specify Inactivity (and Activity) as a
> > > > IIO_EV_TYPE_THRESH with directions IIO_MOD_X_OR_Y_OR_Z.
> > > > Given the above, I implemented Activity and Inactivity events as
> > > > IIO_EV_TYPE_THRESH,
> > > > now I'm a bit confused.
> > >
> > > Hmm. This is one reason I think we need more documentation as those
> > > seem to be wrong. Clearly the event is a threshold on a magnitude of
> > > the acceleration, not the signed value as it applies in both directions.
> > >
> > > >
> > > > Q: Why is this different for the ADXL345?
> > >
> > > Because we got it wrong for these others it seems unless they genuinely
> > > have directional events - which typically means separate positive and
> > > negative thresholds. Right now those events are strictly speaking
> > > only apply to positive accelerations.
> > >
> > > >
> > > > Q: If I implement Activity / Inactivity analogous to the e.g. a
> > > > ADXL380, then shouldn't it be IIO_EV_TYPE_THRESH with
> > > > IIO_MOD_X_OR_Y_OR_Z? Why not?
> > > >
> > >
> > > I think we got it wrong for that part. Going forwards we should work
> > > on getting it (more) correct.
> > >
> >
> > I understand the point better now.
> >
> > > >
> > > > 3. For the ADXL345, a Freefall signal is on all axis lower than
> > > > threshold (magnitude). Thus I push a IIO_MOD_X_AND_Y_AND_Z to a
> > > > separate
> > > > fake channel. Inactivity will be like Freefall independent of the axis.
> > > > The ADXL345 Activity can be configured by axis, as also the event will
> > > > respect the axis information.
> > > >
> > > > Q: Setting up the "fake channel" to particuarly push to
> > > > IIO_MOD_X_AND_Y_AND_Z, I probably better should also evaluate
> > > > IIO_MOD_X_AND_Y_AND_Z in write_event_config(), write_event_value(),
> > > > etc. rather than evaluating IIO_MOD_-types as I'm currently
> > > > doing?
> > >
> > > Yes. That sounds correct for events on these 'fake' channels.
> > > The enable and the thresholds should all be on these fake channels
> > > assuming they don't have different thresholds on a per axis basis
> > > (if they do things get tricky to represent).
> > >
> > > >
> > > > Q: Activity probably remains in the regular channels for the corresponding axis?
> > >
> > > Yes. That is easier to handle as OR of channels is very similar
> > > to separate interrupts etc.
> > >
> >
> > I think I should definitely evaluate the IIO_MOD_X_AND_Y_AND_Z here,
> > to take advantage
> > of the fake channel.
> >
> > > >
> > > >
> > > > 4. I implemented functions like adxl345_write_event_config(),
> > > > adxl345_write_event_value() or corresponding
> > > > readers, as follows
> > > > - THRESH/rising: Activity
> > > > - THRESH/falling: Inactivity
> > > > - MAG/falling: Freefall
> > > >
> > > > If I change Activity and Inactivity to be both of type MAG, I will end
> > > > up with MAG/falling to indicate Freefall or equally Inactivity.
> > > > Both on the IIO_MOD_X_AND_Y_AND_Z channel. I admit (ab)using the
> > > > IIO_EV_TYPEs to solve my combinatorial issues for event configuration
> > > > is probably not as supposed to be.
> > >
> > > Ah.. This I'd missed. I'm fairly sure we didn't hit this for (some) previous
> > > inactivity sensors because they were always rate of change based, (AC)
> > > rather than DC. DC is relatively unlikely to be used in practice because
> > > we can't set the threshold as less than 1G because of gravity. It is a
> > > bit odd that the device supports both DC and AC on this detector.
> > >
> > > I wonder why.... Might be to enable partial axis monitoring. e.g.
> > > If a device is flat on a table we only look for inactivity on the non
> > > vertical axis when doing DC coupling. (as we have 1g always in the other
> > > axis).
> > >
> >
> > Thank you for clarifying your position in the other mail focussed on
> > the AC- / DC-coupling
> > topic. It helped me in better understanding what you actually expect
> > here. Although I'll
> > probably need to re-read it some times, before implementing something.
>
> I definitely need to find time to write some docs on this. Mad few
> weeks coming up but maybe I'll get some time on a plane or at an airport
> to try a first draft.
>
> >
> > > > Given you still ask me to do Inactivity and Freefall as MAG/falling
> > > > with IIO_MOD_X_AND_Y_AND_Z. The difference between both IMHO,
> > > > is that Activity and Inactivity for the ADXL345 indicate sensor state
> > > > changes, where Freefall indicates the particular event. The
> > > > sensor is either in "active" or "standby/inactive", where Freefall
> > > > just triggers and then retriggers and retriggers...
> > >
> > > Maybe. The datasheet is annoyingly vague on these but indeed there
> > > is no event for no longer falling.
> > >
> > > >
> > > > Q: What is the method to distinguish several similar IIO events, e.g.
> > > > to tag them somehow one as Freefall, the other one as Inactivity?
> > >
> > > In general we should not be able to do that. Long ago we made the decision
> > > to have compact event codes so they don't allow for an index on a particular
> > > combination of channel number and modifier. This is mainly because
> > > there is limited purpose. If one event is triggered, then we have
> > > to process anyway so we can just look at the value for 'how far' it was
> > > triggered. I.e. if we thought DC inactivity was triggered, we can just
> > > check free fall as well. (It gets a little more fiddly because of _period
> > > etc which is why they may actually make sense here).
> > >
> > > The virtual (combination OR/AND) was added on top of that later and has
> > > made the connection looser.
> > >
> > > In theory we could use labels + index for the virtual channels to achieve
> > > separate control attributes and be able to tell which was which but
> > > that would be new ABI. I'm not sure how much use this stuff is already
> > > getting from userspace applications and hence whether this would be
> > > a big problem to get supported.
> > >
> > > That would give us something like
> > >
> > > iio\:device0/in_accel0_x&y&z_label freefall
> > > iio\:device0/in_accel1_x&y&z_label inactivity
> > > iio\:device0/events/in_accel0_x&y&z_en etc
> > > iio\:device0/events/in_accel1_x&y&z_en etc
> > >
> > > I don't like it much because it then doesn't generalize to the case
> > > of multiple sensors on each axis (there are multi range parts that do that).
> > > That case is pretty rare though (I think we only have such sensor supported!)
> > > However, it's currently the only option we have to fully represent this.
> > >
Kind of the naive approach to allow for extending everything, but it probably
does not fit together with the existing sensor ABI.
> > > An alternative here might be to assess if anyone is really going to use
> > > DC coupled inactivity detection (because of the 1g problem) and hence whether
> > > we want to support that at all?
> > >
I'd rather drop free fall then. Activity and inactivity, linked to
gether work well for the power saving
thing. I guess that's probably also more of general use. Not sure how
usefull free fall is here.
> > > Yet another alternative might be to configure it purely based on the period
> > > provided. If short use freefall, if long use inactivity. (I don't like this
> > > one though as it doesn't really fit with usecase!)
> > >
That's either free fall or the other? Ideally, I'd like to represent
what's possible with this sensor.
Since I'm not really familiar with how those sensors are used, for me
all features are equally
important. I mean, the sensor offers enabling act/inact and enabling
free fall if one likes.
> > > Sorry for lack of clarity on this. These events are tricky and
> > > it takes me a while to get the whole situation back into my head (and I missing
> > > things like inactivity and freefall being very similar here!)
> > >
Ideally I'd like to implement support for all features of the sensor.
I can understand if some corner case features are too individual to
support them. So, at least the main features.
From what I see in the code, configuring threshold and period, there
might be differences and it can be easy to distinguish in
read/write_event_value() like functions. Where
read/write_event_config() will be more difficult.
So, for the event, when I receive it I have knowledge of the exact
event type, free fall or inactivity. I'm losing this information since
it can currently not be conveyed over the channel. Might be that the
ADXL345 is a very particular case. Might also be, that this shows a
more general shortcoming which should be addressed.
Equally I could say, there is just a MAG event, so turning on
inactivity or free fall would result in the same event. The tricky
case then is if someone wants inactivity AND free fall. In such case
it would be a inact_or_free_fall event. Not sure about. Probably
rather not.
Q: Still, what about the direction - I see, where activity is using
IIO_EV_DIR_RISING, inactivity uses _DIR_FALLING. Free fall now uses
_DIR_FALLING. I'm a bit unsure if this is just a choice. Could I use
_DIR_EITHER here? Or why not? I mean, actually it's not possible to
detect fall into one direction, it's simply "in a fall". We cannot
tell which axis we cannot tell which direction.
> > > If you have time to take a look at what userspace is currently doing with
> > > these events (iio_sensor_proxy etc) that might help us decide what works.
> > >
This might be generally a good option. Honestly, I'd like to mess
still with some other sensors,
first.
Best,
L
> >
> > Just as a quick response here (or perhaps just to rule it out)..
> >
> > Actually, I can spot as MAG-similar event types:
> > - IIO_EV_TYPE_MAG
> > - IIO_EV_TYPE_MAG_ADAPTIVE
> > - IIO_EV_TYPE_CHANGE
> > - IIO_EV_TYPE_MAG_REFERENCED
> >
> > For instance the last one is only used in a single sensor. Is there a
> > chance to put, say, freefall into one of the other "MAG-like" sensor
> > types. Alternatively, what about putting Activity/Inactivity under
> > say, "MAG_REFERENCED"? This might seem to be a stupid question, since
> > I can imagine you have a clear definition of those in mind. But if
> > this was possible. It would solve this problem easily.
>
> The ABI docs do provide some definitions of these.
>
> Free fall is definitely straight forward TYPE MAG. It precisely aligns
> with that definition as a threshold on the per axis magnitudes.
>
> MAG_ADAPTIVE is meant for a case where the event is on the magnitude
> relative to a slow moving adaptive baseline (usually a low pass filtered
> version of the signal but can include corrective jumps - IIRC these turn
> up for magnetic sensors). This differs from a rate of change threshold
> because it's not simply a difference between current and earlier signals
> but rather current and some heavily filtered earlier signal.
> These matter in cases where we have a slow changing baseline such as
> coming into proximity with metal in the environment when using a magnetometer
> for orientation detection.
>
> MAG_REFERENCED is a weird one. This was done for a nice IMU that had
> the ability to estimate orientation and so remove the acceleration due
> to gravity and then apply thresholds to the magnitude of the remaining
> accelerations. The AC filtering on your part is is a 'cheap' way
> to achieve roughly the equivalent of that for the activity detection at least
> where we are removing the 'nothing happening value'.
> (it is less clear for the inactivity case though that will still include
> g, so maybe it is still somewhat valid).
>
> CHANGE is IIRC only for counting channels (so far anyway).
>
> So none of the more esoteric forms of them fit for this DC coupled
> inactivity monitor or freefall. Both of them are the same type of event
> just differing in filters applied.
>
> >
> > If not, then I'll need to think of it and come up with a more
> > elaborate approach. The label + index approach seems to be a bit
> > complex. Going somehow by the time constraints in the event.. I need
> > to play with that in the code to build up an oppinion, I guess.
>
> The time constraints thing falls down on the basis that it would
> be logical to have freefall enabled (for parking any moving parts - those
> used to exist mainly to stop hard disks but maybe there are other use cases?)
> and inactivity for power saving with a much longer timescale.
>
> Freefall used to be fun because the aim was to get moving parts into
> a safe state before the device hit the ground. So that meant if you dropped
> a device with a harddisk from higher up, it sometimes had a better chance
> of surviving. I'm not sure if anyone cares any more! Will be interesting
> to see if that feature goes away on new devices.
>
> Jonathan
>
> >
> > Best,
> > L
> >
> > > Jonathan
> > >
> > > >
> > > > Best,
> > > > L
> > > >
> > > > > > + .dir = IIO_EV_DIR_RISING,
> > > > > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > > > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> > > > > > + },
> > > > > > {
> > > > > > /* single tap */
> > > > > > .type = IIO_EV_TYPE_GESTURE,
> > > > > > @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > > > > return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> > > > > > }
> > > > > >
> > > > > Jonathan
> > > > >
> > > > >
> > >
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-05-06 22:37 ` Lothar Rubusch
@ 2025-05-07 19:48 ` Jonathan Cameron
2025-05-07 21:40 ` Lothar Rubusch
0 siblings, 1 reply; 32+ messages in thread
From: Jonathan Cameron @ 2025-05-07 19:48 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Wed, 7 May 2025 00:37:43 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Hi Jonathan,
> Still some questions and thoughts down below.
>
> On Mon, May 5, 2025 at 2:37 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Sun, 4 May 2025 19:47:55 +0200
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > On Sun, May 4, 2025 at 12:29 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > >
> > > > On Thu, 1 May 2025 00:53:32 +0200
> > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > >
> > > > > Hi Jonathan - Hi IIO list,
> > > > >
> > > > > Please, find some (many) questions inlined down below. Appologies for
> > > > > the separate
> > > > > channels last time and not right away fixing them up as array. I did
> > > > > not want to make extra work.
> > > > >
> > > > > On Sun, Apr 27, 2025 at 2:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > > > >
> > > > > > On Mon, 21 Apr 2025 22:06:38 +0000
> > > > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > > > >
> > > > > > > Make the sensor detect and issue interrupts at activity. Activity
> > > > > > > events are configured by a threshold stored in regmap cache. Initialize
> > > > > > > the activity threshold register to a reasonable default value in probe.
> > > > > > > The value is taken from the older ADXL345 input driver, to provide a
> > > > > > > similar behavior. Reset the activity/inactivity direction enabling
> > > > > > > register in probe. Reset and initialization shall bring the sensor in a
> > > > > > > defined initial state to prevent dangling settings when warm restarting
> > > > > > > the sensor.
> > > > > > >
> > > > > > > Activity, ODR configuration together with the range setting prepare the
> > > > > > > activity/inactivity hystersesis setup, implemented in a follow up patch.
> > > > > > >
> > > > > > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > > > > > ---
> > > > > > > drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> > > > > > > 1 file changed, 214 insertions(+), 3 deletions(-)
> > > > > > >
> > > > > > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > > > > > index 80b5b8402ced..680981609d83 100644
> > > > > > > --- a/drivers/iio/accel/adxl345_core.c
> > > > > > > +++ b/drivers/iio/accel/adxl345_core.c
> > > > > > > @@ -36,11 +36,16 @@
> > > > > > > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > > > > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > > > > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > > > > > +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > > > > >
> > > > > > > #define ADXL345_TAP_Z_EN BIT(0)
> > > > > > > #define ADXL345_TAP_Y_EN BIT(1)
> > > > > > > #define ADXL345_TAP_X_EN BIT(2)
> > > > > > >
> > > > > > > +#define ADXL345_ACT_Z_EN BIT(4)
> > > > > > > +#define ADXL345_ACT_Y_EN BIT(5)
> > > > > > > +#define ADXL345_ACT_X_EN BIT(6)
> > > > > > > +
> > > > > > > /* single/double tap */
> > > > > > > enum adxl345_tap_type {
> > > > > > > ADXL345_SINGLE_TAP,
> > > > > > > @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> > > > > > > [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> > > > > > > };
> > > > > > >
> > > > > > > +/* activity/inactivity */
> > > > > > > +enum adxl345_activity_type {
> > > > > > > + ADXL345_ACTIVITY,
> > > > > > > +};
> > > > > > > +
> > > > > > > +static const unsigned int adxl345_act_int_reg[] = {
> > > > > > > + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> > > > > > > +};
> > > > > > > +
> > > > > > > +static const unsigned int adxl345_act_thresh_reg[] = {
> > > > > > > + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> > > > > > > +};
> > > > > > > +
> > > > > > > enum adxl345_odr {
> > > > > > > ADXL345_ODR_0P10HZ = 0,
> > > > > > > ADXL345_ODR_0P20HZ,
> > > > > > > @@ -154,6 +172,13 @@ struct adxl345_state {
> > > > > > > };
> > > > > > >
> > > > > > > static struct iio_event_spec adxl345_events[] = {
> > > > > > > + {
> > > > > > > + /* activity */
> > > > > > > + .type = IIO_EV_TYPE_THRESH,
> > > > > >
> > > > > > Is this a threshold, or a magnitude? I'd expect an activity detector
> > > > > > to be magnitude as it doesn't care which way up the sensor is.
> > > > > >
> > > > >
> > > > > This is touching the main points still unclear to me. I tried to put
> > > > > this into the
> > > > > following questions. Could you please clarify?
> > > >
> > > > There are some corners where it gets messy. When I have time
> > > > (not for a month or so) I'll try and write some proper docs for this.
> > > >
> > > > >
> > > > > 1. Given a measurement "val", and a configured threshold "thr".
> > > > > A "rising" for IIO_EV_TYPE_THRESH means: val > thr
> > > > > where a "rising" for IIO_EV_TYPE_MAG means something like: val > |thr|
> > > > >
> > > > > Q: Do I understand this correctly now?
> > > >
> > > > Yes that is the intended difference.
> > > >
> > > > >
> > > > > Q: Is this documented somewhere (especially for reviewing further
> > > > > EV_TYPE fields)?
> > > >
> > > > Only in the ABI documentation in
> > > > Documentation/ABI/testing/sysfs-bus-iio
> > > > This is definitely something we should look to improve with some
> > > > docs beyond simply what the ABI is. That ABI is focused on
> > > > how the interrupt is triggered, not so much on what that means
> > > > wrt to freefall etc.
> > > >
> > > >
> > > > >
> > > > > Q: I wonder if I missed this for the Tap events. Going by this
> > > > > definition, then actually the
> > > > > tap events should be rather MAG events, too. Right?
> > > >
> > > > The tap events have their own type (gesture) because they are way
> > > > more complex than a simple threshold whether on magnitude or
> > > > the signed value. So those should be fine as type GESTURE.
> > > >
> > >
> > > I was aware of that. Actually, the case of GESTURE is a bit
> > > particular. On the one side, I
> > > understand having a distinction between THRESH events and MAG events.
> > > Hence, a classification of the type of event in terms of a measurement
> > > value triggering event condition.
> > > This concept seems actually to be clear.
> > >
> > > GESTURE to me then seems a bit like a "wildcard type covering all kind
> > > of tap events". I mean,
> > > saying tap detection, single tap, double tap, tripple tap, and so on
> > > tap go into category GESTURE - naively could also mean, then do a
> > > freefall type as well (?).
> >
> > Nope. Because freefall has a clear definition that aligns with
> > the events that we have for other types of sensor.
> >
> > You are right that gesture is a wild card. I resisted it for a long
> > time but there is just no sane way to handle tap events alongside
> > the sort of things we get on any other sensor type. Having looked
> > at a bunch of them they can be anything from straight magnitude thresholds
> > with time windows to things based on a mixture of jerk (rate of change
> > of acceleration) and other stuff. The only thing that kind of close
> > to this is pedometer step events, but we handle those as a counting
> > channel rather than an event as time of each is less interesting than
> > how many have happened. However as noted below we do have the CHANGE
> > type of event specifically to account for those (which is ugly).
> >
> > >
> > > [this is rather meant as a bit of a provocative rhetoric question than
> > > a proposal]
> > >
> > > > >
> > > > >
> > > > > 2. I oriented myself mostly by reading other drivers, for instance the
> > > > > ADXL367, the ADXL372, or also the more recent ADXL380. I am aware that
> > > > > there might be differences among different
> > > > > (Analog) sensors. But all those sensors specify Inactivity (and Activity) as a
> > > > > IIO_EV_TYPE_THRESH with directions IIO_MOD_X_OR_Y_OR_Z.
> > > > > Given the above, I implemented Activity and Inactivity events as
> > > > > IIO_EV_TYPE_THRESH,
> > > > > now I'm a bit confused.
> > > >
> > > > Hmm. This is one reason I think we need more documentation as those
> > > > seem to be wrong. Clearly the event is a threshold on a magnitude of
> > > > the acceleration, not the signed value as it applies in both directions.
> > > >
> > > > >
> > > > > Q: Why is this different for the ADXL345?
> > > >
> > > > Because we got it wrong for these others it seems unless they genuinely
> > > > have directional events - which typically means separate positive and
> > > > negative thresholds. Right now those events are strictly speaking
> > > > only apply to positive accelerations.
> > > >
> > > > >
> > > > > Q: If I implement Activity / Inactivity analogous to the e.g. a
> > > > > ADXL380, then shouldn't it be IIO_EV_TYPE_THRESH with
> > > > > IIO_MOD_X_OR_Y_OR_Z? Why not?
> > > > >
> > > >
> > > > I think we got it wrong for that part. Going forwards we should work
> > > > on getting it (more) correct.
> > > >
> > >
> > > I understand the point better now.
> > >
> > > > >
> > > > > 3. For the ADXL345, a Freefall signal is on all axis lower than
> > > > > threshold (magnitude). Thus I push a IIO_MOD_X_AND_Y_AND_Z to a
> > > > > separate
> > > > > fake channel. Inactivity will be like Freefall independent of the axis.
> > > > > The ADXL345 Activity can be configured by axis, as also the event will
> > > > > respect the axis information.
> > > > >
> > > > > Q: Setting up the "fake channel" to particuarly push to
> > > > > IIO_MOD_X_AND_Y_AND_Z, I probably better should also evaluate
> > > > > IIO_MOD_X_AND_Y_AND_Z in write_event_config(), write_event_value(),
> > > > > etc. rather than evaluating IIO_MOD_-types as I'm currently
> > > > > doing?
> > > >
> > > > Yes. That sounds correct for events on these 'fake' channels.
> > > > The enable and the thresholds should all be on these fake channels
> > > > assuming they don't have different thresholds on a per axis basis
> > > > (if they do things get tricky to represent).
> > > >
> > > > >
> > > > > Q: Activity probably remains in the regular channels for the corresponding axis?
> > > >
> > > > Yes. That is easier to handle as OR of channels is very similar
> > > > to separate interrupts etc.
> > > >
> > >
> > > I think I should definitely evaluate the IIO_MOD_X_AND_Y_AND_Z here,
> > > to take advantage
> > > of the fake channel.
> > >
> > > > >
> > > > >
> > > > > 4. I implemented functions like adxl345_write_event_config(),
> > > > > adxl345_write_event_value() or corresponding
> > > > > readers, as follows
> > > > > - THRESH/rising: Activity
> > > > > - THRESH/falling: Inactivity
> > > > > - MAG/falling: Freefall
> > > > >
> > > > > If I change Activity and Inactivity to be both of type MAG, I will end
> > > > > up with MAG/falling to indicate Freefall or equally Inactivity.
> > > > > Both on the IIO_MOD_X_AND_Y_AND_Z channel. I admit (ab)using the
> > > > > IIO_EV_TYPEs to solve my combinatorial issues for event configuration
> > > > > is probably not as supposed to be.
> > > >
> > > > Ah.. This I'd missed. I'm fairly sure we didn't hit this for (some) previous
> > > > inactivity sensors because they were always rate of change based, (AC)
> > > > rather than DC. DC is relatively unlikely to be used in practice because
> > > > we can't set the threshold as less than 1G because of gravity. It is a
> > > > bit odd that the device supports both DC and AC on this detector.
> > > >
> > > > I wonder why.... Might be to enable partial axis monitoring. e.g.
> > > > If a device is flat on a table we only look for inactivity on the non
> > > > vertical axis when doing DC coupling. (as we have 1g always in the other
> > > > axis).
> > > >
> > >
> > > Thank you for clarifying your position in the other mail focussed on
> > > the AC- / DC-coupling
> > > topic. It helped me in better understanding what you actually expect
> > > here. Although I'll
> > > probably need to re-read it some times, before implementing something.
> >
> > I definitely need to find time to write some docs on this. Mad few
> > weeks coming up but maybe I'll get some time on a plane or at an airport
> > to try a first draft.
> >
> > >
> > > > > Given you still ask me to do Inactivity and Freefall as MAG/falling
> > > > > with IIO_MOD_X_AND_Y_AND_Z. The difference between both IMHO,
> > > > > is that Activity and Inactivity for the ADXL345 indicate sensor state
> > > > > changes, where Freefall indicates the particular event. The
> > > > > sensor is either in "active" or "standby/inactive", where Freefall
> > > > > just triggers and then retriggers and retriggers...
> > > >
> > > > Maybe. The datasheet is annoyingly vague on these but indeed there
> > > > is no event for no longer falling.
> > > >
> > > > >
> > > > > Q: What is the method to distinguish several similar IIO events, e.g.
> > > > > to tag them somehow one as Freefall, the other one as Inactivity?
> > > >
> > > > In general we should not be able to do that. Long ago we made the decision
> > > > to have compact event codes so they don't allow for an index on a particular
> > > > combination of channel number and modifier. This is mainly because
> > > > there is limited purpose. If one event is triggered, then we have
> > > > to process anyway so we can just look at the value for 'how far' it was
> > > > triggered. I.e. if we thought DC inactivity was triggered, we can just
> > > > check free fall as well. (It gets a little more fiddly because of _period
> > > > etc which is why they may actually make sense here).
> > > >
> > > > The virtual (combination OR/AND) was added on top of that later and has
> > > > made the connection looser.
> > > >
> > > > In theory we could use labels + index for the virtual channels to achieve
> > > > separate control attributes and be able to tell which was which but
> > > > that would be new ABI. I'm not sure how much use this stuff is already
> > > > getting from userspace applications and hence whether this would be
> > > > a big problem to get supported.
> > > >
> > > > That would give us something like
> > > >
> > > > iio\:device0/in_accel0_x&y&z_label freefall
> > > > iio\:device0/in_accel1_x&y&z_label inactivity
> > > > iio\:device0/events/in_accel0_x&y&z_en etc
> > > > iio\:device0/events/in_accel1_x&y&z_en etc
> > > >
> > > > I don't like it much because it then doesn't generalize to the case
> > > > of multiple sensors on each axis (there are multi range parts that do that).
> > > > That case is pretty rare though (I think we only have such sensor supported!)
> > > > However, it's currently the only option we have to fully represent this.
> > > >
>
> Kind of the naive approach to allow for extending everything, but it probably
> does not fit together with the existing sensor ABI.
Agreed.
>
> > > > An alternative here might be to assess if anyone is really going to use
> > > > DC coupled inactivity detection (because of the 1g problem) and hence whether
> > > > we want to support that at all?
> > > >
>
> I'd rather drop free fall then. Activity and inactivity, linked to
> gether work well for the power saving
> thing. I guess that's probably also more of general use. Not sure how
> usefull free fall is here.
>
> > > > Yet another alternative might be to configure it purely based on the period
> > > > provided. If short use freefall, if long use inactivity. (I don't like this
> > > > one though as it doesn't really fit with usecase!)
> > > >
>
> That's either free fall or the other? Ideally, I'd like to represent
> what's possible with this sensor.
That's the only option if using the period to control it. You can't have
one event with two periods.
> Since I'm not really familiar with how those sensors are used, for me
> all features are equally
> important. I mean, the sensor offers enabling act/inact and enabling
> free fall if one likes.
Sure.
>
> > > > Sorry for lack of clarity on this. These events are tricky and
> > > > it takes me a while to get the whole situation back into my head (and I missing
> > > > things like inactivity and freefall being very similar here!)
> > > >
>
> Ideally I'd like to implement support for all features of the sensor.
> I can understand if some corner case features are too individual to
> support them. So, at least the main features.
>
> From what I see in the code, configuring threshold and period, there
> might be differences and it can be easy to distinguish in
> read/write_event_value() like functions. Where
> read/write_event_config() will be more difficult.
> So, for the event, when I receive it I have knowledge of the exact
> event type, free fall or inactivity. I'm losing this information since
> it can currently not be conveyed over the channel. Might be that the
> ADXL345 is a very particular case. Might also be, that this shows a
> more general shortcoming which should be addressed.
I'm sure there will be other sensors that have similar events.
How common is hard to say though.
>
> Equally I could say, there is just a MAG event, so turning on
> inactivity or free fall would result in the same event. The tricky
> case then is if someone wants inactivity AND free fall. In such case
> it would be a inact_or_free_fall event. Not sure about. Probably
> rather not.
It's just a threshold on falling magnitude of signal. What that means depends
on the sensor type and other settings (here the period). Event codes
are the same, but without the settings those are pretty meaningless.
>
> Q: Still, what about the direction - I see, where activity is using
> IIO_EV_DIR_RISING, inactivity uses _DIR_FALLING. Free fall now uses
> _DIR_FALLING. I'm a bit unsure if this is just a choice. Could I use
> _DIR_EITHER here? Or why not? I mean, actually it's not possible to
> detect fall into one direction, it's simply "in a fall". We cannot
> tell which axis we cannot tell which direction.
Not about direction in sense of x/y/z, but direction of the signal.
They are definitely triggered when the |X| > thresh transitions
to |X| < thresh. So direction is falling as value |X| is getting smaller.
The choice reflects the physical thing being measured.
>
> > > > If you have time to take a look at what userspace is currently doing with
> > > > these events (iio_sensor_proxy etc) that might help us decide what works.
> > > >
>
> This might be generally a good option. Honestly, I'd like to mess
> still with some other sensors,
> first.
Make sense.
J
>
> Best,
> L
>
> > >
> > > Just as a quick response here (or perhaps just to rule it out)..
> > >
> > > Actually, I can spot as MAG-similar event types:
> > > - IIO_EV_TYPE_MAG
> > > - IIO_EV_TYPE_MAG_ADAPTIVE
> > > - IIO_EV_TYPE_CHANGE
> > > - IIO_EV_TYPE_MAG_REFERENCED
> > >
> > > For instance the last one is only used in a single sensor. Is there a
> > > chance to put, say, freefall into one of the other "MAG-like" sensor
> > > types. Alternatively, what about putting Activity/Inactivity under
> > > say, "MAG_REFERENCED"? This might seem to be a stupid question, since
> > > I can imagine you have a clear definition of those in mind. But if
> > > this was possible. It would solve this problem easily.
> >
> > The ABI docs do provide some definitions of these.
> >
> > Free fall is definitely straight forward TYPE MAG. It precisely aligns
> > with that definition as a threshold on the per axis magnitudes.
> >
> > MAG_ADAPTIVE is meant for a case where the event is on the magnitude
> > relative to a slow moving adaptive baseline (usually a low pass filtered
> > version of the signal but can include corrective jumps - IIRC these turn
> > up for magnetic sensors). This differs from a rate of change threshold
> > because it's not simply a difference between current and earlier signals
> > but rather current and some heavily filtered earlier signal.
> > These matter in cases where we have a slow changing baseline such as
> > coming into proximity with metal in the environment when using a magnetometer
> > for orientation detection.
> >
> > MAG_REFERENCED is a weird one. This was done for a nice IMU that had
> > the ability to estimate orientation and so remove the acceleration due
> > to gravity and then apply thresholds to the magnitude of the remaining
> > accelerations. The AC filtering on your part is is a 'cheap' way
> > to achieve roughly the equivalent of that for the activity detection at least
> > where we are removing the 'nothing happening value'.
> > (it is less clear for the inactivity case though that will still include
> > g, so maybe it is still somewhat valid).
> >
> > CHANGE is IIRC only for counting channels (so far anyway).
> >
> > So none of the more esoteric forms of them fit for this DC coupled
> > inactivity monitor or freefall. Both of them are the same type of event
> > just differing in filters applied.
> >
> > >
> > > If not, then I'll need to think of it and come up with a more
> > > elaborate approach. The label + index approach seems to be a bit
> > > complex. Going somehow by the time constraints in the event.. I need
> > > to play with that in the code to build up an oppinion, I guess.
> >
> > The time constraints thing falls down on the basis that it would
> > be logical to have freefall enabled (for parking any moving parts - those
> > used to exist mainly to stop hard disks but maybe there are other use cases?)
> > and inactivity for power saving with a much longer timescale.
> >
> > Freefall used to be fun because the aim was to get moving parts into
> > a safe state before the device hit the ground. So that meant if you dropped
> > a device with a harddisk from higher up, it sometimes had a better chance
> > of surviving. I'm not sure if anyone cares any more! Will be interesting
> > to see if that feature goes away on new devices.
> >
> > Jonathan
> >
> > >
> > > Best,
> > > L
> > >
> > > > Jonathan
> > > >
> > > > >
> > > > > Best,
> > > > > L
> > > > >
> > > > > > > + .dir = IIO_EV_DIR_RISING,
> > > > > > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > > > > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> > > > > > > + },
> > > > > > > {
> > > > > > > /* single tap */
> > > > > > > .type = IIO_EV_TYPE_GESTURE,
> > > > > > > @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > > > > > return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> > > > > > > }
> > > > > > >
> > > > > > Jonathan
> > > > > >
> > > > > >
> > > >
> >
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-05-07 19:48 ` Jonathan Cameron
@ 2025-05-07 21:40 ` Lothar Rubusch
2025-05-15 7:22 ` Jonathan Cameron
0 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-05-07 21:40 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Wed, May 7, 2025 at 9:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Wed, 7 May 2025 00:37:43 +0200
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > Hi Jonathan,
> > Still some questions and thoughts down below.
> >
> > On Mon, May 5, 2025 at 2:37 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > >
> > > On Sun, 4 May 2025 19:47:55 +0200
> > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > >
> > > > On Sun, May 4, 2025 at 12:29 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > > >
> > > > > On Thu, 1 May 2025 00:53:32 +0200
> > > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > > >
> > > > > > Hi Jonathan - Hi IIO list,
> > > > > >
> > > > > > Please, find some (many) questions inlined down below. Appologies for
> > > > > > the separate
> > > > > > channels last time and not right away fixing them up as array. I did
> > > > > > not want to make extra work.
> > > > > >
> > > > > > On Sun, Apr 27, 2025 at 2:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > > > > >
> > > > > > > On Mon, 21 Apr 2025 22:06:38 +0000
> > > > > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > > > > >
> > > > > > > > Make the sensor detect and issue interrupts at activity. Activity
> > > > > > > > events are configured by a threshold stored in regmap cache. Initialize
> > > > > > > > the activity threshold register to a reasonable default value in probe.
> > > > > > > > The value is taken from the older ADXL345 input driver, to provide a
> > > > > > > > similar behavior. Reset the activity/inactivity direction enabling
> > > > > > > > register in probe. Reset and initialization shall bring the sensor in a
> > > > > > > > defined initial state to prevent dangling settings when warm restarting
> > > > > > > > the sensor.
> > > > > > > >
> > > > > > > > Activity, ODR configuration together with the range setting prepare the
> > > > > > > > activity/inactivity hystersesis setup, implemented in a follow up patch.
> > > > > > > >
> > > > > > > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > > > > > > ---
> > > > > > > > drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> > > > > > > > 1 file changed, 214 insertions(+), 3 deletions(-)
> > > > > > > >
> > > > > > > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > > > > > > index 80b5b8402ced..680981609d83 100644
> > > > > > > > --- a/drivers/iio/accel/adxl345_core.c
> > > > > > > > +++ b/drivers/iio/accel/adxl345_core.c
> > > > > > > > @@ -36,11 +36,16 @@
> > > > > > > > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > > > > > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > > > > > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > > > > > > +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > > > > > >
> > > > > > > > #define ADXL345_TAP_Z_EN BIT(0)
> > > > > > > > #define ADXL345_TAP_Y_EN BIT(1)
> > > > > > > > #define ADXL345_TAP_X_EN BIT(2)
> > > > > > > >
> > > > > > > > +#define ADXL345_ACT_Z_EN BIT(4)
> > > > > > > > +#define ADXL345_ACT_Y_EN BIT(5)
> > > > > > > > +#define ADXL345_ACT_X_EN BIT(6)
> > > > > > > > +
> > > > > > > > /* single/double tap */
> > > > > > > > enum adxl345_tap_type {
> > > > > > > > ADXL345_SINGLE_TAP,
> > > > > > > > @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> > > > > > > > [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> > > > > > > > };
> > > > > > > >
> > > > > > > > +/* activity/inactivity */
> > > > > > > > +enum adxl345_activity_type {
> > > > > > > > + ADXL345_ACTIVITY,
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +static const unsigned int adxl345_act_int_reg[] = {
> > > > > > > > + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +static const unsigned int adxl345_act_thresh_reg[] = {
> > > > > > > > + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > enum adxl345_odr {
> > > > > > > > ADXL345_ODR_0P10HZ = 0,
> > > > > > > > ADXL345_ODR_0P20HZ,
> > > > > > > > @@ -154,6 +172,13 @@ struct adxl345_state {
> > > > > > > > };
> > > > > > > >
> > > > > > > > static struct iio_event_spec adxl345_events[] = {
> > > > > > > > + {
> > > > > > > > + /* activity */
> > > > > > > > + .type = IIO_EV_TYPE_THRESH,
> > > > > > >
> > > > > > > Is this a threshold, or a magnitude? I'd expect an activity detector
> > > > > > > to be magnitude as it doesn't care which way up the sensor is.
> > > > > > >
> > > > > >
> > > > > > This is touching the main points still unclear to me. I tried to put
> > > > > > this into the
> > > > > > following questions. Could you please clarify?
> > > > >
> > > > > There are some corners where it gets messy. When I have time
> > > > > (not for a month or so) I'll try and write some proper docs for this.
> > > > >
> > > > > >
> > > > > > 1. Given a measurement "val", and a configured threshold "thr".
> > > > > > A "rising" for IIO_EV_TYPE_THRESH means: val > thr
> > > > > > where a "rising" for IIO_EV_TYPE_MAG means something like: val > |thr|
> > > > > >
> > > > > > Q: Do I understand this correctly now?
> > > > >
> > > > > Yes that is the intended difference.
> > > > >
> > > > > >
> > > > > > Q: Is this documented somewhere (especially for reviewing further
> > > > > > EV_TYPE fields)?
> > > > >
> > > > > Only in the ABI documentation in
> > > > > Documentation/ABI/testing/sysfs-bus-iio
> > > > > This is definitely something we should look to improve with some
> > > > > docs beyond simply what the ABI is. That ABI is focused on
> > > > > how the interrupt is triggered, not so much on what that means
> > > > > wrt to freefall etc.
> > > > >
> > > > >
> > > > > >
> > > > > > Q: I wonder if I missed this for the Tap events. Going by this
> > > > > > definition, then actually the
> > > > > > tap events should be rather MAG events, too. Right?
> > > > >
> > > > > The tap events have their own type (gesture) because they are way
> > > > > more complex than a simple threshold whether on magnitude or
> > > > > the signed value. So those should be fine as type GESTURE.
> > > > >
> > > >
> > > > I was aware of that. Actually, the case of GESTURE is a bit
> > > > particular. On the one side, I
> > > > understand having a distinction between THRESH events and MAG events.
> > > > Hence, a classification of the type of event in terms of a measurement
> > > > value triggering event condition.
> > > > This concept seems actually to be clear.
> > > >
> > > > GESTURE to me then seems a bit like a "wildcard type covering all kind
> > > > of tap events". I mean,
> > > > saying tap detection, single tap, double tap, tripple tap, and so on
> > > > tap go into category GESTURE - naively could also mean, then do a
> > > > freefall type as well (?).
> > >
> > > Nope. Because freefall has a clear definition that aligns with
> > > the events that we have for other types of sensor.
> > >
> > > You are right that gesture is a wild card. I resisted it for a long
> > > time but there is just no sane way to handle tap events alongside
> > > the sort of things we get on any other sensor type. Having looked
> > > at a bunch of them they can be anything from straight magnitude thresholds
> > > with time windows to things based on a mixture of jerk (rate of change
> > > of acceleration) and other stuff. The only thing that kind of close
> > > to this is pedometer step events, but we handle those as a counting
> > > channel rather than an event as time of each is less interesting than
> > > how many have happened. However as noted below we do have the CHANGE
> > > type of event specifically to account for those (which is ugly).
> > >
> > > >
> > > > [this is rather meant as a bit of a provocative rhetoric question than
> > > > a proposal]
> > > >
> > > > > >
> > > > > >
> > > > > > 2. I oriented myself mostly by reading other drivers, for instance the
> > > > > > ADXL367, the ADXL372, or also the more recent ADXL380. I am aware that
> > > > > > there might be differences among different
> > > > > > (Analog) sensors. But all those sensors specify Inactivity (and Activity) as a
> > > > > > IIO_EV_TYPE_THRESH with directions IIO_MOD_X_OR_Y_OR_Z.
> > > > > > Given the above, I implemented Activity and Inactivity events as
> > > > > > IIO_EV_TYPE_THRESH,
> > > > > > now I'm a bit confused.
> > > > >
> > > > > Hmm. This is one reason I think we need more documentation as those
> > > > > seem to be wrong. Clearly the event is a threshold on a magnitude of
> > > > > the acceleration, not the signed value as it applies in both directions.
> > > > >
> > > > > >
> > > > > > Q: Why is this different for the ADXL345?
> > > > >
> > > > > Because we got it wrong for these others it seems unless they genuinely
> > > > > have directional events - which typically means separate positive and
> > > > > negative thresholds. Right now those events are strictly speaking
> > > > > only apply to positive accelerations.
> > > > >
> > > > > >
> > > > > > Q: If I implement Activity / Inactivity analogous to the e.g. a
> > > > > > ADXL380, then shouldn't it be IIO_EV_TYPE_THRESH with
> > > > > > IIO_MOD_X_OR_Y_OR_Z? Why not?
> > > > > >
> > > > >
> > > > > I think we got it wrong for that part. Going forwards we should work
> > > > > on getting it (more) correct.
> > > > >
> > > >
> > > > I understand the point better now.
> > > >
> > > > > >
> > > > > > 3. For the ADXL345, a Freefall signal is on all axis lower than
> > > > > > threshold (magnitude). Thus I push a IIO_MOD_X_AND_Y_AND_Z to a
> > > > > > separate
> > > > > > fake channel. Inactivity will be like Freefall independent of the axis.
> > > > > > The ADXL345 Activity can be configured by axis, as also the event will
> > > > > > respect the axis information.
> > > > > >
> > > > > > Q: Setting up the "fake channel" to particuarly push to
> > > > > > IIO_MOD_X_AND_Y_AND_Z, I probably better should also evaluate
> > > > > > IIO_MOD_X_AND_Y_AND_Z in write_event_config(), write_event_value(),
> > > > > > etc. rather than evaluating IIO_MOD_-types as I'm currently
> > > > > > doing?
> > > > >
> > > > > Yes. That sounds correct for events on these 'fake' channels.
> > > > > The enable and the thresholds should all be on these fake channels
> > > > > assuming they don't have different thresholds on a per axis basis
> > > > > (if they do things get tricky to represent).
> > > > >
> > > > > >
> > > > > > Q: Activity probably remains in the regular channels for the corresponding axis?
> > > > >
> > > > > Yes. That is easier to handle as OR of channels is very similar
> > > > > to separate interrupts etc.
> > > > >
> > > >
> > > > I think I should definitely evaluate the IIO_MOD_X_AND_Y_AND_Z here,
> > > > to take advantage
> > > > of the fake channel.
> > > >
> > > > > >
> > > > > >
> > > > > > 4. I implemented functions like adxl345_write_event_config(),
> > > > > > adxl345_write_event_value() or corresponding
> > > > > > readers, as follows
> > > > > > - THRESH/rising: Activity
> > > > > > - THRESH/falling: Inactivity
> > > > > > - MAG/falling: Freefall
> > > > > >
> > > > > > If I change Activity and Inactivity to be both of type MAG, I will end
> > > > > > up with MAG/falling to indicate Freefall or equally Inactivity.
> > > > > > Both on the IIO_MOD_X_AND_Y_AND_Z channel. I admit (ab)using the
> > > > > > IIO_EV_TYPEs to solve my combinatorial issues for event configuration
> > > > > > is probably not as supposed to be.
> > > > >
> > > > > Ah.. This I'd missed. I'm fairly sure we didn't hit this for (some) previous
> > > > > inactivity sensors because they were always rate of change based, (AC)
> > > > > rather than DC. DC is relatively unlikely to be used in practice because
> > > > > we can't set the threshold as less than 1G because of gravity. It is a
> > > > > bit odd that the device supports both DC and AC on this detector.
> > > > >
> > > > > I wonder why.... Might be to enable partial axis monitoring. e.g.
> > > > > If a device is flat on a table we only look for inactivity on the non
> > > > > vertical axis when doing DC coupling. (as we have 1g always in the other
> > > > > axis).
> > > > >
> > > >
> > > > Thank you for clarifying your position in the other mail focussed on
> > > > the AC- / DC-coupling
> > > > topic. It helped me in better understanding what you actually expect
> > > > here. Although I'll
> > > > probably need to re-read it some times, before implementing something.
> > >
> > > I definitely need to find time to write some docs on this. Mad few
> > > weeks coming up but maybe I'll get some time on a plane or at an airport
> > > to try a first draft.
> > >
> > > >
> > > > > > Given you still ask me to do Inactivity and Freefall as MAG/falling
> > > > > > with IIO_MOD_X_AND_Y_AND_Z. The difference between both IMHO,
> > > > > > is that Activity and Inactivity for the ADXL345 indicate sensor state
> > > > > > changes, where Freefall indicates the particular event. The
> > > > > > sensor is either in "active" or "standby/inactive", where Freefall
> > > > > > just triggers and then retriggers and retriggers...
> > > > >
> > > > > Maybe. The datasheet is annoyingly vague on these but indeed there
> > > > > is no event for no longer falling.
> > > > >
> > > > > >
> > > > > > Q: What is the method to distinguish several similar IIO events, e.g.
> > > > > > to tag them somehow one as Freefall, the other one as Inactivity?
> > > > >
> > > > > In general we should not be able to do that. Long ago we made the decision
> > > > > to have compact event codes so they don't allow for an index on a particular
> > > > > combination of channel number and modifier. This is mainly because
> > > > > there is limited purpose. If one event is triggered, then we have
> > > > > to process anyway so we can just look at the value for 'how far' it was
> > > > > triggered. I.e. if we thought DC inactivity was triggered, we can just
> > > > > check free fall as well. (It gets a little more fiddly because of _period
> > > > > etc which is why they may actually make sense here).
> > > > >
> > > > > The virtual (combination OR/AND) was added on top of that later and has
> > > > > made the connection looser.
> > > > >
> > > > > In theory we could use labels + index for the virtual channels to achieve
> > > > > separate control attributes and be able to tell which was which but
> > > > > that would be new ABI. I'm not sure how much use this stuff is already
> > > > > getting from userspace applications and hence whether this would be
> > > > > a big problem to get supported.
> > > > >
> > > > > That would give us something like
> > > > >
> > > > > iio\:device0/in_accel0_x&y&z_label freefall
> > > > > iio\:device0/in_accel1_x&y&z_label inactivity
> > > > > iio\:device0/events/in_accel0_x&y&z_en etc
> > > > > iio\:device0/events/in_accel1_x&y&z_en etc
> > > > >
> > > > > I don't like it much because it then doesn't generalize to the case
> > > > > of multiple sensors on each axis (there are multi range parts that do that).
> > > > > That case is pretty rare though (I think we only have such sensor supported!)
> > > > > However, it's currently the only option we have to fully represent this.
> > > > >
> >
> > Kind of the naive approach to allow for extending everything, but it probably
> > does not fit together with the existing sensor ABI.
>
> Agreed.
>
> >
> > > > > An alternative here might be to assess if anyone is really going to use
> > > > > DC coupled inactivity detection (because of the 1g problem) and hence whether
> > > > > we want to support that at all?
> > > > >
> >
> > I'd rather drop free fall then. Activity and inactivity, linked to
> > gether work well for the power saving
> > thing. I guess that's probably also more of general use. Not sure how
> > usefull free fall is here.
> >
> > > > > Yet another alternative might be to configure it purely based on the period
> > > > > provided. If short use freefall, if long use inactivity. (I don't like this
> > > > > one though as it doesn't really fit with usecase!)
> > > > >
> >
> > That's either free fall or the other? Ideally, I'd like to represent
> > what's possible with this sensor.
> That's the only option if using the period to control it. You can't have
> one event with two periods.
>
> > Since I'm not really familiar with how those sensors are used, for me
> > all features are equally
> > important. I mean, the sensor offers enabling act/inact and enabling
> > free fall if one likes.
>
> Sure.
>
> >
> > > > > Sorry for lack of clarity on this. These events are tricky and
> > > > > it takes me a while to get the whole situation back into my head (and I missing
> > > > > things like inactivity and freefall being very similar here!)
> > > > >
> >
> > Ideally I'd like to implement support for all features of the sensor.
> > I can understand if some corner case features are too individual to
> > support them. So, at least the main features.
> >
> > From what I see in the code, configuring threshold and period, there
> > might be differences and it can be easy to distinguish in
> > read/write_event_value() like functions. Where
> > read/write_event_config() will be more difficult.
> > So, for the event, when I receive it I have knowledge of the exact
> > event type, free fall or inactivity. I'm losing this information since
> > it can currently not be conveyed over the channel. Might be that the
> > ADXL345 is a very particular case. Might also be, that this shows a
> > more general shortcoming which should be addressed.
>
> I'm sure there will be other sensors that have similar events.
> How common is hard to say though.
>
> >
> > Equally I could say, there is just a MAG event, so turning on
> > inactivity or free fall would result in the same event. The tricky
> > case then is if someone wants inactivity AND free fall. In such case
> > it would be a inact_or_free_fall event. Not sure about. Probably
> > rather not.
>
> It's just a threshold on falling magnitude of signal. What that means depends
> on the sensor type and other settings (here the period). Event codes
> are the same, but without the settings those are pretty meaningless.
>
> >
> > Q: Still, what about the direction - I see, where activity is using
> > IIO_EV_DIR_RISING, inactivity uses _DIR_FALLING. Free fall now uses
> > _DIR_FALLING. I'm a bit unsure if this is just a choice. Could I use
> > _DIR_EITHER here? Or why not? I mean, actually it's not possible to
> > detect fall into one direction, it's simply "in a fall". We cannot
> > tell which axis we cannot tell which direction.
>
> Not about direction in sense of x/y/z, but direction of the signal.
>
> They are definitely triggered when the |X| > thresh transitions
> to |X| < thresh. So direction is falling as value |X| is getting smaller.
>
> The choice reflects the physical thing being measured.
>
> >
> > > > > If you have time to take a look at what userspace is currently doing with
> > > > > these events (iio_sensor_proxy etc) that might help us decide what works.
> > > > >
> >
> > This might be generally a good option. Honestly, I'd like to mess
> > still with some other sensors,
> > first.
>
> Make sense.
Could the following solve the situation: sharing the same event,
distinguishing by cached information in the driver.
Given, activity/inactivity and free-fall will have all MAG events,
with activity is RISING, inactivity FALLING, and free-fall also
FALLING.
If only activity/inactivity or only free-fall is enabled (or none),
there will be the particular MAG events. If now activity/inactivity
and free-fall are enabled, all will send the same MAG event. That
means some activity/inactivity event will then be shared with
free-fall.
The following ideas now apply only to the case both are enabled:
act/inact and free-fall.
Since the driver's ISR catches the sensor interrupt and evaluates, the
driver has knowledge about the exact type of event. One idea then is,
to keep this cached in the driver. Since activity / inactivity
indicate a state switch (happening rarely), I can distinguish a state
switch from free-fall by keeping activity/inactivity state switch as
cached value(s) in the driver. Here, I can think of keeping the
activity state, or indicate state switch, or the like.
Another idea is not even distinguish (so much). Since practically
free-fall is issued and re-issued quite often, most of the MAG FALLING
events might be free-fall. I can default generally to free-fall if
both is enabled. I'm not sure if activity/inactivity indication may
lead to anything outside, also the free-fall could imply being
interpreted of a sort of inactivity/activity type.
Let me know, what you think.
Best,
L
>
> J
>
> >
> > Best,
> > L
> >
> > > >
> > > > Just as a quick response here (or perhaps just to rule it out)..
> > > >
> > > > Actually, I can spot as MAG-similar event types:
> > > > - IIO_EV_TYPE_MAG
> > > > - IIO_EV_TYPE_MAG_ADAPTIVE
> > > > - IIO_EV_TYPE_CHANGE
> > > > - IIO_EV_TYPE_MAG_REFERENCED
> > > >
> > > > For instance the last one is only used in a single sensor. Is there a
> > > > chance to put, say, freefall into one of the other "MAG-like" sensor
> > > > types. Alternatively, what about putting Activity/Inactivity under
> > > > say, "MAG_REFERENCED"? This might seem to be a stupid question, since
> > > > I can imagine you have a clear definition of those in mind. But if
> > > > this was possible. It would solve this problem easily.
> > >
> > > The ABI docs do provide some definitions of these.
> > >
> > > Free fall is definitely straight forward TYPE MAG. It precisely aligns
> > > with that definition as a threshold on the per axis magnitudes.
> > >
> > > MAG_ADAPTIVE is meant for a case where the event is on the magnitude
> > > relative to a slow moving adaptive baseline (usually a low pass filtered
> > > version of the signal but can include corrective jumps - IIRC these turn
> > > up for magnetic sensors). This differs from a rate of change threshold
> > > because it's not simply a difference between current and earlier signals
> > > but rather current and some heavily filtered earlier signal.
> > > These matter in cases where we have a slow changing baseline such as
> > > coming into proximity with metal in the environment when using a magnetometer
> > > for orientation detection.
> > >
> > > MAG_REFERENCED is a weird one. This was done for a nice IMU that had
> > > the ability to estimate orientation and so remove the acceleration due
> > > to gravity and then apply thresholds to the magnitude of the remaining
> > > accelerations. The AC filtering on your part is is a 'cheap' way
> > > to achieve roughly the equivalent of that for the activity detection at least
> > > where we are removing the 'nothing happening value'.
> > > (it is less clear for the inactivity case though that will still include
> > > g, so maybe it is still somewhat valid).
> > >
> > > CHANGE is IIRC only for counting channels (so far anyway).
> > >
> > > So none of the more esoteric forms of them fit for this DC coupled
> > > inactivity monitor or freefall. Both of them are the same type of event
> > > just differing in filters applied.
> > >
> > > >
> > > > If not, then I'll need to think of it and come up with a more
> > > > elaborate approach. The label + index approach seems to be a bit
> > > > complex. Going somehow by the time constraints in the event.. I need
> > > > to play with that in the code to build up an oppinion, I guess.
> > >
> > > The time constraints thing falls down on the basis that it would
> > > be logical to have freefall enabled (for parking any moving parts - those
> > > used to exist mainly to stop hard disks but maybe there are other use cases?)
> > > and inactivity for power saving with a much longer timescale.
> > >
> > > Freefall used to be fun because the aim was to get moving parts into
> > > a safe state before the device hit the ground. So that meant if you dropped
> > > a device with a harddisk from higher up, it sometimes had a better chance
> > > of surviving. I'm not sure if anyone cares any more! Will be interesting
> > > to see if that feature goes away on new devices.
> > >
> > > Jonathan
> > >
> > > >
> > > > Best,
> > > > L
> > > >
> > > > > Jonathan
> > > > >
> > > > > >
> > > > > > Best,
> > > > > > L
> > > > > >
> > > > > > > > + .dir = IIO_EV_DIR_RISING,
> > > > > > > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > > > > > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> > > > > > > > + },
> > > > > > > > {
> > > > > > > > /* single tap */
> > > > > > > > .type = IIO_EV_TYPE_GESTURE,
> > > > > > > > @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > > > > > > return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> > > > > > > > }
> > > > > > > >
> > > > > > > Jonathan
> > > > > > >
> > > > > > >
> > > > >
> > >
> >
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 08/11] iio: accel: adxl345: add activity event feature
2025-05-07 21:40 ` Lothar Rubusch
@ 2025-05-15 7:22 ` Jonathan Cameron
0 siblings, 0 replies; 32+ messages in thread
From: Jonathan Cameron @ 2025-05-15 7:22 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Wed, 7 May 2025 23:40:22 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> On Wed, May 7, 2025 at 9:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Wed, 7 May 2025 00:37:43 +0200
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > Hi Jonathan,
> > > Still some questions and thoughts down below.
> > >
> > > On Mon, May 5, 2025 at 2:37 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > >
> > > > On Sun, 4 May 2025 19:47:55 +0200
> > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > >
> > > > > On Sun, May 4, 2025 at 12:29 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > > > >
> > > > > > On Thu, 1 May 2025 00:53:32 +0200
> > > > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > > > >
> > > > > > > Hi Jonathan - Hi IIO list,
> > > > > > >
> > > > > > > Please, find some (many) questions inlined down below. Appologies for
> > > > > > > the separate
> > > > > > > channels last time and not right away fixing them up as array. I did
> > > > > > > not want to make extra work.
> > > > > > >
> > > > > > > On Sun, Apr 27, 2025 at 2:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > > > > > >
> > > > > > > > On Mon, 21 Apr 2025 22:06:38 +0000
> > > > > > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > > > > > >
> > > > > > > > > Make the sensor detect and issue interrupts at activity. Activity
> > > > > > > > > events are configured by a threshold stored in regmap cache. Initialize
> > > > > > > > > the activity threshold register to a reasonable default value in probe.
> > > > > > > > > The value is taken from the older ADXL345 input driver, to provide a
> > > > > > > > > similar behavior. Reset the activity/inactivity direction enabling
> > > > > > > > > register in probe. Reset and initialization shall bring the sensor in a
> > > > > > > > > defined initial state to prevent dangling settings when warm restarting
> > > > > > > > > the sensor.
> > > > > > > > >
> > > > > > > > > Activity, ODR configuration together with the range setting prepare the
> > > > > > > > > activity/inactivity hystersesis setup, implemented in a follow up patch.
> > > > > > > > >
> > > > > > > > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > > > > > > > ---
> > > > > > > > > drivers/iio/accel/adxl345_core.c | 217 ++++++++++++++++++++++++++++++-
> > > > > > > > > 1 file changed, 214 insertions(+), 3 deletions(-)
> > > > > > > > >
> > > > > > > > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > > > > > > > index 80b5b8402ced..680981609d83 100644
> > > > > > > > > --- a/drivers/iio/accel/adxl345_core.c
> > > > > > > > > +++ b/drivers/iio/accel/adxl345_core.c
> > > > > > > > > @@ -36,11 +36,16 @@
> > > > > > > > > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > > > > > > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > > > > > > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > > > > > > > +#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > > > > > > >
> > > > > > > > > #define ADXL345_TAP_Z_EN BIT(0)
> > > > > > > > > #define ADXL345_TAP_Y_EN BIT(1)
> > > > > > > > > #define ADXL345_TAP_X_EN BIT(2)
> > > > > > > > >
> > > > > > > > > +#define ADXL345_ACT_Z_EN BIT(4)
> > > > > > > > > +#define ADXL345_ACT_Y_EN BIT(5)
> > > > > > > > > +#define ADXL345_ACT_X_EN BIT(6)
> > > > > > > > > +
> > > > > > > > > /* single/double tap */
> > > > > > > > > enum adxl345_tap_type {
> > > > > > > > > ADXL345_SINGLE_TAP,
> > > > > > > > > @@ -64,6 +69,19 @@ static const unsigned int adxl345_tap_time_reg[] = {
> > > > > > > > > [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
> > > > > > > > > };
> > > > > > > > >
> > > > > > > > > +/* activity/inactivity */
> > > > > > > > > +enum adxl345_activity_type {
> > > > > > > > > + ADXL345_ACTIVITY,
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > > > +static const unsigned int adxl345_act_int_reg[] = {
> > > > > > > > > + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > > > +static const unsigned int adxl345_act_thresh_reg[] = {
> > > > > > > > > + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > > > enum adxl345_odr {
> > > > > > > > > ADXL345_ODR_0P10HZ = 0,
> > > > > > > > > ADXL345_ODR_0P20HZ,
> > > > > > > > > @@ -154,6 +172,13 @@ struct adxl345_state {
> > > > > > > > > };
> > > > > > > > >
> > > > > > > > > static struct iio_event_spec adxl345_events[] = {
> > > > > > > > > + {
> > > > > > > > > + /* activity */
> > > > > > > > > + .type = IIO_EV_TYPE_THRESH,
> > > > > > > >
> > > > > > > > Is this a threshold, or a magnitude? I'd expect an activity detector
> > > > > > > > to be magnitude as it doesn't care which way up the sensor is.
> > > > > > > >
> > > > > > >
> > > > > > > This is touching the main points still unclear to me. I tried to put
> > > > > > > this into the
> > > > > > > following questions. Could you please clarify?
> > > > > >
> > > > > > There are some corners where it gets messy. When I have time
> > > > > > (not for a month or so) I'll try and write some proper docs for this.
> > > > > >
> > > > > > >
> > > > > > > 1. Given a measurement "val", and a configured threshold "thr".
> > > > > > > A "rising" for IIO_EV_TYPE_THRESH means: val > thr
> > > > > > > where a "rising" for IIO_EV_TYPE_MAG means something like: val > |thr|
> > > > > > >
> > > > > > > Q: Do I understand this correctly now?
> > > > > >
> > > > > > Yes that is the intended difference.
> > > > > >
> > > > > > >
> > > > > > > Q: Is this documented somewhere (especially for reviewing further
> > > > > > > EV_TYPE fields)?
> > > > > >
> > > > > > Only in the ABI documentation in
> > > > > > Documentation/ABI/testing/sysfs-bus-iio
> > > > > > This is definitely something we should look to improve with some
> > > > > > docs beyond simply what the ABI is. That ABI is focused on
> > > > > > how the interrupt is triggered, not so much on what that means
> > > > > > wrt to freefall etc.
> > > > > >
> > > > > >
> > > > > > >
> > > > > > > Q: I wonder if I missed this for the Tap events. Going by this
> > > > > > > definition, then actually the
> > > > > > > tap events should be rather MAG events, too. Right?
> > > > > >
> > > > > > The tap events have their own type (gesture) because they are way
> > > > > > more complex than a simple threshold whether on magnitude or
> > > > > > the signed value. So those should be fine as type GESTURE.
> > > > > >
> > > > >
> > > > > I was aware of that. Actually, the case of GESTURE is a bit
> > > > > particular. On the one side, I
> > > > > understand having a distinction between THRESH events and MAG events.
> > > > > Hence, a classification of the type of event in terms of a measurement
> > > > > value triggering event condition.
> > > > > This concept seems actually to be clear.
> > > > >
> > > > > GESTURE to me then seems a bit like a "wildcard type covering all kind
> > > > > of tap events". I mean,
> > > > > saying tap detection, single tap, double tap, tripple tap, and so on
> > > > > tap go into category GESTURE - naively could also mean, then do a
> > > > > freefall type as well (?).
> > > >
> > > > Nope. Because freefall has a clear definition that aligns with
> > > > the events that we have for other types of sensor.
> > > >
> > > > You are right that gesture is a wild card. I resisted it for a long
> > > > time but there is just no sane way to handle tap events alongside
> > > > the sort of things we get on any other sensor type. Having looked
> > > > at a bunch of them they can be anything from straight magnitude thresholds
> > > > with time windows to things based on a mixture of jerk (rate of change
> > > > of acceleration) and other stuff. The only thing that kind of close
> > > > to this is pedometer step events, but we handle those as a counting
> > > > channel rather than an event as time of each is less interesting than
> > > > how many have happened. However as noted below we do have the CHANGE
> > > > type of event specifically to account for those (which is ugly).
> > > >
> > > > >
> > > > > [this is rather meant as a bit of a provocative rhetoric question than
> > > > > a proposal]
> > > > >
> > > > > > >
> > > > > > >
> > > > > > > 2. I oriented myself mostly by reading other drivers, for instance the
> > > > > > > ADXL367, the ADXL372, or also the more recent ADXL380. I am aware that
> > > > > > > there might be differences among different
> > > > > > > (Analog) sensors. But all those sensors specify Inactivity (and Activity) as a
> > > > > > > IIO_EV_TYPE_THRESH with directions IIO_MOD_X_OR_Y_OR_Z.
> > > > > > > Given the above, I implemented Activity and Inactivity events as
> > > > > > > IIO_EV_TYPE_THRESH,
> > > > > > > now I'm a bit confused.
> > > > > >
> > > > > > Hmm. This is one reason I think we need more documentation as those
> > > > > > seem to be wrong. Clearly the event is a threshold on a magnitude of
> > > > > > the acceleration, not the signed value as it applies in both directions.
> > > > > >
> > > > > > >
> > > > > > > Q: Why is this different for the ADXL345?
> > > > > >
> > > > > > Because we got it wrong for these others it seems unless they genuinely
> > > > > > have directional events - which typically means separate positive and
> > > > > > negative thresholds. Right now those events are strictly speaking
> > > > > > only apply to positive accelerations.
> > > > > >
> > > > > > >
> > > > > > > Q: If I implement Activity / Inactivity analogous to the e.g. a
> > > > > > > ADXL380, then shouldn't it be IIO_EV_TYPE_THRESH with
> > > > > > > IIO_MOD_X_OR_Y_OR_Z? Why not?
> > > > > > >
> > > > > >
> > > > > > I think we got it wrong for that part. Going forwards we should work
> > > > > > on getting it (more) correct.
> > > > > >
> > > > >
> > > > > I understand the point better now.
> > > > >
> > > > > > >
> > > > > > > 3. For the ADXL345, a Freefall signal is on all axis lower than
> > > > > > > threshold (magnitude). Thus I push a IIO_MOD_X_AND_Y_AND_Z to a
> > > > > > > separate
> > > > > > > fake channel. Inactivity will be like Freefall independent of the axis.
> > > > > > > The ADXL345 Activity can be configured by axis, as also the event will
> > > > > > > respect the axis information.
> > > > > > >
> > > > > > > Q: Setting up the "fake channel" to particuarly push to
> > > > > > > IIO_MOD_X_AND_Y_AND_Z, I probably better should also evaluate
> > > > > > > IIO_MOD_X_AND_Y_AND_Z in write_event_config(), write_event_value(),
> > > > > > > etc. rather than evaluating IIO_MOD_-types as I'm currently
> > > > > > > doing?
> > > > > >
> > > > > > Yes. That sounds correct for events on these 'fake' channels.
> > > > > > The enable and the thresholds should all be on these fake channels
> > > > > > assuming they don't have different thresholds on a per axis basis
> > > > > > (if they do things get tricky to represent).
> > > > > >
> > > > > > >
> > > > > > > Q: Activity probably remains in the regular channels for the corresponding axis?
> > > > > >
> > > > > > Yes. That is easier to handle as OR of channels is very similar
> > > > > > to separate interrupts etc.
> > > > > >
> > > > >
> > > > > I think I should definitely evaluate the IIO_MOD_X_AND_Y_AND_Z here,
> > > > > to take advantage
> > > > > of the fake channel.
> > > > >
> > > > > > >
> > > > > > >
> > > > > > > 4. I implemented functions like adxl345_write_event_config(),
> > > > > > > adxl345_write_event_value() or corresponding
> > > > > > > readers, as follows
> > > > > > > - THRESH/rising: Activity
> > > > > > > - THRESH/falling: Inactivity
> > > > > > > - MAG/falling: Freefall
> > > > > > >
> > > > > > > If I change Activity and Inactivity to be both of type MAG, I will end
> > > > > > > up with MAG/falling to indicate Freefall or equally Inactivity.
> > > > > > > Both on the IIO_MOD_X_AND_Y_AND_Z channel. I admit (ab)using the
> > > > > > > IIO_EV_TYPEs to solve my combinatorial issues for event configuration
> > > > > > > is probably not as supposed to be.
> > > > > >
> > > > > > Ah.. This I'd missed. I'm fairly sure we didn't hit this for (some) previous
> > > > > > inactivity sensors because they were always rate of change based, (AC)
> > > > > > rather than DC. DC is relatively unlikely to be used in practice because
> > > > > > we can't set the threshold as less than 1G because of gravity. It is a
> > > > > > bit odd that the device supports both DC and AC on this detector.
> > > > > >
> > > > > > I wonder why.... Might be to enable partial axis monitoring. e.g.
> > > > > > If a device is flat on a table we only look for inactivity on the non
> > > > > > vertical axis when doing DC coupling. (as we have 1g always in the other
> > > > > > axis).
> > > > > >
> > > > >
> > > > > Thank you for clarifying your position in the other mail focussed on
> > > > > the AC- / DC-coupling
> > > > > topic. It helped me in better understanding what you actually expect
> > > > > here. Although I'll
> > > > > probably need to re-read it some times, before implementing something.
> > > >
> > > > I definitely need to find time to write some docs on this. Mad few
> > > > weeks coming up but maybe I'll get some time on a plane or at an airport
> > > > to try a first draft.
> > > >
> > > > >
> > > > > > > Given you still ask me to do Inactivity and Freefall as MAG/falling
> > > > > > > with IIO_MOD_X_AND_Y_AND_Z. The difference between both IMHO,
> > > > > > > is that Activity and Inactivity for the ADXL345 indicate sensor state
> > > > > > > changes, where Freefall indicates the particular event. The
> > > > > > > sensor is either in "active" or "standby/inactive", where Freefall
> > > > > > > just triggers and then retriggers and retriggers...
> > > > > >
> > > > > > Maybe. The datasheet is annoyingly vague on these but indeed there
> > > > > > is no event for no longer falling.
> > > > > >
> > > > > > >
> > > > > > > Q: What is the method to distinguish several similar IIO events, e.g.
> > > > > > > to tag them somehow one as Freefall, the other one as Inactivity?
> > > > > >
> > > > > > In general we should not be able to do that. Long ago we made the decision
> > > > > > to have compact event codes so they don't allow for an index on a particular
> > > > > > combination of channel number and modifier. This is mainly because
> > > > > > there is limited purpose. If one event is triggered, then we have
> > > > > > to process anyway so we can just look at the value for 'how far' it was
> > > > > > triggered. I.e. if we thought DC inactivity was triggered, we can just
> > > > > > check free fall as well. (It gets a little more fiddly because of _period
> > > > > > etc which is why they may actually make sense here).
> > > > > >
> > > > > > The virtual (combination OR/AND) was added on top of that later and has
> > > > > > made the connection looser.
> > > > > >
> > > > > > In theory we could use labels + index for the virtual channels to achieve
> > > > > > separate control attributes and be able to tell which was which but
> > > > > > that would be new ABI. I'm not sure how much use this stuff is already
> > > > > > getting from userspace applications and hence whether this would be
> > > > > > a big problem to get supported.
> > > > > >
> > > > > > That would give us something like
> > > > > >
> > > > > > iio\:device0/in_accel0_x&y&z_label freefall
> > > > > > iio\:device0/in_accel1_x&y&z_label inactivity
> > > > > > iio\:device0/events/in_accel0_x&y&z_en etc
> > > > > > iio\:device0/events/in_accel1_x&y&z_en etc
> > > > > >
> > > > > > I don't like it much because it then doesn't generalize to the case
> > > > > > of multiple sensors on each axis (there are multi range parts that do that).
> > > > > > That case is pretty rare though (I think we only have such sensor supported!)
> > > > > > However, it's currently the only option we have to fully represent this.
> > > > > >
> > >
> > > Kind of the naive approach to allow for extending everything, but it probably
> > > does not fit together with the existing sensor ABI.
> >
> > Agreed.
> >
> > >
> > > > > > An alternative here might be to assess if anyone is really going to use
> > > > > > DC coupled inactivity detection (because of the 1g problem) and hence whether
> > > > > > we want to support that at all?
> > > > > >
> > >
> > > I'd rather drop free fall then. Activity and inactivity, linked to
> > > gether work well for the power saving
> > > thing. I guess that's probably also more of general use. Not sure how
> > > usefull free fall is here.
> > >
> > > > > > Yet another alternative might be to configure it purely based on the period
> > > > > > provided. If short use freefall, if long use inactivity. (I don't like this
> > > > > > one though as it doesn't really fit with usecase!)
> > > > > >
> > >
> > > That's either free fall or the other? Ideally, I'd like to represent
> > > what's possible with this sensor.
> > That's the only option if using the period to control it. You can't have
> > one event with two periods.
> >
> > > Since I'm not really familiar with how those sensors are used, for me
> > > all features are equally
> > > important. I mean, the sensor offers enabling act/inact and enabling
> > > free fall if one likes.
> >
> > Sure.
> >
> > >
> > > > > > Sorry for lack of clarity on this. These events are tricky and
> > > > > > it takes me a while to get the whole situation back into my head (and I missing
> > > > > > things like inactivity and freefall being very similar here!)
> > > > > >
> > >
> > > Ideally I'd like to implement support for all features of the sensor.
> > > I can understand if some corner case features are too individual to
> > > support them. So, at least the main features.
> > >
> > > From what I see in the code, configuring threshold and period, there
> > > might be differences and it can be easy to distinguish in
> > > read/write_event_value() like functions. Where
> > > read/write_event_config() will be more difficult.
> > > So, for the event, when I receive it I have knowledge of the exact
> > > event type, free fall or inactivity. I'm losing this information since
> > > it can currently not be conveyed over the channel. Might be that the
> > > ADXL345 is a very particular case. Might also be, that this shows a
> > > more general shortcoming which should be addressed.
> >
> > I'm sure there will be other sensors that have similar events.
> > How common is hard to say though.
> >
> > >
> > > Equally I could say, there is just a MAG event, so turning on
> > > inactivity or free fall would result in the same event. The tricky
> > > case then is if someone wants inactivity AND free fall. In such case
> > > it would be a inact_or_free_fall event. Not sure about. Probably
> > > rather not.
> >
> > It's just a threshold on falling magnitude of signal. What that means depends
> > on the sensor type and other settings (here the period). Event codes
> > are the same, but without the settings those are pretty meaningless.
> >
> > >
> > > Q: Still, what about the direction - I see, where activity is using
> > > IIO_EV_DIR_RISING, inactivity uses _DIR_FALLING. Free fall now uses
> > > _DIR_FALLING. I'm a bit unsure if this is just a choice. Could I use
> > > _DIR_EITHER here? Or why not? I mean, actually it's not possible to
> > > detect fall into one direction, it's simply "in a fall". We cannot
> > > tell which axis we cannot tell which direction.
> >
> > Not about direction in sense of x/y/z, but direction of the signal.
> >
> > They are definitely triggered when the |X| > thresh transitions
> > to |X| < thresh. So direction is falling as value |X| is getting smaller.
> >
> > The choice reflects the physical thing being measured.
> >
> > >
> > > > > > If you have time to take a look at what userspace is currently doing with
> > > > > > these events (iio_sensor_proxy etc) that might help us decide what works.
> > > > > >
> > >
> > > This might be generally a good option. Honestly, I'd like to mess
> > > still with some other sensors,
> > > first.
> >
> > Make sense.
>
> Could the following solve the situation: sharing the same event,
> distinguishing by cached information in the driver.
>
> Given, activity/inactivity and free-fall will have all MAG events,
> with activity is RISING, inactivity FALLING, and free-fall also
> FALLING.
> If only activity/inactivity or only free-fall is enabled (or none),
> there will be the particular MAG events. If now activity/inactivity
> and free-fall are enabled, all will send the same MAG event. That
> means some activity/inactivity event will then be shared with
> free-fall.
>
> The following ideas now apply only to the case both are enabled:
> act/inact and free-fall.
>
> Since the driver's ISR catches the sensor interrupt and evaluates, the
> driver has knowledge about the exact type of event. One idea then is,
> to keep this cached in the driver. Since activity / inactivity
> indicate a state switch (happening rarely), I can distinguish a state
> switch from free-fall by keeping activity/inactivity state switch as
> cached value(s) in the driver. Here, I can think of keeping the
> activity state, or indicate state switch, or the like.
>
> Another idea is not even distinguish (so much). Since practically
> free-fall is issued and re-issued quite often, most of the MAG FALLING
> events might be free-fall. I can default generally to free-fall if
> both is enabled. I'm not sure if activity/inactivity indication may
> lead to anything outside, also the free-fall could imply being
> interpreted of a sort of inactivity/activity type.
>
> Let me know, what you think.
I'm not grasping what these suggestions mean for userspace ABI.
My gut feeling is that almost no one will use free fall in practice
because the dominant usecase for that which I know of
(moving parts in laptops etc) isn't really at thing any more.
I think someone did long ago mention that it is also used for parking
mechanical lenses in phones though so maybe that still matters.
I see you sent a new version. Perhaps that will make all clear.
Unfortunately I'm travelling this week and it's all a bit of rush
for the end of the kernel cycle, so it might take me a little
while to catch up with reviews.
Jonathan
>
> Best,
> L
>
> >
> > J
> >
> > >
> > > Best,
> > > L
> > >
> > > > >
> > > > > Just as a quick response here (or perhaps just to rule it out)..
> > > > >
> > > > > Actually, I can spot as MAG-similar event types:
> > > > > - IIO_EV_TYPE_MAG
> > > > > - IIO_EV_TYPE_MAG_ADAPTIVE
> > > > > - IIO_EV_TYPE_CHANGE
> > > > > - IIO_EV_TYPE_MAG_REFERENCED
> > > > >
> > > > > For instance the last one is only used in a single sensor. Is there a
> > > > > chance to put, say, freefall into one of the other "MAG-like" sensor
> > > > > types. Alternatively, what about putting Activity/Inactivity under
> > > > > say, "MAG_REFERENCED"? This might seem to be a stupid question, since
> > > > > I can imagine you have a clear definition of those in mind. But if
> > > > > this was possible. It would solve this problem easily.
> > > >
> > > > The ABI docs do provide some definitions of these.
> > > >
> > > > Free fall is definitely straight forward TYPE MAG. It precisely aligns
> > > > with that definition as a threshold on the per axis magnitudes.
> > > >
> > > > MAG_ADAPTIVE is meant for a case where the event is on the magnitude
> > > > relative to a slow moving adaptive baseline (usually a low pass filtered
> > > > version of the signal but can include corrective jumps - IIRC these turn
> > > > up for magnetic sensors). This differs from a rate of change threshold
> > > > because it's not simply a difference between current and earlier signals
> > > > but rather current and some heavily filtered earlier signal.
> > > > These matter in cases where we have a slow changing baseline such as
> > > > coming into proximity with metal in the environment when using a magnetometer
> > > > for orientation detection.
> > > >
> > > > MAG_REFERENCED is a weird one. This was done for a nice IMU that had
> > > > the ability to estimate orientation and so remove the acceleration due
> > > > to gravity and then apply thresholds to the magnitude of the remaining
> > > > accelerations. The AC filtering on your part is is a 'cheap' way
> > > > to achieve roughly the equivalent of that for the activity detection at least
> > > > where we are removing the 'nothing happening value'.
> > > > (it is less clear for the inactivity case though that will still include
> > > > g, so maybe it is still somewhat valid).
> > > >
> > > > CHANGE is IIRC only for counting channels (so far anyway).
> > > >
> > > > So none of the more esoteric forms of them fit for this DC coupled
> > > > inactivity monitor or freefall. Both of them are the same type of event
> > > > just differing in filters applied.
> > > >
> > > > >
> > > > > If not, then I'll need to think of it and come up with a more
> > > > > elaborate approach. The label + index approach seems to be a bit
> > > > > complex. Going somehow by the time constraints in the event.. I need
> > > > > to play with that in the code to build up an oppinion, I guess.
> > > >
> > > > The time constraints thing falls down on the basis that it would
> > > > be logical to have freefall enabled (for parking any moving parts - those
> > > > used to exist mainly to stop hard disks but maybe there are other use cases?)
> > > > and inactivity for power saving with a much longer timescale.
> > > >
> > > > Freefall used to be fun because the aim was to get moving parts into
> > > > a safe state before the device hit the ground. So that meant if you dropped
> > > > a device with a harddisk from higher up, it sometimes had a better chance
> > > > of surviving. I'm not sure if anyone cares any more! Will be interesting
> > > > to see if that feature goes away on new devices.
> > > >
> > > > Jonathan
> > > >
> > > > >
> > > > > Best,
> > > > > L
> > > > >
> > > > > > Jonathan
> > > > > >
> > > > > > >
> > > > > > > Best,
> > > > > > > L
> > > > > > >
> > > > > > > > > + .dir = IIO_EV_DIR_RISING,
> > > > > > > > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > > > > > > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
> > > > > > > > > + },
> > > > > > > > > {
> > > > > > > > > /* single tap */
> > > > > > > > > .type = IIO_EV_TYPE_GESTURE,
> > > > > > > > > @@ -265,6 +290,99 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > > > > > > > return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> > > > > > > > > }
> > > > > > > > >
> > > > > > > > Jonathan
> > > > > > > >
> > > > > > > >
> > > > > >
> > > >
> > >
> >
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH v7 09/11] iio: accel: adxl345: add inactivity feature
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (7 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 08/11] iio: accel: adxl345: add activity event feature Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-27 12:38 ` Jonathan Cameron
2025-04-27 12:49 ` Jonathan Cameron
2025-04-21 22:06 ` [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
` (2 subsequent siblings)
11 siblings, 2 replies; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add the inactivity feature of the sensor. When activity and inactivity
are enabled, a link bit will be set linking activity and inactivity
handling. Additionally, the auto-sleep mode will be enabled. Due to the
link bit the sensor is going to auto-sleep when inactivity was
detected.
Inactivity detection needs a threshold to be configured, and a time
after which it will go into inactivity state if measurements under
threshold.
When a ODR is configured this time for inactivity is adjusted with a
corresponding reasonable default value, in order to have higher
frequencies and lower inactivity times, and lower sample frequency but
give more time until inactivity. Both with reasonable upper and lower
boundaries, since many of the sensor's features (e.g. auto-sleep) will
need to operate beween 12.5 Hz and 400 Hz. This is a default setting
when actively changing sample frequency, explicitly setting the time
until inactivity will overwrite the default.
Similarly, setting the g-range will provide a default value for the
activity and inactivity thresholds. Both are implicit defaults, but
equally can be overwritten to be explicitly configured.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 172 ++++++++++++++++++++++++++++++-
1 file changed, 167 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 680981609d83..b25efcad069b 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -37,11 +37,17 @@
#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
#define ADXL345_REG_TAP_SUPPRESS BIT(3)
#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
+#define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
+#define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
#define ADXL345_TAP_Z_EN BIT(0)
#define ADXL345_TAP_Y_EN BIT(1)
#define ADXL345_TAP_X_EN BIT(2)
+#define ADXL345_INACT_Z_EN BIT(0)
+#define ADXL345_INACT_Y_EN BIT(1)
+#define ADXL345_INACT_X_EN BIT(2)
+
#define ADXL345_ACT_Z_EN BIT(4)
#define ADXL345_ACT_Y_EN BIT(5)
#define ADXL345_ACT_X_EN BIT(6)
@@ -72,14 +78,17 @@ static const unsigned int adxl345_tap_time_reg[] = {
/* activity/inactivity */
enum adxl345_activity_type {
ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
};
static const unsigned int adxl345_act_int_reg[] = {
[ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
+ [ADXL345_INACTIVITY] = ADXL345_INT_INACTIVITY,
};
static const unsigned int adxl345_act_thresh_reg[] = {
[ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
+ [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
};
enum adxl345_odr {
@@ -232,6 +241,16 @@ static const struct iio_event_spec adxl345_freefall_event_spec = {
BIT(IIO_EV_INFO_PERIOD),
};
+/* inactivity */
+static const struct iio_event_spec adxl345_inactivity_event_spec = {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+
+};
+
static const struct iio_chan_spec adxl345_channels[] = {
ADXL345_CHANNEL(0, chan_x, X),
ADXL345_CHANNEL(1, chan_y, Y),
@@ -244,6 +263,14 @@ static const struct iio_chan_spec adxl345_channels[] = {
.event_spec = &adxl345_freefall_event_spec,
.num_event_specs = 1,
},
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_AND_Y_AND_Z,
+ .scan_index = -1, /* Fake channel */
+ .event_spec = &adxl345_inactivity_event_spec,
+ .num_event_specs = 1,
+ },
};
static const unsigned long adxl345_scan_masks[] = {
@@ -287,7 +314,8 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
{
unsigned int val = en ? ADXL345_POWER_CTL_MEASURE : ADXL345_POWER_CTL_STANDBY;
- return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
+ return regmap_update_bits(st->regmap, ADXL345_REG_POWER_CTL,
+ ADXL345_POWER_CTL_MEASURE, val);
}
/* act/inact */
@@ -319,6 +347,21 @@ static int adxl345_is_act_inact_en(struct adxl345_state *st,
*en = false;
return -EINVAL;
}
+ } else {
+ switch (axis) {
+ case IIO_MOD_X:
+ *en = FIELD_GET(ADXL345_INACT_X_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Y:
+ *en = FIELD_GET(ADXL345_INACT_Y_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Z:
+ *en = FIELD_GET(ADXL345_INACT_Z_EN, axis_ctrl);
+ break;
+ default:
+ *en = false;
+ return -EINVAL;
+ }
}
if (*en) {
@@ -338,6 +381,7 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
bool cmd_en)
{
bool en;
+ unsigned int inact_time_s;
unsigned int threshold;
u32 axis_ctrl = 0;
int ret;
@@ -356,6 +400,9 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
default:
return -EINVAL;
}
+ } else {
+ axis_ctrl = ADXL345_INACT_X_EN | ADXL345_INACT_Y_EN |
+ ADXL345_INACT_Z_EN;
}
if (cmd_en)
@@ -376,11 +423,69 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
if (type == ADXL345_ACTIVITY) {
en = FIELD_GET(ADXL345_REG_ACT_AXIS_MSK, axis_ctrl) &&
threshold;
+ } else {
+ ret = regmap_read(st->regmap, ADXL345_REG_TIME_INACT, &inact_time_s);
+ if (ret)
+ return ret;
+
+ en = FIELD_GET(ADXL345_REG_INACT_AXIS_MSK, axis_ctrl) &&
+ threshold && inact_time_s;
}
- return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
- adxl345_act_int_reg[type],
- en ? adxl345_act_int_reg[type] : 0);
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_act_int_reg[type],
+ en ? adxl345_act_int_reg[type] : 0);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(st->regmap, ADXL345_REG_POWER_CTL,
+ ADXL345_POWER_CTL_INACT_MSK,
+ en ? (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
+ : 0);
+}
+
+/**
+ * adxl345_set_inact_time_s - Configure inactivity time explicitly or by ODR.
+ * @st: The sensor state instance.
+ * @val_s: A desired time value, between 0 and 255.
+ *
+ * Inactivity time can be configured between 1 and 255 sec. If a val_s of 0
+ * is configured by a user, then a default inactivity time will be computed.
+ *
+ * In such case, it should take power consumption into consideration. Thus it
+ * shall be shorter for higher frequencies and longer for lower frequencies.
+ * Hence, frequencies above 255 Hz shall default to 10 s and frequencies below
+ * 10 Hz shall result in 255 s to detect inactivity.
+ *
+ * The approach simply subtracts the pre-decimal figure of the configured
+ * sample frequency from 255 s to compute inactivity time [s]. Sub-Hz are thus
+ * ignored in this estimation. The recommended ODRs for various features
+ * (activity/inactivity, sleep modes, free fall, etc.) lie between 12.5 Hz and
+ * 400 Hz, thus higher or lower frequencies will result in the boundary
+ * defaults or need to be explicitly specified via val_s.
+ *
+ * Return: 0 or error value.
+ */
+static int adxl345_set_inact_time_s(struct adxl345_state *st, u32 val_s)
+{
+ unsigned int max_boundary = 255;
+ unsigned int min_boundary = 10;
+ unsigned int val = min(val_s, max_boundary);
+ enum adxl345_odr odr;
+ unsigned int regval;
+ int ret;
+
+ if (val == 0) {
+ ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val);
+ if (ret)
+ return ret;
+ odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval);
+
+ val = (adxl345_odr_tbl[odr][0] > max_boundary)
+ ? min_boundary : max_boundary - adxl345_odr_tbl[odr][0];
+ }
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val);
}
/* tap */
@@ -846,6 +951,13 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
if (ret)
return ret;
return int_en;
+ case IIO_EV_DIR_FALLING:
+ ret = adxl345_is_act_inact_en(st, chan->channel2,
+ ADXL345_INACTIVITY,
+ &int_en);
+ if (ret)
+ return ret;
+ return int_en;
default:
return -EINVAL;
}
@@ -890,6 +1002,9 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
case IIO_EV_DIR_RISING:
return adxl345_set_act_inact_en(st, chan->channel2,
ADXL345_ACTIVITY, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_act_inact_en(st, chan->channel2,
+ ADXL345_INACTIVITY, state);
default:
return -EINVAL;
}
@@ -917,7 +1032,8 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
int *val, int *val2)
{
struct adxl345_state *st = iio_priv(indio_dev);
- unsigned int act_threshold;
+ unsigned int act_threshold, inact_threshold;
+ unsigned int inact_time_s;
unsigned int tap_threshold;
unsigned int ff_threshold;
int ret;
@@ -936,9 +1052,24 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
*val = act_threshold;
return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ &inact_threshold);
+ if (ret)
+ return ret;
+
+ *val = inact_threshold;
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
+ case IIO_EV_INFO_PERIOD:
+ ret = regmap_read(st->regmap, ADXL345_REG_TIME_INACT, &inact_time_s);
+ if (ret)
+ return ret;
+ *val = inact_time_s;
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
@@ -1019,10 +1150,22 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
if (ret)
return ret;
break;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ val);
+ if (ret)
+ return ret;
+ break;
default:
return -EINVAL;
}
break;
+ case IIO_EV_INFO_PERIOD:
+ ret = adxl345_set_inact_time_s(st, val);
+ if (ret)
+ return ret;
+ break;
default:
return -EINVAL;
}
@@ -1323,6 +1466,17 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
return ret;
}
+ if (FIELD_GET(ADXL345_INT_INACTIVITY, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_FALLING),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) {
ret = iio_push_event(indio_dev,
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
@@ -1569,10 +1723,18 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ ret = regmap_write(st->regmap, ADXL345_REG_TIME_INACT, 3);
+ if (ret)
+ return ret;
+
ret = regmap_write(st->regmap, ADXL345_REG_THRESH_ACT, 6);
if (ret)
return ret;
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_INACT, 4);
+ if (ret)
+ return ret;
+
ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold);
if (ret)
return ret;
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v7 09/11] iio: accel: adxl345: add inactivity feature
2025-04-21 22:06 ` [PATCH v7 09/11] iio: accel: adxl345: add inactivity feature Lothar Rubusch
@ 2025-04-27 12:38 ` Jonathan Cameron
2025-04-27 12:49 ` Jonathan Cameron
1 sibling, 0 replies; 32+ messages in thread
From: Jonathan Cameron @ 2025-04-27 12:38 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Mon, 21 Apr 2025 22:06:39 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Add the inactivity feature of the sensor. When activity and inactivity
> are enabled, a link bit will be set linking activity and inactivity
> handling. Additionally, the auto-sleep mode will be enabled. Due to the
> link bit the sensor is going to auto-sleep when inactivity was
> detected.
>
> Inactivity detection needs a threshold to be configured, and a time
> after which it will go into inactivity state if measurements under
> threshold.
>
> When a ODR is configured this time for inactivity is adjusted with a
> corresponding reasonable default value, in order to have higher
> frequencies and lower inactivity times, and lower sample frequency but
> give more time until inactivity. Both with reasonable upper and lower
> boundaries, since many of the sensor's features (e.g. auto-sleep) will
> need to operate beween 12.5 Hz and 400 Hz. This is a default setting
> when actively changing sample frequency, explicitly setting the time
> until inactivity will overwrite the default.
>
> Similarly, setting the g-range will provide a default value for the
> activity and inactivity thresholds. Both are implicit defaults, but
> equally can be overwritten to be explicitly configured.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Tweaked - see below for why. Please sanity check I didn't mess it up!
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index dd8cea5c48f3..a61c97c086e9 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -241,16 +241,14 @@ static const struct iio_event_spec adxl345_all_event_spec[] = {
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_PERIOD),
},
-};
-
-/* inactivity */
-static const struct iio_event_spec adxl345_inactivity_event_spec = {
+ /* inactivity */
+ {
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_FALLING,
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
- BIT(IIO_EV_INFO_PERIOD),
-
+ BIT(IIO_EV_INFO_PERIOD),
+ },
};
static const struct iio_chan_spec adxl345_channels[] = {
@@ -265,14 +263,6 @@ static const struct iio_chan_spec adxl345_channels[] = {
.event_spec = adxl345_all_event_spec,
.num_event_specs = ARRAY_SIZE(adxl345_all_event_spec),
},
- {
- .type = IIO_ACCEL,
- .modified = 1,
- .channel2 = IIO_MOD_X_AND_Y_AND_Z,
- .scan_index = -1, /* Fake channel */
- .event_spec = &adxl345_inactivity_event_spec,
- .num_event_specs = 1,
- },
};
static const unsigned long adxl345_scan_masks[] = {
> ---
> drivers/iio/accel/adxl345_core.c | 172 ++++++++++++++++++++++++++++++-
> 1 file changed, 167 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> index 680981609d83..b25efcad069b 100644
> --- a/drivers/iio/accel/adxl345_core.c
> +++ b/drivers/iio/accel/adxl345_core.c
> @@ -37,11 +37,17 @@
>
> +/* inactivity */
> +static const struct iio_event_spec adxl345_inactivity_event_spec = {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_FALLING,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_PERIOD),
> +
> +};
> +
> static const struct iio_chan_spec adxl345_channels[] = {
> ADXL345_CHANNEL(0, chan_x, X),
> ADXL345_CHANNEL(1, chan_y, Y),
> @@ -244,6 +263,14 @@ static const struct iio_chan_spec adxl345_channels[] = {
> .event_spec = &adxl345_freefall_event_spec,
> .num_event_specs = 1,
> },
> + {
> + .type = IIO_ACCEL,
> + .modified = 1,
> + .channel2 = IIO_MOD_X_AND_Y_AND_Z,
> + .scan_index = -1, /* Fake channel */
> + .event_spec = &adxl345_inactivity_event_spec,
> + .num_event_specs = 1,
Why do we need a separate fake channel for this?
Should reuse the one that we have for freefall which has the same definition.
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_AND_Y_AND_Z,
+ .scan_index = -1, /* Fake channel */
+ .event_spec = &adxl345_freefall_event_spec,
+ .num_event_specs = 1,
+ },
> + },
> };
>
> static const unsigned long adxl345_scan_masks[] = {
> @@ -287,7 +314,8 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> {
> unsigned int val = en ? ADXL345_POWER_CTL_MEASURE : ADXL345_POWER_CTL_STANDBY;
>
> - return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> + return regmap_update_bits(st->regmap, ADXL345_REG_POWER_CTL,
> + ADXL345_POWER_CTL_MEASURE, val);
> }
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v7 09/11] iio: accel: adxl345: add inactivity feature
2025-04-21 22:06 ` [PATCH v7 09/11] iio: accel: adxl345: add inactivity feature Lothar Rubusch
2025-04-27 12:38 ` Jonathan Cameron
@ 2025-04-27 12:49 ` Jonathan Cameron
1 sibling, 0 replies; 32+ messages in thread
From: Jonathan Cameron @ 2025-04-27 12:49 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Mon, 21 Apr 2025 22:06:39 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Add the inactivity feature of the sensor. When activity and inactivity
> are enabled, a link bit will be set linking activity and inactivity
> handling. Additionally, the auto-sleep mode will be enabled. Due to the
> link bit the sensor is going to auto-sleep when inactivity was
> detected.
>
> Inactivity detection needs a threshold to be configured, and a time
> after which it will go into inactivity state if measurements under
> threshold.
>
> When a ODR is configured this time for inactivity is adjusted with a
> corresponding reasonable default value, in order to have higher
> frequencies and lower inactivity times, and lower sample frequency but
> give more time until inactivity. Both with reasonable upper and lower
> boundaries, since many of the sensor's features (e.g. auto-sleep) will
> need to operate beween 12.5 Hz and 400 Hz. This is a default setting
> when actively changing sample frequency, explicitly setting the time
> until inactivity will overwrite the default.
>
> Similarly, setting the g-range will provide a default value for the
> activity and inactivity thresholds. Both are implicit defaults, but
> equally can be overwritten to be explicitly configured.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
One more thing I only noticed later...
> enum adxl345_odr {
> @@ -232,6 +241,16 @@ static const struct iio_event_spec adxl345_freefall_event_spec = {
> BIT(IIO_EV_INFO_PERIOD),
> };
>
> +/* inactivity */
> +static const struct iio_event_spec adxl345_inactivity_event_spec = {
> + .type = IIO_EV_TYPE_THRESH,
Similar to the activity detector. I'd expect inactivity to be magnitude
based not signed value (which EV_TYPE_THRESH is).
> + .dir = IIO_EV_DIR_FALLING,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_PERIOD),
> +
> +};
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (8 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 09/11] iio: accel: adxl345: add inactivity feature Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-27 13:00 ` Jonathan Cameron
2025-04-21 22:06 ` [PATCH v7 11/11] docs: iio: add documentation for adxl345 driver Lothar Rubusch
2025-04-27 13:01 ` [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Jonathan Cameron
11 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add coupling activity/inactivity detection by the AC/DC bit. This is an
addititional enhancement for the detection of activity states and
completes the activity / inactivity feature of the ADXL345.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 162 ++++++++++++++++++++++++++++++-
1 file changed, 159 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index b25efcad069b..c07ad5774c8a 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -37,7 +37,9 @@
#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
#define ADXL345_REG_TAP_SUPPRESS BIT(3)
#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
+#define ADXL345_REG_ACT_ACDC_MSK BIT(7)
#define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
+#define ADXL345_REG_INACT_ACDC_MSK BIT(3)
#define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
#define ADXL345_TAP_Z_EN BIT(0)
@@ -91,6 +93,11 @@ static const unsigned int adxl345_act_thresh_reg[] = {
[ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
};
+static const unsigned int adxl345_act_acdc_msk[] = {
+ [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC_MSK,
+ [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC_MSK,
+};
+
enum adxl345_odr {
ADXL345_ODR_0P10HZ = 0,
ADXL345_ODR_0P20HZ,
@@ -204,6 +211,18 @@ static struct iio_event_spec adxl345_events[] = {
BIT(IIO_EV_INFO_RESET_TIMEOUT) |
BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
},
+ {
+ /* activity, activity - ac bit */
+ .type = IIO_EV_TYPE_MAG_REFERENCED,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ /* activity, inactivity - ac bit */
+ .type = IIO_EV_TYPE_MAG_REFERENCED,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
+ },
};
#define ADXL345_CHANNEL(index, reg, axis) { \
@@ -320,6 +339,69 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
/* act/inact */
+static int adxl345_is_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type, bool *ac)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
+ if (ret)
+ return ret;
+
+ if (type == ADXL345_ACTIVITY)
+ *ac = FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval);
+ else
+ *ac = FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval);
+
+ return 0;
+}
+
+static int adxl345_set_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type, bool ac)
+{
+ unsigned int act_inact_ac = ac ? 0xff : 0x00;
+
+ /*
+ * A setting of false selects dc-coupled operation, and a setting of
+ * true enables ac-coupled operation. In dc-coupled operation, the
+ * current acceleration magnitude is compared directly with
+ * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine
+ * whether activity or inactivity is detected.
+ *
+ * In ac-coupled operation for activity detection, the acceleration
+ * value at the start of activity detection is taken as a reference
+ * value. New samples of acceleration are then compared to this
+ * reference value, and if the magnitude of the difference exceeds the
+ * ADXL345_REG_THRESH_ACT value, the device triggers an activity
+ * interrupt.
+ *
+ * Similarly, in ac-coupled operation for inactivity detection, a
+ * reference value is used for comparison and is updated whenever the
+ * device exceeds the inactivity threshold. After the reference value
+ * is selected, the device compares the magnitude of the difference
+ * between the reference value and the current acceleration with
+ * ADXL345_REG_THRESH_INACT. If the difference is less than the value in
+ * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the
+ * device is considered inactive and the inactivity interrupt is
+ * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G]
+ *
+ * In a conclusion, the first acceleration snapshot sample which hit the
+ * threshold in a particular direction is always taken as acceleration
+ * reference value to that direction. Since for the hardware activity
+ * and inactivity depend on the x/y/z axis, so do ac and dc coupling.
+ * Note, this sw driver always enables or disables all three x/y/z axis
+ * for detection via act_axis_ctrl and inact_axis_ctrl, respectively.
+ * Where in dc-coupling samples are compared against the thresholds, in
+ * ac-coupling measurement difference to the first acceleration
+ * reference value are compared against the threshold. So, ac-coupling
+ * allows for a bit more dynamic compensation depending on the initial
+ * sample.
+ */
+ return regmap_update_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
+ adxl345_act_acdc_msk[type], act_inact_ac);
+}
+
static int adxl345_is_act_inact_en(struct adxl345_state *st,
enum iio_modifier axis,
enum adxl345_activity_type type, bool *en)
@@ -774,9 +856,16 @@ static int adxl345_find_odr(struct adxl345_state *st, int val,
static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
{
- return regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE,
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE,
ADXL345_BW_RATE_MSK,
FIELD_PREP(ADXL345_BW_RATE_MSK, odr));
+ if (ret)
+ return ret;
+
+ /* update inactivity time by ODR */
+ return adxl345_set_inact_time_s(st, 0);
}
static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
@@ -797,9 +886,51 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
{
- return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
+ unsigned int act_threshold, inact_threshold;
+ unsigned int range_old;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val);
+ if (ret)
+ return ret;
+ range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
+
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ &act_threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ &inact_threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
ADXL345_DATA_FORMAT_RANGE,
FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
+ if (ret)
+ return ret;
+
+ act_threshold = act_threshold
+ * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ act_threshold = min(255, max(1, inact_threshold));
+
+ inact_threshold = inact_threshold
+ * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ inact_threshold = min(255, max(1, inact_threshold));
+
+ ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ act_threshold);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ inact_threshold);
}
static int adxl345_read_avail(struct iio_dev *indio_dev,
@@ -938,7 +1069,7 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
enum iio_event_direction dir)
{
struct adxl345_state *st = iio_priv(indio_dev);
- bool int_en;
+ bool int_en, act_ac, inact_ac;
int ret;
switch (type) {
@@ -983,6 +1114,21 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
if (ret)
return ret;
return int_en;
+ case IIO_EV_TYPE_MAG_REFERENCED:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = adxl345_is_act_inact_ac(st, ADXL345_ACTIVITY, &act_ac);
+ if (ret)
+ return ret;
+ return act_ac;
+ case IIO_EV_DIR_FALLING:
+ ret = adxl345_is_act_inact_ac(st, ADXL345_INACTIVITY, &inact_ac);
+ if (ret)
+ return ret;
+ return inact_ac;
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -1019,6 +1165,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
}
case IIO_EV_TYPE_MAG:
return adxl345_set_ff_en(st, state);
+ case IIO_EV_TYPE_MAG_REFERENCED:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl345_set_act_inact_ac(st, ADXL345_ACTIVITY, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_act_inact_ac(st, ADXL345_INACTIVITY, state);
+ default:
+ return -EINVAL;
+ }
+
default:
return -EINVAL;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-04-21 22:06 ` [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
@ 2025-04-27 13:00 ` Jonathan Cameron
2025-05-01 7:35 ` Lothar Rubusch
0 siblings, 1 reply; 32+ messages in thread
From: Jonathan Cameron @ 2025-04-27 13:00 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Mon, 21 Apr 2025 22:06:40 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Add coupling activity/inactivity detection by the AC/DC bit. This is an
> addititional enhancement for the detection of activity states and
> completes the activity / inactivity feature of the ADXL345.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
I've dragged the table from the earlier patch into this one that actually uses it.
However I'm a little unsure on exactly how we present this feature.
So until those questions are resolved I've dropped the patch (you'll need
to rebase on my testing branch and fix up missing table for v8).
The bit that made me not apply this series (with some tweaks) was that
I'd expect enabling AC events to be visible as disabling of DC ones.
Also, I just noticed you aren't pushing the new event types.
These controls need to look like a separate event detector hardware block
with it's own controls + its own event codes. The fact only this or
the DC version can be enabled at any time should only be exposed in the
reported state, not apparent via what files we expose etc. On some
other device they may be independent hardware blocks.
Note I'd also expect to see value controls for these new events. You may
need to cache the values and update on event change if the meaning is
very different. That's because the expectation would be an event
setup sequence from userspace is:
1) Set value of threshold
2) Enable event
On a change of event (due to shared hardware) The value set may scramble
the event already enabled.
So write the values into a cache and update to the right one when changing
event.
> ---
> drivers/iio/accel/adxl345_core.c | 162 ++++++++++++++++++++++++++++++-
> 1 file changed, 159 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> index b25efcad069b..c07ad5774c8a 100644
> --- a/drivers/iio/accel/adxl345_core.c
> +++ b/drivers/iio/accel/adxl345_core.c
> @@ -37,7 +37,9 @@
> #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> #define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> +#define ADXL345_REG_ACT_ACDC_MSK BIT(7)
> #define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
> +#define ADXL345_REG_INACT_ACDC_MSK BIT(3)
> #define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
>
> #define ADXL345_TAP_Z_EN BIT(0)
> @@ -91,6 +93,11 @@ static const unsigned int adxl345_act_thresh_reg[] = {
> [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
> };
>
> +static const unsigned int adxl345_act_acdc_msk[] = {
> + [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC_MSK,
> + [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC_MSK,
> +};
> +
> enum adxl345_odr {
> ADXL345_ODR_0P10HZ = 0,
> ADXL345_ODR_0P20HZ,
> @@ -204,6 +211,18 @@ static struct iio_event_spec adxl345_events[] = {
> BIT(IIO_EV_INFO_RESET_TIMEOUT) |
> BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
> },
> + {
> + /* activity, activity - ac bit */
Comment says activity and inactivity but channel type wise this
is just activity (as rising)
> + .type = IIO_EV_TYPE_MAG_REFERENCED,
> + .dir = IIO_EV_DIR_RISING,
> + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> + },
> + {
> + /* activity, inactivity - ac bit */
Likewise this seems to be inactivity. Should this be in the x&y&z
channel, not this one?
> + .type = IIO_EV_TYPE_MAG_REFERENCED,
> + .dir = IIO_EV_DIR_FALLING,
> + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> + },
> };
>
> #define ADXL345_CHANNEL(index, reg, axis) { \
> @@ -320,6 +339,69 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
>
> /* act/inact */
>
> +static int adxl345_is_act_inact_ac(struct adxl345_state *st,
> + enum adxl345_activity_type type, bool *ac)
> +{
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
> + if (ret)
> + return ret;
> +
> + if (type == ADXL345_ACTIVITY)
> + *ac = FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval);
> + else
> + *ac = FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval);
> +
> + return 0;
> +}
> +
> +static int adxl345_set_act_inact_ac(struct adxl345_state *st,
> + enum adxl345_activity_type type, bool ac)
> +{
> + unsigned int act_inact_ac = ac ? 0xff : 0x00;
> +
> + /*
> + * A setting of false selects dc-coupled operation, and a setting of
> + * true enables ac-coupled operation. In dc-coupled operation, the
> + * current acceleration magnitude is compared directly with
> + * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine
> + * whether activity or inactivity is detected.
> + *
> + * In ac-coupled operation for activity detection, the acceleration
> + * value at the start of activity detection is taken as a reference
> + * value. New samples of acceleration are then compared to this
> + * reference value, and if the magnitude of the difference exceeds the
> + * ADXL345_REG_THRESH_ACT value, the device triggers an activity
> + * interrupt.
> + *
> + * Similarly, in ac-coupled operation for inactivity detection, a
> + * reference value is used for comparison and is updated whenever the
> + * device exceeds the inactivity threshold. After the reference value
> + * is selected, the device compares the magnitude of the difference
> + * between the reference value and the current acceleration with
> + * ADXL345_REG_THRESH_INACT. If the difference is less than the value in
> + * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the
> + * device is considered inactive and the inactivity interrupt is
> + * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G]
> + *
> + * In a conclusion, the first acceleration snapshot sample which hit the
> + * threshold in a particular direction is always taken as acceleration
> + * reference value to that direction. Since for the hardware activity
> + * and inactivity depend on the x/y/z axis, so do ac and dc coupling.
> + * Note, this sw driver always enables or disables all three x/y/z axis
> + * for detection via act_axis_ctrl and inact_axis_ctrl, respectively.
> + * Where in dc-coupling samples are compared against the thresholds, in
> + * ac-coupling measurement difference to the first acceleration
> + * reference value are compared against the threshold. So, ac-coupling
> + * allows for a bit more dynamic compensation depending on the initial
> + * sample.
> + */
> + return regmap_update_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
> + adxl345_act_acdc_msk[type], act_inact_ac);
> +}
> static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> @@ -797,9 +886,51 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
>
> static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
> {
> - return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> + unsigned int act_threshold, inact_threshold;
> + unsigned int range_old;
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val);
> + if (ret)
> + return ret;
> + range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
> +
> + ret = regmap_read(st->regmap,
> + adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> + &act_threshold);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(st->regmap,
> + adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> + &inact_threshold);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> ADXL345_DATA_FORMAT_RANGE,
> FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
> + if (ret)
> + return ret;
> +
> + act_threshold = act_threshold
> + * adxl345_range_factor_tbl[range_old]
> + / adxl345_range_factor_tbl[range];
> + act_threshold = min(255, max(1, inact_threshold));
> +
This is first use of the range table. So introduce that in this patch.
> + inact_threshold = inact_threshold
> + * adxl345_range_factor_tbl[range_old]
> + / adxl345_range_factor_tbl[range];
> + inact_threshold = min(255, max(1, inact_threshold));
> +
> + ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> + act_threshold);
> + if (ret)
> + return ret;
> +
> + return regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> + inact_threshold);
> }
>
> static int adxl345_read_avail(struct iio_dev *indio_dev,
> @@ -938,7 +1069,7 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> enum iio_event_direction dir)
> {
> struct adxl345_state *st = iio_priv(indio_dev);
> - bool int_en;
> + bool int_en, act_ac, inact_ac;
> int ret;
>
> switch (type) {
> @@ -983,6 +1114,21 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> if (ret)
> return ret;
> return int_en;
> + case IIO_EV_TYPE_MAG_REFERENCED:
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + ret = adxl345_is_act_inact_ac(st, ADXL345_ACTIVITY, &act_ac);
Do we not need a check in the enabling of the DC events as well? If we have enabled
AC the DC one should report disabled (and if we enable that again then we should
update this.
> + if (ret)
> + return ret;
> + return act_ac;
> + case IIO_EV_DIR_FALLING:
> + ret = adxl345_is_act_inact_ac(st, ADXL345_INACTIVITY, &inact_ac);
> + if (ret)
> + return ret;
> + return inact_ac;
> + default:
> + return -EINVAL;
> + }
> default:
> return -EINVAL;
> }
> @@ -1019,6 +1165,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
> }
> case IIO_EV_TYPE_MAG:
> return adxl345_set_ff_en(st, state);
> + case IIO_EV_TYPE_MAG_REFERENCED:
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + return adxl345_set_act_inact_ac(st, ADXL345_ACTIVITY, state);
Similar to read path. The DC events should be affected by this as well as the AC ones.
> + case IIO_EV_DIR_FALLING:
> + return adxl345_set_act_inact_ac(st, ADXL345_INACTIVITY, state);
> + default:
> + return -EINVAL;
> + }
> +
> default:
> return -EINVAL;
> }
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-04-27 13:00 ` Jonathan Cameron
@ 2025-05-01 7:35 ` Lothar Rubusch
2025-05-04 10:39 ` Jonathan Cameron
0 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-05-01 7:35 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Sun, Apr 27, 2025 at 3:00 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Mon, 21 Apr 2025 22:06:40 +0000
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > Add coupling activity/inactivity detection by the AC/DC bit. This is an
> > addititional enhancement for the detection of activity states and
> > completes the activity / inactivity feature of the ADXL345.
> >
> > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
>
> I've dragged the table from the earlier patch into this one that actually uses it.
> However I'm a little unsure on exactly how we present this feature.
>
> So until those questions are resolved I've dropped the patch (you'll need
> to rebase on my testing branch and fix up missing table for v8).
>
> The bit that made me not apply this series (with some tweaks) was that
> I'd expect enabling AC events to be visible as disabling of DC ones.
>
There are no AC events, nor DC ones.
Think of AC- or DC-coupled detection as modes of operating the
ACTIVITY/INACTIVITY
detection. The events are ACTIVITY or INACTIVITY. It has effect on how
the sensor detects
if it needs to trigger an (IN)/ACTIVITY event. DC is just going by the
configured thresholds,
where AC is supposed to apply some more elaborate way of declaring ACTIVITY or
INACTIVITY.
The fact that you imply on this means to me, at least I explained it
wrong, or need to clarify
better.
> Also, I just noticed you aren't pushing the new event types.
>
> These controls need to look like a separate event detector hardware block
> with it's own controls + its own event codes. The fact only this or
> the DC version can be enabled at any time should only be exposed in the
> reported state, not apparent via what files we expose etc. On some
> other device they may be independent hardware blocks.
>
> Note I'd also expect to see value controls for these new events. You may
> need to cache the values and update on event change if the meaning is
> very different. That's because the expectation would be an event
> setup sequence from userspace is:
>
> 1) Set value of threshold
> 2) Enable event
>
> On a change of event (due to shared hardware) The value set may scramble
> the event already enabled.
>
> So write the values into a cache and update to the right one when changing
> event.
>
Might be that I got you wrong here, but I assume the above does
actually not apply.
Best,
L
> > ---
> > drivers/iio/accel/adxl345_core.c | 162 ++++++++++++++++++++++++++++++-
> > 1 file changed, 159 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > index b25efcad069b..c07ad5774c8a 100644
> > --- a/drivers/iio/accel/adxl345_core.c
> > +++ b/drivers/iio/accel/adxl345_core.c
> > @@ -37,7 +37,9 @@
> > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > #define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > +#define ADXL345_REG_ACT_ACDC_MSK BIT(7)
> > #define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
> > +#define ADXL345_REG_INACT_ACDC_MSK BIT(3)
> > #define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
> >
> > #define ADXL345_TAP_Z_EN BIT(0)
> > @@ -91,6 +93,11 @@ static const unsigned int adxl345_act_thresh_reg[] = {
> > [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
> > };
> >
> > +static const unsigned int adxl345_act_acdc_msk[] = {
> > + [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC_MSK,
> > + [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC_MSK,
> > +};
> > +
> > enum adxl345_odr {
> > ADXL345_ODR_0P10HZ = 0,
> > ADXL345_ODR_0P20HZ,
> > @@ -204,6 +211,18 @@ static struct iio_event_spec adxl345_events[] = {
> > BIT(IIO_EV_INFO_RESET_TIMEOUT) |
> > BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
> > },
> > + {
> > + /* activity, activity - ac bit */
> Comment says activity and inactivity but channel type wise this
> is just activity (as rising)
>
> > + .type = IIO_EV_TYPE_MAG_REFERENCED,
> > + .dir = IIO_EV_DIR_RISING,
> > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> > + },
> > + {
> > + /* activity, inactivity - ac bit */
>
> Likewise this seems to be inactivity. Should this be in the x&y&z
> channel, not this one?
>
> > + .type = IIO_EV_TYPE_MAG_REFERENCED,
> > + .dir = IIO_EV_DIR_FALLING,
> > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> > + },
> > };
> >
> > #define ADXL345_CHANNEL(index, reg, axis) { \
> > @@ -320,6 +339,69 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> >
> > /* act/inact */
> >
> > +static int adxl345_is_act_inact_ac(struct adxl345_state *st,
> > + enum adxl345_activity_type type, bool *ac)
> > +{
> > + unsigned int regval;
> > + int ret;
> > +
> > + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
> > + if (ret)
> > + return ret;
> > +
> > + if (type == ADXL345_ACTIVITY)
> > + *ac = FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval);
> > + else
> > + *ac = FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval);
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl345_set_act_inact_ac(struct adxl345_state *st,
> > + enum adxl345_activity_type type, bool ac)
> > +{
> > + unsigned int act_inact_ac = ac ? 0xff : 0x00;
> > +
> > + /*
> > + * A setting of false selects dc-coupled operation, and a setting of
> > + * true enables ac-coupled operation. In dc-coupled operation, the
> > + * current acceleration magnitude is compared directly with
> > + * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine
> > + * whether activity or inactivity is detected.
> > + *
> > + * In ac-coupled operation for activity detection, the acceleration
> > + * value at the start of activity detection is taken as a reference
> > + * value. New samples of acceleration are then compared to this
> > + * reference value, and if the magnitude of the difference exceeds the
> > + * ADXL345_REG_THRESH_ACT value, the device triggers an activity
> > + * interrupt.
> > + *
> > + * Similarly, in ac-coupled operation for inactivity detection, a
> > + * reference value is used for comparison and is updated whenever the
> > + * device exceeds the inactivity threshold. After the reference value
> > + * is selected, the device compares the magnitude of the difference
> > + * between the reference value and the current acceleration with
> > + * ADXL345_REG_THRESH_INACT. If the difference is less than the value in
> > + * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the
> > + * device is considered inactive and the inactivity interrupt is
> > + * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G]
> > + *
> > + * In a conclusion, the first acceleration snapshot sample which hit the
> > + * threshold in a particular direction is always taken as acceleration
> > + * reference value to that direction. Since for the hardware activity
> > + * and inactivity depend on the x/y/z axis, so do ac and dc coupling.
> > + * Note, this sw driver always enables or disables all three x/y/z axis
> > + * for detection via act_axis_ctrl and inact_axis_ctrl, respectively.
> > + * Where in dc-coupling samples are compared against the thresholds, in
> > + * ac-coupling measurement difference to the first acceleration
> > + * reference value are compared against the threshold. So, ac-coupling
> > + * allows for a bit more dynamic compensation depending on the initial
> > + * sample.
> > + */
> > + return regmap_update_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
> > + adxl345_act_acdc_msk[type], act_inact_ac);
> > +}
>
> > static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> > @@ -797,9 +886,51 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> >
> > static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
> > {
> > - return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> > + unsigned int act_threshold, inact_threshold;
> > + unsigned int range_old;
> > + unsigned int regval;
> > + int ret;
> > +
> > + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val);
> > + if (ret)
> > + return ret;
> > + range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
> > +
> > + ret = regmap_read(st->regmap,
> > + adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> > + &act_threshold);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_read(st->regmap,
> > + adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> > + &inact_threshold);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> > ADXL345_DATA_FORMAT_RANGE,
> > FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
> > + if (ret)
> > + return ret;
> > +
> > + act_threshold = act_threshold
> > + * adxl345_range_factor_tbl[range_old]
> > + / adxl345_range_factor_tbl[range];
> > + act_threshold = min(255, max(1, inact_threshold));
> > +
> This is first use of the range table. So introduce that in this patch.
>
> > + inact_threshold = inact_threshold
> > + * adxl345_range_factor_tbl[range_old]
> > + / adxl345_range_factor_tbl[range];
> > + inact_threshold = min(255, max(1, inact_threshold));
> > +
> > + ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> > + act_threshold);
> > + if (ret)
> > + return ret;
> > +
> > + return regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> > + inact_threshold);
> > }
> >
> > static int adxl345_read_avail(struct iio_dev *indio_dev,
> > @@ -938,7 +1069,7 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> > enum iio_event_direction dir)
> > {
> > struct adxl345_state *st = iio_priv(indio_dev);
> > - bool int_en;
> > + bool int_en, act_ac, inact_ac;
> > int ret;
> >
> > switch (type) {
> > @@ -983,6 +1114,21 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> > if (ret)
> > return ret;
> > return int_en;
> > + case IIO_EV_TYPE_MAG_REFERENCED:
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + ret = adxl345_is_act_inact_ac(st, ADXL345_ACTIVITY, &act_ac);
>
> Do we not need a check in the enabling of the DC events as well? If we have enabled
> AC the DC one should report disabled (and if we enable that again then we should
> update this.
>
> > + if (ret)
> > + return ret;
> > + return act_ac;
> > + case IIO_EV_DIR_FALLING:
> > + ret = adxl345_is_act_inact_ac(st, ADXL345_INACTIVITY, &inact_ac);
> > + if (ret)
> > + return ret;
> > + return inact_ac;
> > + default:
> > + return -EINVAL;
> > + }
> > default:
> > return -EINVAL;
> > }
> > @@ -1019,6 +1165,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
> > }
> > case IIO_EV_TYPE_MAG:
> > return adxl345_set_ff_en(st, state);
> > + case IIO_EV_TYPE_MAG_REFERENCED:
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + return adxl345_set_act_inact_ac(st, ADXL345_ACTIVITY, state);
>
> Similar to read path. The DC events should be affected by this as well as the AC ones.
>
> > + case IIO_EV_DIR_FALLING:
> > + return adxl345_set_act_inact_ac(st, ADXL345_INACTIVITY, state);
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > default:
> > return -EINVAL;
> > }
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-05-01 7:35 ` Lothar Rubusch
@ 2025-05-04 10:39 ` Jonathan Cameron
2025-05-07 20:04 ` Lothar Rubusch
0 siblings, 1 reply; 32+ messages in thread
From: Jonathan Cameron @ 2025-05-04 10:39 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 1 May 2025 09:35:29 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> On Sun, Apr 27, 2025 at 3:00 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Mon, 21 Apr 2025 22:06:40 +0000
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > Add coupling activity/inactivity detection by the AC/DC bit. This is an
> > > addititional enhancement for the detection of activity states and
> > > completes the activity / inactivity feature of the ADXL345.
> > >
> > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> >
> > I've dragged the table from the earlier patch into this one that actually uses it.
> > However I'm a little unsure on exactly how we present this feature.
> >
> > So until those questions are resolved I've dropped the patch (you'll need
> > to rebase on my testing branch and fix up missing table for v8).
> >
> > The bit that made me not apply this series (with some tweaks) was that
> > I'd expect enabling AC events to be visible as disabling of DC ones.
> >
>
> There are no AC events, nor DC ones.
>
> Think of AC- or DC-coupled detection as modes of operating the
> ACTIVITY/INACTIVITY
> detection. The events are ACTIVITY or INACTIVITY.
That's where we differ. For generalizing an ABI we absolutely should
not think of them as modes - that's an implementation detail only
> It has effect on how
> the sensor detects
> if it needs to trigger an (IN)/ACTIVITY event. DC is just going by the
> configured thresholds,
> where AC is supposed to apply some more elaborate way of declaring ACTIVITY or
> INACTIVITY.
>
> The fact that you imply on this means to me, at least I explained it
> wrong, or need to clarify
> better.
Not at all (I think). This is a question of datasheet representation vs
what we are doing in the ABI. Lets have a thought experiment.
If the device had separate enables, thresholds and periods for
DC activity, DC inactivity, AC activity and AC inactivity would you be
thinking of them as modes of one event? They would be separate event types
that could be all enabled at the same time.
Now step 2. We have many sensors that have limited numbers of highly programmable
event detection engines (the adis IMUs for instance have only a few such engines
but many types of events). For those we treat them as a fifo. If you request
3 events and the device has 2 detectors you get the last two that were asked
for. We do this because we don't want a detector with 10s of modes with
the parameter meaning changing for each one. The ABI should look the same
as independent detectors. Some models are more complex and have more such
engines, but the interface remains the same.
So here we have a pair of less flexible engines that can support either
AC or DC for inactivity/activity. We should represent that the same
way we represent the separate engines case. Hence when you enable
activity AC, our short (1 element) fifo means we disable activity DC.
Which is why treating them as separate engines (with rules on which can
be enabled at any time) is the right way to go. IIRC correctly long
ago we had various experiments with interfaces around this (including
mode based ones) and ended up with this approach that looks like a
whole bunch of event detectors with a mux that means we can only use
a subset at a time.
Jonathan
>
> > Also, I just noticed you aren't pushing the new event types.
> >
> > These controls need to look like a separate event detector hardware block
> > with it's own controls + its own event codes. The fact only this or
> > the DC version can be enabled at any time should only be exposed in the
> > reported state, not apparent via what files we expose etc. On some
> > other device they may be independent hardware blocks.
> >
> > Note I'd also expect to see value controls for these new events. You may
> > need to cache the values and update on event change if the meaning is
> > very different. That's because the expectation would be an event
> > setup sequence from userspace is:
> >
> > 1) Set value of threshold
> > 2) Enable event
> >
> > On a change of event (due to shared hardware) The value set may scramble
> > the event already enabled.
> >
> > So write the values into a cache and update to the right one when changing
> > event.
> >
>
> Might be that I got you wrong here, but I assume the above does
> actually not apply.
>
> Best,
> L
>
> > > ---
> > > drivers/iio/accel/adxl345_core.c | 162 ++++++++++++++++++++++++++++++-
> > > 1 file changed, 159 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > index b25efcad069b..c07ad5774c8a 100644
> > > --- a/drivers/iio/accel/adxl345_core.c
> > > +++ b/drivers/iio/accel/adxl345_core.c
> > > @@ -37,7 +37,9 @@
> > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > #define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > +#define ADXL345_REG_ACT_ACDC_MSK BIT(7)
> > > #define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
> > > +#define ADXL345_REG_INACT_ACDC_MSK BIT(3)
> > > #define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
> > >
> > > #define ADXL345_TAP_Z_EN BIT(0)
> > > @@ -91,6 +93,11 @@ static const unsigned int adxl345_act_thresh_reg[] = {
> > > [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
> > > };
> > >
> > > +static const unsigned int adxl345_act_acdc_msk[] = {
> > > + [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC_MSK,
> > > + [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC_MSK,
> > > +};
> > > +
> > > enum adxl345_odr {
> > > ADXL345_ODR_0P10HZ = 0,
> > > ADXL345_ODR_0P20HZ,
> > > @@ -204,6 +211,18 @@ static struct iio_event_spec adxl345_events[] = {
> > > BIT(IIO_EV_INFO_RESET_TIMEOUT) |
> > > BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
> > > },
> > > + {
> > > + /* activity, activity - ac bit */
> > Comment says activity and inactivity but channel type wise this
> > is just activity (as rising)
> >
> > > + .type = IIO_EV_TYPE_MAG_REFERENCED,
> > > + .dir = IIO_EV_DIR_RISING,
> > > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> > > + },
> > > + {
> > > + /* activity, inactivity - ac bit */
> >
> > Likewise this seems to be inactivity. Should this be in the x&y&z
> > channel, not this one?
> >
> > > + .type = IIO_EV_TYPE_MAG_REFERENCED,
> > > + .dir = IIO_EV_DIR_FALLING,
> > > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> > > + },
> > > };
> > >
> > > #define ADXL345_CHANNEL(index, reg, axis) { \
> > > @@ -320,6 +339,69 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > >
> > > /* act/inact */
> > >
> > > +static int adxl345_is_act_inact_ac(struct adxl345_state *st,
> > > + enum adxl345_activity_type type, bool *ac)
> > > +{
> > > + unsigned int regval;
> > > + int ret;
> > > +
> > > + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + if (type == ADXL345_ACTIVITY)
> > > + *ac = FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval);
> > > + else
> > > + *ac = FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int adxl345_set_act_inact_ac(struct adxl345_state *st,
> > > + enum adxl345_activity_type type, bool ac)
> > > +{
> > > + unsigned int act_inact_ac = ac ? 0xff : 0x00;
> > > +
> > > + /*
> > > + * A setting of false selects dc-coupled operation, and a setting of
> > > + * true enables ac-coupled operation. In dc-coupled operation, the
> > > + * current acceleration magnitude is compared directly with
> > > + * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine
> > > + * whether activity or inactivity is detected.
> > > + *
> > > + * In ac-coupled operation for activity detection, the acceleration
> > > + * value at the start of activity detection is taken as a reference
> > > + * value. New samples of acceleration are then compared to this
> > > + * reference value, and if the magnitude of the difference exceeds the
> > > + * ADXL345_REG_THRESH_ACT value, the device triggers an activity
> > > + * interrupt.
> > > + *
> > > + * Similarly, in ac-coupled operation for inactivity detection, a
> > > + * reference value is used for comparison and is updated whenever the
> > > + * device exceeds the inactivity threshold. After the reference value
> > > + * is selected, the device compares the magnitude of the difference
> > > + * between the reference value and the current acceleration with
> > > + * ADXL345_REG_THRESH_INACT. If the difference is less than the value in
> > > + * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the
> > > + * device is considered inactive and the inactivity interrupt is
> > > + * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G]
> > > + *
> > > + * In a conclusion, the first acceleration snapshot sample which hit the
> > > + * threshold in a particular direction is always taken as acceleration
> > > + * reference value to that direction. Since for the hardware activity
> > > + * and inactivity depend on the x/y/z axis, so do ac and dc coupling.
> > > + * Note, this sw driver always enables or disables all three x/y/z axis
> > > + * for detection via act_axis_ctrl and inact_axis_ctrl, respectively.
> > > + * Where in dc-coupling samples are compared against the thresholds, in
> > > + * ac-coupling measurement difference to the first acceleration
> > > + * reference value are compared against the threshold. So, ac-coupling
> > > + * allows for a bit more dynamic compensation depending on the initial
> > > + * sample.
> > > + */
> > > + return regmap_update_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
> > > + adxl345_act_acdc_msk[type], act_inact_ac);
> > > +}
> >
> > > static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> > > @@ -797,9 +886,51 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> > >
> > > static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
> > > {
> > > - return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> > > + unsigned int act_threshold, inact_threshold;
> > > + unsigned int range_old;
> > > + unsigned int regval;
> > > + int ret;
> > > +
> > > + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val);
> > > + if (ret)
> > > + return ret;
> > > + range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
> > > +
> > > + ret = regmap_read(st->regmap,
> > > + adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> > > + &act_threshold);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = regmap_read(st->regmap,
> > > + adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> > > + &inact_threshold);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> > > ADXL345_DATA_FORMAT_RANGE,
> > > FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
> > > + if (ret)
> > > + return ret;
> > > +
> > > + act_threshold = act_threshold
> > > + * adxl345_range_factor_tbl[range_old]
> > > + / adxl345_range_factor_tbl[range];
> > > + act_threshold = min(255, max(1, inact_threshold));
> > > +
> > This is first use of the range table. So introduce that in this patch.
> >
> > > + inact_threshold = inact_threshold
> > > + * adxl345_range_factor_tbl[range_old]
> > > + / adxl345_range_factor_tbl[range];
> > > + inact_threshold = min(255, max(1, inact_threshold));
> > > +
> > > + ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> > > + act_threshold);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> > > + inact_threshold);
> > > }
> > >
> > > static int adxl345_read_avail(struct iio_dev *indio_dev,
> > > @@ -938,7 +1069,7 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> > > enum iio_event_direction dir)
> > > {
> > > struct adxl345_state *st = iio_priv(indio_dev);
> > > - bool int_en;
> > > + bool int_en, act_ac, inact_ac;
> > > int ret;
> > >
> > > switch (type) {
> > > @@ -983,6 +1114,21 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> > > if (ret)
> > > return ret;
> > > return int_en;
> > > + case IIO_EV_TYPE_MAG_REFERENCED:
> > > + switch (dir) {
> > > + case IIO_EV_DIR_RISING:
> > > + ret = adxl345_is_act_inact_ac(st, ADXL345_ACTIVITY, &act_ac);
> >
> > Do we not need a check in the enabling of the DC events as well? If we have enabled
> > AC the DC one should report disabled (and if we enable that again then we should
> > update this.
> >
> > > + if (ret)
> > > + return ret;
> > > + return act_ac;
> > > + case IIO_EV_DIR_FALLING:
> > > + ret = adxl345_is_act_inact_ac(st, ADXL345_INACTIVITY, &inact_ac);
> > > + if (ret)
> > > + return ret;
> > > + return inact_ac;
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > default:
> > > return -EINVAL;
> > > }
> > > @@ -1019,6 +1165,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
> > > }
> > > case IIO_EV_TYPE_MAG:
> > > return adxl345_set_ff_en(st, state);
> > > + case IIO_EV_TYPE_MAG_REFERENCED:
> > > + switch (dir) {
> > > + case IIO_EV_DIR_RISING:
> > > + return adxl345_set_act_inact_ac(st, ADXL345_ACTIVITY, state);
> >
> > Similar to read path. The DC events should be affected by this as well as the AC ones.
> >
> > > + case IIO_EV_DIR_FALLING:
> > > + return adxl345_set_act_inact_ac(st, ADXL345_INACTIVITY, state);
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > +
> > > default:
> > > return -EINVAL;
> > > }
> >
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-05-04 10:39 ` Jonathan Cameron
@ 2025-05-07 20:04 ` Lothar Rubusch
2025-05-15 7:36 ` Jonathan Cameron
0 siblings, 1 reply; 32+ messages in thread
From: Lothar Rubusch @ 2025-05-07 20:04 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
Hi Jonathan,
On Sun, May 4, 2025 at 12:39 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Thu, 1 May 2025 09:35:29 +0200
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > On Sun, Apr 27, 2025 at 3:00 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > >
> > > On Mon, 21 Apr 2025 22:06:40 +0000
> > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > >
> > > > Add coupling activity/inactivity detection by the AC/DC bit. This is an
> > > > addititional enhancement for the detection of activity states and
> > > > completes the activity / inactivity feature of the ADXL345.
> > > >
> > > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > >
> > > I've dragged the table from the earlier patch into this one that actually uses it.
> > > However I'm a little unsure on exactly how we present this feature.
> > >
> > > So until those questions are resolved I've dropped the patch (you'll need
> > > to rebase on my testing branch and fix up missing table for v8).
> > >
> > > The bit that made me not apply this series (with some tweaks) was that
> > > I'd expect enabling AC events to be visible as disabling of DC ones.
> > >
> >
> > There are no AC events, nor DC ones.
> >
> > Think of AC- or DC-coupled detection as modes of operating the
> > ACTIVITY/INACTIVITY
> > detection. The events are ACTIVITY or INACTIVITY.
>
> That's where we differ. For generalizing an ABI we absolutely should
> not think of them as modes - that's an implementation detail only
>
> > It has effect on how
> > the sensor detects
> > if it needs to trigger an (IN)/ACTIVITY event. DC is just going by the
> > configured thresholds,
> > where AC is supposed to apply some more elaborate way of declaring ACTIVITY or
> > INACTIVITY.
> >
> > The fact that you imply on this means to me, at least I explained it
> > wrong, or need to clarify
> > better.
>
> Not at all (I think). This is a question of datasheet representation vs
> what we are doing in the ABI. Lets have a thought experiment.
>
> If the device had separate enables, thresholds and periods for
> DC activity, DC inactivity, AC activity and AC inactivity would you be
> thinking of them as modes of one event? They would be separate event types
> that could be all enabled at the same time.
>
> Now step 2. We have many sensors that have limited numbers of highly programmable
> event detection engines (the adis IMUs for instance have only a few such engines
> but many types of events). For those we treat them as a fifo. If you request
> 3 events and the device has 2 detectors you get the last two that were asked
> for. We do this because we don't want a detector with 10s of modes with
> the parameter meaning changing for each one. The ABI should look the same
> as independent detectors. Some models are more complex and have more such
> engines, but the interface remains the same.
>
> So here we have a pair of less flexible engines that can support either
> AC or DC for inactivity/activity. We should represent that the same
> way we represent the separate engines case. Hence when you enable
> activity AC, our short (1 element) fifo means we disable activity DC.
>
> Which is why treating them as separate engines (with rules on which can
> be enabled at any time) is the right way to go. IIRC correctly long
> ago we had various experiments with interfaces around this (including
> mode based ones) and ended up with this approach that looks like a
> whole bunch of event detectors with a mux that means we can only use
> a subset at a time.
>
> Jonathan
>
Thank you so much for this explanation. It makes it much clearer to
me now to understand what
you were writing about before.
Anyway, I just wanted to confirm what is missing here. I'll need to
distinguish the following events:
- activity_ac
- inactivity_ac
- activity_dc
- inactivity_dc
For the implementation, each shall be a separate event, i.e. each will
have separate handles in sysfs:
- event config: enable
- event value: threshold
- event value: time
Internally I will use the regmap cached flag for AC/DC to distinguish
if the ACTIVITY or INACTIVITY event belongs to activity_ac or
inactivity_dc. Threshold and time values will be specific to ACTIVITY
and INACTIVITY, respectively. Since, the events will be still activity
/ inactivity, as their registers will be so, too. The distinction thus
will be rather a question of the presented handles in sysfs.
All events will be type MAG, and dir RISING (activity) or FALLING
(inactivity). As said above, dc / ac coupling I will read from the
cache.
Is this approach sufficient to solve the required adjustments, or do
the events themselves need further information about coupling (I guess
so, how can I represent this in the events?)?
Best,
L
>
> >
> > > Also, I just noticed you aren't pushing the new event types.
> > >
> > > These controls need to look like a separate event detector hardware block
> > > with it's own controls + its own event codes. The fact only this or
> > > the DC version can be enabled at any time should only be exposed in the
> > > reported state, not apparent via what files we expose etc. On some
> > > other device they may be independent hardware blocks.
> > >
> > > Note I'd also expect to see value controls for these new events. You may
> > > need to cache the values and update on event change if the meaning is
> > > very different. That's because the expectation would be an event
> > > setup sequence from userspace is:
> > >
> > > 1) Set value of threshold
> > > 2) Enable event
> > >
> > > On a change of event (due to shared hardware) The value set may scramble
> > > the event already enabled.
> > >
> > > So write the values into a cache and update to the right one when changing
> > > event.
> > >
> >
> > Might be that I got you wrong here, but I assume the above does
> > actually not apply.
> >
> > Best,
> > L
> >
> > > > ---
> > > > drivers/iio/accel/adxl345_core.c | 162 ++++++++++++++++++++++++++++++-
> > > > 1 file changed, 159 insertions(+), 3 deletions(-)
> > > >
> > > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > > index b25efcad069b..c07ad5774c8a 100644
> > > > --- a/drivers/iio/accel/adxl345_core.c
> > > > +++ b/drivers/iio/accel/adxl345_core.c
> > > > @@ -37,7 +37,9 @@
> > > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > > #define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > > +#define ADXL345_REG_ACT_ACDC_MSK BIT(7)
> > > > #define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
> > > > +#define ADXL345_REG_INACT_ACDC_MSK BIT(3)
> > > > #define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
> > > >
> > > > #define ADXL345_TAP_Z_EN BIT(0)
> > > > @@ -91,6 +93,11 @@ static const unsigned int adxl345_act_thresh_reg[] = {
> > > > [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
> > > > };
> > > >
> > > > +static const unsigned int adxl345_act_acdc_msk[] = {
> > > > + [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC_MSK,
> > > > + [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC_MSK,
> > > > +};
> > > > +
> > > > enum adxl345_odr {
> > > > ADXL345_ODR_0P10HZ = 0,
> > > > ADXL345_ODR_0P20HZ,
> > > > @@ -204,6 +211,18 @@ static struct iio_event_spec adxl345_events[] = {
> > > > BIT(IIO_EV_INFO_RESET_TIMEOUT) |
> > > > BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
> > > > },
> > > > + {
> > > > + /* activity, activity - ac bit */
> > > Comment says activity and inactivity but channel type wise this
> > > is just activity (as rising)
> > >
> > > > + .type = IIO_EV_TYPE_MAG_REFERENCED,
> > > > + .dir = IIO_EV_DIR_RISING,
> > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> > > > + },
> > > > + {
> > > > + /* activity, inactivity - ac bit */
> > >
> > > Likewise this seems to be inactivity. Should this be in the x&y&z
> > > channel, not this one?
> > >
> > > > + .type = IIO_EV_TYPE_MAG_REFERENCED,
> > > > + .dir = IIO_EV_DIR_FALLING,
> > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> > > > + },
> > > > };
> > > >
> > > > #define ADXL345_CHANNEL(index, reg, axis) { \
> > > > @@ -320,6 +339,69 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > >
> > > > /* act/inact */
> > > >
> > > > +static int adxl345_is_act_inact_ac(struct adxl345_state *st,
> > > > + enum adxl345_activity_type type, bool *ac)
> > > > +{
> > > > + unsigned int regval;
> > > > + int ret;
> > > > +
> > > > + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + if (type == ADXL345_ACTIVITY)
> > > > + *ac = FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval);
> > > > + else
> > > > + *ac = FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int adxl345_set_act_inact_ac(struct adxl345_state *st,
> > > > + enum adxl345_activity_type type, bool ac)
> > > > +{
> > > > + unsigned int act_inact_ac = ac ? 0xff : 0x00;
> > > > +
> > > > + /*
> > > > + * A setting of false selects dc-coupled operation, and a setting of
> > > > + * true enables ac-coupled operation. In dc-coupled operation, the
> > > > + * current acceleration magnitude is compared directly with
> > > > + * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine
> > > > + * whether activity or inactivity is detected.
> > > > + *
> > > > + * In ac-coupled operation for activity detection, the acceleration
> > > > + * value at the start of activity detection is taken as a reference
> > > > + * value. New samples of acceleration are then compared to this
> > > > + * reference value, and if the magnitude of the difference exceeds the
> > > > + * ADXL345_REG_THRESH_ACT value, the device triggers an activity
> > > > + * interrupt.
> > > > + *
> > > > + * Similarly, in ac-coupled operation for inactivity detection, a
> > > > + * reference value is used for comparison and is updated whenever the
> > > > + * device exceeds the inactivity threshold. After the reference value
> > > > + * is selected, the device compares the magnitude of the difference
> > > > + * between the reference value and the current acceleration with
> > > > + * ADXL345_REG_THRESH_INACT. If the difference is less than the value in
> > > > + * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the
> > > > + * device is considered inactive and the inactivity interrupt is
> > > > + * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G]
> > > > + *
> > > > + * In a conclusion, the first acceleration snapshot sample which hit the
> > > > + * threshold in a particular direction is always taken as acceleration
> > > > + * reference value to that direction. Since for the hardware activity
> > > > + * and inactivity depend on the x/y/z axis, so do ac and dc coupling.
> > > > + * Note, this sw driver always enables or disables all three x/y/z axis
> > > > + * for detection via act_axis_ctrl and inact_axis_ctrl, respectively.
> > > > + * Where in dc-coupling samples are compared against the thresholds, in
> > > > + * ac-coupling measurement difference to the first acceleration
> > > > + * reference value are compared against the threshold. So, ac-coupling
> > > > + * allows for a bit more dynamic compensation depending on the initial
> > > > + * sample.
> > > > + */
> > > > + return regmap_update_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
> > > > + adxl345_act_acdc_msk[type], act_inact_ac);
> > > > +}
> > >
> > > > static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> > > > @@ -797,9 +886,51 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> > > >
> > > > static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
> > > > {
> > > > - return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> > > > + unsigned int act_threshold, inact_threshold;
> > > > + unsigned int range_old;
> > > > + unsigned int regval;
> > > > + int ret;
> > > > +
> > > > + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val);
> > > > + if (ret)
> > > > + return ret;
> > > > + range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
> > > > +
> > > > + ret = regmap_read(st->regmap,
> > > > + adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> > > > + &act_threshold);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + ret = regmap_read(st->regmap,
> > > > + adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> > > > + &inact_threshold);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> > > > ADXL345_DATA_FORMAT_RANGE,
> > > > FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + act_threshold = act_threshold
> > > > + * adxl345_range_factor_tbl[range_old]
> > > > + / adxl345_range_factor_tbl[range];
> > > > + act_threshold = min(255, max(1, inact_threshold));
> > > > +
> > > This is first use of the range table. So introduce that in this patch.
> > >
> > > > + inact_threshold = inact_threshold
> > > > + * adxl345_range_factor_tbl[range_old]
> > > > + / adxl345_range_factor_tbl[range];
> > > > + inact_threshold = min(255, max(1, inact_threshold));
> > > > +
> > > > + ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> > > > + act_threshold);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + return regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> > > > + inact_threshold);
> > > > }
> > > >
> > > > static int adxl345_read_avail(struct iio_dev *indio_dev,
> > > > @@ -938,7 +1069,7 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> > > > enum iio_event_direction dir)
> > > > {
> > > > struct adxl345_state *st = iio_priv(indio_dev);
> > > > - bool int_en;
> > > > + bool int_en, act_ac, inact_ac;
> > > > int ret;
> > > >
> > > > switch (type) {
> > > > @@ -983,6 +1114,21 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> > > > if (ret)
> > > > return ret;
> > > > return int_en;
> > > > + case IIO_EV_TYPE_MAG_REFERENCED:
> > > > + switch (dir) {
> > > > + case IIO_EV_DIR_RISING:
> > > > + ret = adxl345_is_act_inact_ac(st, ADXL345_ACTIVITY, &act_ac);
> > >
> > > Do we not need a check in the enabling of the DC events as well? If we have enabled
> > > AC the DC one should report disabled (and if we enable that again then we should
> > > update this.
> > >
> > > > + if (ret)
> > > > + return ret;
> > > > + return act_ac;
> > > > + case IIO_EV_DIR_FALLING:
> > > > + ret = adxl345_is_act_inact_ac(st, ADXL345_INACTIVITY, &inact_ac);
> > > > + if (ret)
> > > > + return ret;
> > > > + return inact_ac;
> > > > + default:
> > > > + return -EINVAL;
> > > > + }
> > > > default:
> > > > return -EINVAL;
> > > > }
> > > > @@ -1019,6 +1165,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
> > > > }
> > > > case IIO_EV_TYPE_MAG:
> > > > return adxl345_set_ff_en(st, state);
> > > > + case IIO_EV_TYPE_MAG_REFERENCED:
> > > > + switch (dir) {
> > > > + case IIO_EV_DIR_RISING:
> > > > + return adxl345_set_act_inact_ac(st, ADXL345_ACTIVITY, state);
> > >
> > > Similar to read path. The DC events should be affected by this as well as the AC ones.
> > >
> > > > + case IIO_EV_DIR_FALLING:
> > > > + return adxl345_set_act_inact_ac(st, ADXL345_INACTIVITY, state);
> > > > + default:
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > default:
> > > > return -EINVAL;
> > > > }
> > >
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-05-07 20:04 ` Lothar Rubusch
@ 2025-05-15 7:36 ` Jonathan Cameron
0 siblings, 0 replies; 32+ messages in thread
From: Jonathan Cameron @ 2025-05-15 7:36 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Wed, 7 May 2025 22:04:29 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Hi Jonathan,
>
> On Sun, May 4, 2025 at 12:39 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Thu, 1 May 2025 09:35:29 +0200
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > On Sun, Apr 27, 2025 at 3:00 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > >
> > > > On Mon, 21 Apr 2025 22:06:40 +0000
> > > > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > > >
> > > > > Add coupling activity/inactivity detection by the AC/DC bit. This is an
> > > > > addititional enhancement for the detection of activity states and
> > > > > completes the activity / inactivity feature of the ADXL345.
> > > > >
> > > > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > >
> > > > I've dragged the table from the earlier patch into this one that actually uses it.
> > > > However I'm a little unsure on exactly how we present this feature.
> > > >
> > > > So until those questions are resolved I've dropped the patch (you'll need
> > > > to rebase on my testing branch and fix up missing table for v8).
> > > >
> > > > The bit that made me not apply this series (with some tweaks) was that
> > > > I'd expect enabling AC events to be visible as disabling of DC ones.
> > > >
> > >
> > > There are no AC events, nor DC ones.
> > >
> > > Think of AC- or DC-coupled detection as modes of operating the
> > > ACTIVITY/INACTIVITY
> > > detection. The events are ACTIVITY or INACTIVITY.
> >
> > That's where we differ. For generalizing an ABI we absolutely should
> > not think of them as modes - that's an implementation detail only
> >
> > > It has effect on how
> > > the sensor detects
> > > if it needs to trigger an (IN)/ACTIVITY event. DC is just going by the
> > > configured thresholds,
> > > where AC is supposed to apply some more elaborate way of declaring ACTIVITY or
> > > INACTIVITY.
> > >
> > > The fact that you imply on this means to me, at least I explained it
> > > wrong, or need to clarify
> > > better.
> >
> > Not at all (I think). This is a question of datasheet representation vs
> > what we are doing in the ABI. Lets have a thought experiment.
> >
> > If the device had separate enables, thresholds and periods for
> > DC activity, DC inactivity, AC activity and AC inactivity would you be
> > thinking of them as modes of one event? They would be separate event types
> > that could be all enabled at the same time.
> >
> > Now step 2. We have many sensors that have limited numbers of highly programmable
> > event detection engines (the adis IMUs for instance have only a few such engines
> > but many types of events). For those we treat them as a fifo. If you request
> > 3 events and the device has 2 detectors you get the last two that were asked
> > for. We do this because we don't want a detector with 10s of modes with
> > the parameter meaning changing for each one. The ABI should look the same
> > as independent detectors. Some models are more complex and have more such
> > engines, but the interface remains the same.
> >
> > So here we have a pair of less flexible engines that can support either
> > AC or DC for inactivity/activity. We should represent that the same
> > way we represent the separate engines case. Hence when you enable
> > activity AC, our short (1 element) fifo means we disable activity DC.
> >
> > Which is why treating them as separate engines (with rules on which can
> > be enabled at any time) is the right way to go. IIRC correctly long
> > ago we had various experiments with interfaces around this (including
> > mode based ones) and ended up with this approach that looks like a
> > whole bunch of event detectors with a mux that means we can only use
> > a subset at a time.
> >
> > Jonathan
> >
>
> Thank you so much for this explanation. It makes it much clearer to
> me now to understand what
> you were writing about before.
>
> Anyway, I just wanted to confirm what is missing here. I'll need to
> distinguish the following events:
> - activity_ac
> - inactivity_ac
> - activity_dc
> - inactivity_dc
>
> For the implementation, each shall be a separate event, i.e. each will
> have separate handles in sysfs:
> - event config: enable
> - event value: threshold
> - event value: time
>
yes, so 4 different events for inactivty/activity - only one type
of inactivity enabled at a time and only one type of activity event enabled
> Internally I will use the regmap cached flag for AC/DC to distinguish
> if the ACTIVITY or INACTIVITY event belongs to activity_ac or
> inactivity_dc. Threshold and time values will be specific to ACTIVITY
> and INACTIVITY, respectively. Since, the events will be still activity
> / inactivity, as their registers will be so, too.
> The distinction thus
> will be rather a question of the presented handles in sysfs.
> All events will be type MAG, and dir RISING (activity) or FALLING
> (inactivity). As said above, dc / ac coupling I will read from the
> cache.
Not quite. The caching bit is all fine but the sysfs events
have to represent AC/DC and they do that with different event types.
>
> Is this approach sufficient to solve the required adjustments, or do
> the events themselves need further information about coupling (I guess
> so, how can I represent this in the events?)?
activity_dc is a simple magnitude threshold type event.
activity_ac is one of two options depending on how we think about it.
MAG_REFERENCED if we ignore the reset of the reference when activity_ac and
inactivity_ac are coupled.
MAG_ADAPTIVE if we take it that the base point for the threshold is changing
over time (which is slightly more accurate if inactivity and activity are
coupled).
I think I slightly prefer MAG_ADAPTIVE but it's a corner case because
until the other state occurs we don't adapt and it looks more like
a referenced event.
So we'd have 4 event types for these. 2 for activity and 2 for inactivity.
Jonathan
>
> Best,
> L
>
> >
> > >
> > > > Also, I just noticed you aren't pushing the new event types.
> > > >
> > > > These controls need to look like a separate event detector hardware block
> > > > with it's own controls + its own event codes. The fact only this or
> > > > the DC version can be enabled at any time should only be exposed in the
> > > > reported state, not apparent via what files we expose etc. On some
> > > > other device they may be independent hardware blocks.
> > > >
> > > > Note I'd also expect to see value controls for these new events. You may
> > > > need to cache the values and update on event change if the meaning is
> > > > very different. That's because the expectation would be an event
> > > > setup sequence from userspace is:
> > > >
> > > > 1) Set value of threshold
> > > > 2) Enable event
> > > >
> > > > On a change of event (due to shared hardware) The value set may scramble
> > > > the event already enabled.
> > > >
> > > > So write the values into a cache and update to the right one when changing
> > > > event.
> > > >
> > >
> > > Might be that I got you wrong here, but I assume the above does
> > > actually not apply.
> > >
> > > Best,
> > > L
> > >
> > > > > ---
> > > > > drivers/iio/accel/adxl345_core.c | 162 ++++++++++++++++++++++++++++++-
> > > > > 1 file changed, 159 insertions(+), 3 deletions(-)
> > > > >
> > > > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > > > index b25efcad069b..c07ad5774c8a 100644
> > > > > --- a/drivers/iio/accel/adxl345_core.c
> > > > > +++ b/drivers/iio/accel/adxl345_core.c
> > > > > @@ -37,7 +37,9 @@
> > > > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
> > > > > #define ADXL345_REG_TAP_SUPPRESS BIT(3)
> > > > > #define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > > > +#define ADXL345_REG_ACT_ACDC_MSK BIT(7)
> > > > > #define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
> > > > > +#define ADXL345_REG_INACT_ACDC_MSK BIT(3)
> > > > > #define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
> > > > >
> > > > > #define ADXL345_TAP_Z_EN BIT(0)
> > > > > @@ -91,6 +93,11 @@ static const unsigned int adxl345_act_thresh_reg[] = {
> > > > > [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
> > > > > };
> > > > >
> > > > > +static const unsigned int adxl345_act_acdc_msk[] = {
> > > > > + [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC_MSK,
> > > > > + [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC_MSK,
> > > > > +};
> > > > > +
> > > > > enum adxl345_odr {
> > > > > ADXL345_ODR_0P10HZ = 0,
> > > > > ADXL345_ODR_0P20HZ,
> > > > > @@ -204,6 +211,18 @@ static struct iio_event_spec adxl345_events[] = {
> > > > > BIT(IIO_EV_INFO_RESET_TIMEOUT) |
> > > > > BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
> > > > > },
> > > > > + {
> > > > > + /* activity, activity - ac bit */
> > > > Comment says activity and inactivity but channel type wise this
> > > > is just activity (as rising)
> > > >
> > > > > + .type = IIO_EV_TYPE_MAG_REFERENCED,
> > > > > + .dir = IIO_EV_DIR_RISING,
> > > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> > > > > + },
> > > > > + {
> > > > > + /* activity, inactivity - ac bit */
> > > >
> > > > Likewise this seems to be inactivity. Should this be in the x&y&z
> > > > channel, not this one?
> > > >
> > > > > + .type = IIO_EV_TYPE_MAG_REFERENCED,
> > > > > + .dir = IIO_EV_DIR_FALLING,
> > > > > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
> > > > > + },
> > > > > };
> > > > >
> > > > > #define ADXL345_CHANNEL(index, reg, axis) { \
> > > > > @@ -320,6 +339,69 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> > > > >
> > > > > /* act/inact */
> > > > >
> > > > > +static int adxl345_is_act_inact_ac(struct adxl345_state *st,
> > > > > + enum adxl345_activity_type type, bool *ac)
> > > > > +{
> > > > > + unsigned int regval;
> > > > > + int ret;
> > > > > +
> > > > > + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > +
> > > > > + if (type == ADXL345_ACTIVITY)
> > > > > + *ac = FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval);
> > > > > + else
> > > > > + *ac = FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static int adxl345_set_act_inact_ac(struct adxl345_state *st,
> > > > > + enum adxl345_activity_type type, bool ac)
> > > > > +{
> > > > > + unsigned int act_inact_ac = ac ? 0xff : 0x00;
> > > > > +
> > > > > + /*
> > > > > + * A setting of false selects dc-coupled operation, and a setting of
> > > > > + * true enables ac-coupled operation. In dc-coupled operation, the
> > > > > + * current acceleration magnitude is compared directly with
> > > > > + * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine
> > > > > + * whether activity or inactivity is detected.
> > > > > + *
> > > > > + * In ac-coupled operation for activity detection, the acceleration
> > > > > + * value at the start of activity detection is taken as a reference
> > > > > + * value. New samples of acceleration are then compared to this
> > > > > + * reference value, and if the magnitude of the difference exceeds the
> > > > > + * ADXL345_REG_THRESH_ACT value, the device triggers an activity
> > > > > + * interrupt.
> > > > > + *
> > > > > + * Similarly, in ac-coupled operation for inactivity detection, a
> > > > > + * reference value is used for comparison and is updated whenever the
> > > > > + * device exceeds the inactivity threshold. After the reference value
> > > > > + * is selected, the device compares the magnitude of the difference
> > > > > + * between the reference value and the current acceleration with
> > > > > + * ADXL345_REG_THRESH_INACT. If the difference is less than the value in
> > > > > + * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the
> > > > > + * device is considered inactive and the inactivity interrupt is
> > > > > + * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G]
> > > > > + *
> > > > > + * In a conclusion, the first acceleration snapshot sample which hit the
> > > > > + * threshold in a particular direction is always taken as acceleration
> > > > > + * reference value to that direction. Since for the hardware activity
> > > > > + * and inactivity depend on the x/y/z axis, so do ac and dc coupling.
> > > > > + * Note, this sw driver always enables or disables all three x/y/z axis
> > > > > + * for detection via act_axis_ctrl and inact_axis_ctrl, respectively.
> > > > > + * Where in dc-coupling samples are compared against the thresholds, in
> > > > > + * ac-coupling measurement difference to the first acceleration
> > > > > + * reference value are compared against the threshold. So, ac-coupling
> > > > > + * allows for a bit more dynamic compensation depending on the initial
> > > > > + * sample.
> > > > > + */
> > > > > + return regmap_update_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
> > > > > + adxl345_act_acdc_msk[type], act_inact_ac);
> > > > > +}
> > > >
> > > > > static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> > > > > @@ -797,9 +886,51 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
> > > > >
> > > > > static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
> > > > > {
> > > > > - return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> > > > > + unsigned int act_threshold, inact_threshold;
> > > > > + unsigned int range_old;
> > > > > + unsigned int regval;
> > > > > + int ret;
> > > > > +
> > > > > + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > + range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
> > > > > +
> > > > > + ret = regmap_read(st->regmap,
> > > > > + adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> > > > > + &act_threshold);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > +
> > > > > + ret = regmap_read(st->regmap,
> > > > > + adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> > > > > + &inact_threshold);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > +
> > > > > + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
> > > > > ADXL345_DATA_FORMAT_RANGE,
> > > > > FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
> > > > > + if (ret)
> > > > > + return ret;
> > > > > +
> > > > > + act_threshold = act_threshold
> > > > > + * adxl345_range_factor_tbl[range_old]
> > > > > + / adxl345_range_factor_tbl[range];
> > > > > + act_threshold = min(255, max(1, inact_threshold));
> > > > > +
> > > > This is first use of the range table. So introduce that in this patch.
> > > >
> > > > > + inact_threshold = inact_threshold
> > > > > + * adxl345_range_factor_tbl[range_old]
> > > > > + / adxl345_range_factor_tbl[range];
> > > > > + inact_threshold = min(255, max(1, inact_threshold));
> > > > > +
> > > > > + ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY],
> > > > > + act_threshold);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > +
> > > > > + return regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_INACTIVITY],
> > > > > + inact_threshold);
> > > > > }
> > > > >
> > > > > static int adxl345_read_avail(struct iio_dev *indio_dev,
> > > > > @@ -938,7 +1069,7 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> > > > > enum iio_event_direction dir)
> > > > > {
> > > > > struct adxl345_state *st = iio_priv(indio_dev);
> > > > > - bool int_en;
> > > > > + bool int_en, act_ac, inact_ac;
> > > > > int ret;
> > > > >
> > > > > switch (type) {
> > > > > @@ -983,6 +1114,21 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
> > > > > if (ret)
> > > > > return ret;
> > > > > return int_en;
> > > > > + case IIO_EV_TYPE_MAG_REFERENCED:
> > > > > + switch (dir) {
> > > > > + case IIO_EV_DIR_RISING:
> > > > > + ret = adxl345_is_act_inact_ac(st, ADXL345_ACTIVITY, &act_ac);
> > > >
> > > > Do we not need a check in the enabling of the DC events as well? If we have enabled
> > > > AC the DC one should report disabled (and if we enable that again then we should
> > > > update this.
> > > >
> > > > > + if (ret)
> > > > > + return ret;
> > > > > + return act_ac;
> > > > > + case IIO_EV_DIR_FALLING:
> > > > > + ret = adxl345_is_act_inact_ac(st, ADXL345_INACTIVITY, &inact_ac);
> > > > > + if (ret)
> > > > > + return ret;
> > > > > + return inact_ac;
> > > > > + default:
> > > > > + return -EINVAL;
> > > > > + }
> > > > > default:
> > > > > return -EINVAL;
> > > > > }
> > > > > @@ -1019,6 +1165,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
> > > > > }
> > > > > case IIO_EV_TYPE_MAG:
> > > > > return adxl345_set_ff_en(st, state);
> > > > > + case IIO_EV_TYPE_MAG_REFERENCED:
> > > > > + switch (dir) {
> > > > > + case IIO_EV_DIR_RISING:
> > > > > + return adxl345_set_act_inact_ac(st, ADXL345_ACTIVITY, state);
> > > >
> > > > Similar to read path. The DC events should be affected by this as well as the AC ones.
> > > >
> > > > > + case IIO_EV_DIR_FALLING:
> > > > > + return adxl345_set_act_inact_ac(st, ADXL345_INACTIVITY, state);
> > > > > + default:
> > > > > + return -EINVAL;
> > > > > + }
> > > > > +
> > > > > default:
> > > > > return -EINVAL;
> > > > > }
> > > >
> >
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH v7 11/11] docs: iio: add documentation for adxl345 driver
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (9 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 10/11] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
@ 2025-04-21 22:06 ` Lothar Rubusch
2025-04-27 13:01 ` [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Jonathan Cameron
11 siblings, 0 replies; 32+ messages in thread
From: Lothar Rubusch @ 2025-04-21 22:06 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
The documentation describes the ADXL345 driver, IIO interface,
interface usage and configuration.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
Documentation/iio/adxl345.rst | 423 ++++++++++++++++++++++++++++++++++
1 file changed, 423 insertions(+)
create mode 100644 Documentation/iio/adxl345.rst
diff --git a/Documentation/iio/adxl345.rst b/Documentation/iio/adxl345.rst
new file mode 100644
index 000000000000..e12fe280a94f
--- /dev/null
+++ b/Documentation/iio/adxl345.rst
@@ -0,0 +1,423 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+ADXL345 driver
+===============
+
+This driver supports Analog Device's ADXL345/375 on SPI/I2C bus.
+
+1. Supported Devices
+====================
+
+* `ADXL345 <https://www.analog.com/ADXL345>`_
+* `ADXL375 <https://www.analog.com/ADXL375>`_
+
+The ADXL345 is a generic purpose low power, 3-axis accelerometer with selectable
+measurement ranges. The ADXL345 supports the ±2 g, ±4 g, ±8 g, and ±16 g ranges.
+
+2. Device Attributes
+====================
+
+Each IIO device, has a device folder under ``/sys/bus/iio/devices/iio:deviceX``,
+where X is the IIO index of the device. Under these folders reside a set of
+device files, depending on the characteristics and features of the hardware
+device in questions. These files are consistently generalized and documented in
+the IIO ABI documentation.
+
+The following table shows the ADXL345 related device files, found in the
+specific device folder path ``/sys/bus/iio/devices/iio:deviceX``.
+
++-------------------------------------------+----------------------------------------------------------+
+| 3-Axis Accelerometer related device files | Description |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_sampling_frequency | Currently selected sample rate. |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_sampling_frequency_available | Available sampling frequency configurations. |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_scale | Scale/range for the accelerometer channels. |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_scale_available | Available scale ranges for the accelerometer channel. |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_x_calibbias | Calibration offset for the X-axis accelerometer channel. |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_x_raw | Raw X-axis accelerometer channel value. |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_y_calibbias | y-axis acceleration offset correction |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_y_raw | Raw Y-axis accelerometer channel value. |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_z_calibbias | Calibration offset for the Z-axis accelerometer channel. |
++-------------------------------------------+----------------------------------------------------------+
+| in_accel_z_raw | Raw Z-axis accelerometer channel value. |
++-------------------------------------------+----------------------------------------------------------+
+
+Channel Processed Values
+-------------------------
+
+A channel value can be read from its _raw attribute. The value returned is the
+raw value as reported by the devices. To get the processed value of the channel,
+apply the following formula:
+
+.. code-block:: bash
+
+ processed value = (_raw + _offset) * _scale
+
+Where _offset and _scale are device attributes. If no _offset attribute is
+present, simply assume its value is 0.
+
++-------------------------------------+---------------------------+
+| Channel type | Measurement unit |
++-------------------------------------+---------------------------+
+| Acceleration on X, Y, and Z axis | Meters per second squared |
++-------------------------------------+---------------------------+
+
+Sensor Events
+-------------
+
+Particular IIO events will be triggered by the corresponding interrupts. The
+sensor driver supports no or one active INT line, where the sensor has two
+possible INT IOs. Configure the used INT line in the devicetree. If no INT line
+is configured, the sensor falls back to FIFO bypass mode and no events are
+possible, only X, Y and Z axis measurements are possible.
+
+The following table shows the ADXL345 related device files, found in the
+specific device folder path ``/sys/bus/iio/devices/iio:deviceX/events``.
+
++---------------------------------------------+-----------------------------------------+
+| Event handle | Description |
++---------------------------------------------+-----------------------------------------+
+| in_accel_gesture_doubletap_en | Enable double tap detection on all axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_gesture_doubletap_reset_timeout | Double tap window in [us] |
++---------------------------------------------+-----------------------------------------+
+| in_accel_gesture_doubletap_tap2_min_delay | Double tap latent in [us] |
++---------------------------------------------+-----------------------------------------+
+| in_accel_gesture_singletap_timeout | Single tap duration in [us] |
++---------------------------------------------+-----------------------------------------+
+| in_accel_gesture_singletap_value | Single tap threshold value in 62.5/LSB |
++---------------------------------------------+-----------------------------------------+
+| in_accel_x&y&z_mag_falling_en | Enable free fall detection on all axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_mag_falling_period | Free fall time in [us] |
++---------------------------------------------+-----------------------------------------+
+| in_accel_mag_falling_value | Free fall threshold value in 62.5/LSB |
++---------------------------------------------+-----------------------------------------+
+| in_accel_mag_referenced_falling_en | Set 1 to AC-coupled inactivity, 0 for DC|
++---------------------------------------------+-----------------------------------------+
+| in_accel_mag_referenced_rising_en | Set 1 to AC-coupled activity, 0 for DC |
++---------------------------------------------+-----------------------------------------+
+| in_accel_x&y&z_thresh_falling_en | Enable inactivity detection on all axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_thresh_falling_period | Inactivity time in seconds |
++---------------------------------------------+-----------------------------------------+
+| in_accel_thresh_falling_value | Inactivity threshold value in 62.5/LSB |
++---------------------------------------------+-----------------------------------------+
+| in_accel_x_thresh_rising_en | Enable activity detection on X axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_y_thresh_rising_en | Enable activity detection on Y axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_z_thresh_rising_en | Enable activity detection on Z axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_thresh_rising_value | Activity threshold value in 62.5/LSB |
++---------------------------------------------+-----------------------------------------+
+| in_accel_x_gesture_singletap_en | Enable single tap detection on X axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_y_gesture_singletap_en | Enable single tap detection on Y axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_z_gesture_singletap_en | Enable single tap detection on Z axis |
++---------------------------------------------+-----------------------------------------+
+
+Find a detailed description of a particular functionality in the sensor
+datasheet.
+
+Setting the **ODR** explicitly will result in estimated adjusted default values
+for the inactivity time detection, where higher frequencies shall default to
+longer wait periods, and vice versa. It is also possible to explicetly
+configure inactivity wait times, if the defaulting approach does not match
+application requirements. Setting 0 here, will fall back to default setting.
+
+The **g range** configuration also tries to estimate activity and inactivity
+thresholds when switching to another g range. The default range will be
+factorized by the relation of old range divided by new range. The value never
+becomes 0 and will be at least 1 and at most 255 i.e. 62.5g/LSB according to
+the datasheet. Nevertheless activity and inactivity thresholds can be
+overwritten by explicit values.
+
+When **activity** and **inactivity** events are enabled, the driver automatically
+will implement its hysteresis solution by setting link bit and autosleep bit.
+The link bit serially links the activity and inactivity functions. On the other
+side, the autosleep function switches the sensor to sleep mode if the
+inactivity function is enabled. This will reduce current consumption to the
+sub-12.5Hz rate. Inactivity time can be configured between 1s and 255s. When 0
+is configured as inactivity time, the driver will define a reasonable value
+depending on a heuristic approach to optimize power consumption.
+
+In **dc-coupled** operation, the current acceleration magnitude is compared
+directly with THRESH_ACT and THRESH_INACT registers to determine whether
+activity or inactivity was detected. In ac-coupled operation for activity
+detection, the acceleration value at the start of activity detection is taken
+as a reference value. New samples are then compared to this reference value.
+Note, ac-coupling and dc-coupling are individually set for activity and/or
+inactivity detection. Activity detection can be enabled on particular axis.
+Inactivity detection on the other side, is enabled or disabled on all axis.
+
+**Single tap** detection can be configured according to the datasheet by specifying
+threshold and duration. If only the single tap is in use, the single tap
+interrupt is triggered when the acceleration goes above threshold (i.e. DUR
+start) and below the threshold, as long as duration is not exceeded. If single
+tap and double tap are in use, the single tap is triggered when the doulbe tap
+event has been either validated or invalidated.
+
+For **double tap** configure additionally window and latency in [us]. Latency
+starts counting when the single tap goes below threshold and is a waiting
+period, any spikes here are ignored for double tap detection. After latency,
+the window starts. Any rise above threshold, with a consequent fall below
+threshold within window time, rises a double tap signal when going below
+threshold.
+
+Double tap event detection is best described in the datasheet. After a
+single tap event was detected, a double tap event can be detected. Therefore the
+signal must match several criteria, and detection can also considered invalid
+for three reasons:
+* If the **suppress bit** is set and when still in the tap latency period, any
+measurement of acceleration spike above the tap threshold invalidates double tap
+detection immediately, i.e. during latency must not occur spikes for double tap
+detection when the suppress bit is set.
+* A double tap event is considered invalid, if acceleration lies above the
+threshold at the start of the window time for double tap.
+* Additionally, double tap detection can be considered invalid, if an
+acceleration exceeds the time limit for taps, set by duration register.
+Note, since for double tap the same duration counts, i.e. when rising above
+threshold, a consequent falling below threshold has to be within duration time.
+Also note, the suppress bit is generally set when double tap is enabled.
+
+A **free fall** event will be detected if the signal goes below the configured
+threshold, for the configured time [us].
+
+Note, that activity/inactivy, as also freefall is recommended for 12.5 Hz ODR
+up to 400 Hz.
+
+Usage Examples
+--------------
+
+Show device name:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat name
+ adxl345
+
+Show accelerometer channels value:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_x_raw
+ -1
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_y_raw
+ 2
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_z_raw
+ -253
+
+Set calibration offset for accelerometer channels:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_x_calibbias
+ 0
+
+ root:/sys/bus/iio/devices/iio:device0> echo 50 > in_accel_x_calibbias
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_x_calibbias
+ 50
+
+Given the 13-bit full resolution, the available ranges are calculated by the
+following forumla:
+
+.. code-block:: bash
+
+ (g * 2 * 9.80665) / (2^(resolution) - 1) * 100; for g := 2|4|8|16
+
+Scale range configuration:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_scale
+ 0.478899
+ root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_scale_available
+ 0.478899 0.957798 1.915595 3.831190
+
+ root:/sys/bus/iio/devices/iio:device0> echo 1.915595 > ./in_accel_scale
+ root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_scale
+ 1.915595
+
+Set output data rate (ODR):
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_sampling_frequency
+ 200.000000
+
+ root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_sampling_frequency_available
+ 0.097000 0.195000 0.390000 0.781000 1.562000 3.125000 6.250000 12.500000 25.000000 50.000000 100.000000 200.000000 400.000000 800.000000 1600.000000 3200.000000
+
+ root:/sys/bus/iio/devices/iio:device0> echo 1.562000 > ./in_accel_sampling_frequency
+ root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_sampling_frequency
+ 1.562000
+
+Configure one or several events:
+
+.. code-block:: bash
+
+ root:> cd /sys/bus/iio/devices/iio:device0
+
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./buffer0/in_accel_x_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./buffer0/in_accel_y_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./buffer0/in_accel_z_en
+
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./scan_elements/in_accel_x_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./scan_elements/in_accel_y_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./scan_elements/in_accel_z_en
+
+ root:/sys/bus/iio/devices/iio:device0> echo 14 > ./in_accel_x_calibbias
+ root:/sys/bus/iio/devices/iio:device0> echo 2 > ./in_accel_y_calibbias
+ root:/sys/bus/iio/devices/iio:device0> echo -250 > ./in_accel_z_calibbias
+
+ root:/sys/bus/iio/devices/iio:device0> echo 24 > ./buffer0/length
+
+ ## activity, threshold [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 6 > ./events/in_accel_thresh_rising_value
+
+ ## inactivity, threshold, [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 4 > ./events/in_accel_thresh_falling_value
+
+ ## inactivity, time [s]
+ root:/sys/bus/iio/devices/iio:device0> echo 3 > ./events/in_accel_thresh_falling_period
+
+ ## singletap, threshold
+ root:/sys/bus/iio/devices/iio:device0> echo 35 > ./events/in_accel_gesture_singletap_value
+
+ ## singletap, duration [us]
+ root:/sys/bus/iio/devices/iio:device0> echo 0.001875 > ./events/in_accel_gesture_singletap_timeout
+
+ ## doubletap, window [us]
+ root:/sys/bus/iio/devices/iio:device0> echo 0.025 > ./events/in_accel_gesture_doubletap_reset_timeout
+
+ ## doubletap, latent [us]
+ root:/sys/bus/iio/devices/iio:device0> echo 0.025 > ./events/in_accel_gesture_doubletap_tap2_min_delay
+
+ ## freefall, threshold [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 8 > ./events/in_accel_mag_falling_value
+
+ ## freefall, time [ms]
+ root:/sys/bus/iio/devices/iio:device0> echo 1.25 > ./events/in_accel_mag_falling_period
+
+ ## activity, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_thresh_rising_en
+
+ ## inactivity, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x\&y\&z_thresh_falling_en
+
+ ## freefall, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x\&y\&z_mag_falling_en
+
+ ## singletap, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x_gesture_singletap_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_y_gesture_singletap_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_z_gesture_singletap_en
+
+ ## doubletap, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_gesture_doubletap_en
+
+Verify incoming events:
+
+.. code-block:: bash
+
+ root:# iio_event_monitor adxl345
+ Found IIO device with name adxl345 with device number 0
+ Event: time: 1739063415957073383, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063415963770218, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063416002563061, type: accel(z), channel: 0, evtype: gesture, direction: singletap
+ Event: time: 1739063426271128739, type: accel(x&y&z), channel: 0, evtype: thresh, direction: falling
+ Event: time: 1739063436539080713, type: accel(x&y&z), channel: 0, evtype: thresh, direction: falling
+ Event: time: 1739063438357970381, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446726161586, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446727892670, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446743019768, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446744650696, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446763559386, type: accel(z), channel: 0, evtype: gesture, direction: singletap
+ Event: time: 1739063448818126480, type: accel(x&y&z), channel: 0, evtype: thresh, direction: falling
+ ...
+
+Activity and inactivity belong together and indicate state changes as follows
+
+.. code-block:: bash
+
+ root:# iio_event_monitor adxl345
+ Found IIO device with name adxl345 with device number 0
+ Event: time: 1744648001133946293, type: accel(x), channel: 0, evtype: thresh, direction: rising
+ <after inactivity time elapsed>
+ Event: time: 1744648057724775499, type: accel(x&y&z), channel: 0, evtype: thresh, direction: falling
+ ...
+
+3. Device Buffers
+=================
+
+This driver supports IIO buffers.
+
+All devices support retrieving the raw acceleration and temperature measurements
+using buffers.
+
+Usage examples
+--------------
+
+Select channels for buffer read:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_accel_x_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_accel_y_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_accel_z_en
+
+Set the number of samples to be stored in the buffer:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> echo 10 > buffer/length
+
+Enable buffer readings:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > buffer/enable
+
+Obtain buffered data:
+
+.. code-block:: bash
+
+ root:> iio_readdev -b 16 -s 1024 adxl345 | hexdump -d
+ WARNING: High-speed mode not enabled
+ 0000000 00003 00012 00013 00005 00010 00011 00005 00011
+ 0000010 00013 00004 00012 00011 00003 00012 00014 00007
+ 0000020 00011 00013 00004 00013 00014 00003 00012 00013
+ 0000030 00004 00012 00013 00005 00011 00011 00005 00012
+ 0000040 00014 00005 00012 00014 00004 00010 00012 00004
+ 0000050 00013 00011 00003 00011 00012 00005 00011 00013
+ 0000060 00003 00012 00012 00003 00012 00012 00004 00012
+ 0000070 00012 00003 00013 00013 00003 00013 00012 00005
+ 0000080 00012 00013 00003 00011 00012 00005 00012 00013
+ 0000090 00003 00013 00011 00005 00013 00014 00003 00012
+ 00000a0 00012 00003 00012 00013 00004 00012 00015 00004
+ 00000b0 00014 00011 00003 00014 00013 00004 00012 00011
+ 00000c0 00004 00012 00013 00004 00014 00011 00004 00013
+ 00000d0 00012 00002 00014 00012 00005 00012 00013 00005
+ 00000e0 00013 00013 00003 00013 00013 00005 00012 00013
+ 00000f0 00004 00014 00015 00005 00012 00011 00005 00012
+ ...
+
+See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered
+data is structured.
+
+4. IIO Interfacing Tools
+========================
+
+See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO
+interfacing tools.
--
2.39.5
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events
2025-04-21 22:06 [PATCH v7 00/11] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (10 preceding siblings ...)
2025-04-21 22:06 ` [PATCH v7 11/11] docs: iio: add documentation for adxl345 driver Lothar Rubusch
@ 2025-04-27 13:01 ` Jonathan Cameron
11 siblings, 0 replies; 32+ messages in thread
From: Jonathan Cameron @ 2025-04-27 13:01 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Mon, 21 Apr 2025 22:06:30 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Add several interrupt based sensor detection events:
> - single tap
> - double tap
> - free fall
> - activity
> - inactivity
> - sample frequency
> - full frequency g range approach
> - documentation
>
> All the needed parameters for each and methods of adjusting them, and
> forward a resulting IIO event for each to the IIO channel.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Hi.
As you'll see if you follow replies through. I tweaked and queued much of this
but then noticed some issues in patch 9. Hence I've bounced it back to you and
backed out the patches from my tree for now.
Please either incorporate the changes I mentioned (or point out why they are wrong!)
Also rebase the whole thing iio.git/togreg so that you don't have the patches
that are already applied in v8.
Thanks,
Jonathan
^ permalink raw reply [flat|nested] 32+ messages in thread