* [PATCH v8 1/7] iio: accel: adxl345: extend sample frequency adjustments
2025-05-10 22:43 [PATCH v8 0/7] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
@ 2025-05-10 22:43 ` Lothar Rubusch
2025-05-25 16:57 ` Jonathan Cameron
2025-05-10 22:44 ` [PATCH v8 2/7] iio: accel: adxl345: add g-range configuration Lothar Rubusch
` (5 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Lothar Rubusch @ 2025-05-10 22:43 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 c464c87033fb..bbdc9d10d962 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;
@@ -107,6 +146,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', \
@@ -383,14 +423,53 @@ 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);
}
+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) {
@@ -428,12 +507,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;
@@ -444,7 +521,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:
@@ -452,20 +534,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,
@@ -654,7 +742,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;
}
@@ -667,19 +755,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;
@@ -931,9 +1006,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,
@@ -999,6 +1074,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] 13+ messages in thread
* Re: [PATCH v8 1/7] iio: accel: adxl345: extend sample frequency adjustments
2025-05-10 22:43 ` [PATCH v8 1/7] iio: accel: adxl345: extend sample frequency adjustments Lothar Rubusch
@ 2025-05-25 16:57 ` Jonathan Cameron
0 siblings, 0 replies; 13+ messages in thread
From: Jonathan Cameron @ 2025-05-25 16:57 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Sat, 10 May 2025 22:43:59 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> 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>
Applied to the testing branch of iio.git.
Note I'll rebase that on rc1 once it is available.
Thanks,
Jonathan
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v8 2/7] iio: accel: adxl345: add g-range configuration
2025-05-10 22:43 [PATCH v8 0/7] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-05-10 22:43 ` [PATCH v8 1/7] iio: accel: adxl345: extend sample frequency adjustments Lothar Rubusch
@ 2025-05-10 22:44 ` Lothar Rubusch
2025-05-25 16:58 ` Jonathan Cameron
2025-05-10 22:44 ` [PATCH v8 3/7] iio: accel: adxl345: add activity event feature Lothar Rubusch
` (4 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Lothar Rubusch @ 2025-05-10 22:44 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 | 82 ++++++++++++++++++++++++++++++--
1 file changed, 79 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index bbdc9d10d962..7c093c0241de 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,25 @@ 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 },
+};
+
struct adxl345_state {
const struct adxl345_chip_info *info;
struct regmap *regmap;
@@ -146,7 +172,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', \
@@ -446,12 +473,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;
@@ -470,6 +525,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) {
@@ -488,8 +544,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,
@@ -521,6 +581,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;
@@ -549,6 +610,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;
}
@@ -741,6 +811,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:
@@ -1083,6 +1155,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] 13+ messages in thread
* [PATCH v8 3/7] iio: accel: adxl345: add activity event feature
2025-05-10 22:43 [PATCH v8 0/7] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-05-10 22:43 ` [PATCH v8 1/7] iio: accel: adxl345: extend sample frequency adjustments Lothar Rubusch
2025-05-10 22:44 ` [PATCH v8 2/7] iio: accel: adxl345: add g-range configuration Lothar Rubusch
@ 2025-05-10 22:44 ` Lothar Rubusch
2025-05-10 22:44 ` [PATCH v8 4/7] iio: accel: adxl345: add inactivity feature Lothar Rubusch
` (3 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Lothar Rubusch @ 2025-05-10 22:44 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 | 218 ++++++++++++++++++++++++++++++-
1 file changed, 215 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 7c093c0241de..32d014bd1e52 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,
@@ -145,6 +163,13 @@ struct adxl345_state {
};
static struct iio_event_spec adxl345_events[] = {
+ {
+ /* activity */
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+ },
{
/* single tap */
.type = IIO_EV_TYPE_GESTURE,
@@ -239,6 +264,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,
@@ -636,6 +754,18 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
int ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ 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:
@@ -667,6 +797,15 @@ 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_MAG:
+ 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:
@@ -689,10 +828,30 @@ 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;
int ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ 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:
@@ -743,6 +902,25 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
return ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ 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:
@@ -985,7 +1163,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);
@@ -1012,6 +1191,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_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
samples = adxl345_get_samples(st);
if (samples < 0)
@@ -1039,6 +1228,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;
@@ -1047,7 +1237,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;
@@ -1058,12 +1249,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))
@@ -1224,6 +1422,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] 13+ messages in thread
* [PATCH v8 4/7] iio: accel: adxl345: add inactivity feature
2025-05-10 22:43 [PATCH v8 0/7] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (2 preceding siblings ...)
2025-05-10 22:44 ` [PATCH v8 3/7] iio: accel: adxl345: add activity event feature Lothar Rubusch
@ 2025-05-10 22:44 ` Lothar Rubusch
2025-05-10 22:44 ` [PATCH v8 5/7] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
` (2 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Lothar Rubusch @ 2025-05-10 22:44 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 | 176 ++++++++++++++++++++++++++++++-
1 file changed, 171 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 32d014bd1e52..ba68f304035a 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 {
@@ -214,10 +223,29 @@ enum adxl345_chans {
chan_x, chan_y, chan_z,
};
+static const struct iio_event_spec adxl345_fake_chan_events[] = {
+ {
+ /* inactivity */
+ .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_fake_chan_events,
+ .num_event_specs = ARRAY_SIZE(adxl345_fake_chan_events),
+ },
};
static const unsigned long adxl345_scan_masks[] = {
@@ -261,7 +289,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 */
@@ -293,6 +322,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) {
@@ -312,6 +356,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;
@@ -330,6 +375,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)
@@ -350,11 +398,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 */
@@ -763,6 +869,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;
}
@@ -803,6 +916,10 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
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;
}
@@ -828,7 +945,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;
int ret;
@@ -846,9 +964,26 @@ 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;
}
@@ -913,10 +1048,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;
}
@@ -1201,6 +1348,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_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)
@@ -1432,10 +1590,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] 13+ messages in thread
* [PATCH v8 5/7] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-05-10 22:43 [PATCH v8 0/7] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (3 preceding siblings ...)
2025-05-10 22:44 ` [PATCH v8 4/7] iio: accel: adxl345: add inactivity feature Lothar Rubusch
@ 2025-05-10 22:44 ` Lothar Rubusch
2025-05-10 22:44 ` [PATCH v8 6/7] iio: accel: adxl345: add freefall feature Lothar Rubusch
2025-05-10 22:44 ` [PATCH v8 7/7] docs: iio: add documentation for adxl345 driver Lothar Rubusch
6 siblings, 0 replies; 13+ messages in thread
From: Lothar Rubusch @ 2025-05-10 22:44 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add coupling activity/inactivity detection by setting the AC/DC bit. This
is an additional configuration to the activity and inactivity magnitude
events, respectively. DC-coupled event generation takes the configured
threshold values directly, where AC-coupled event generation references to
the acceleration value at the start of the activity detection. New samples
of acceleration are then compared to this reference.
Both types are implemented using MAG for DC-coupled activity/inactivity,
but MAG_REFERENCED for AC-coupled activity inactivity events. Threshold and
periods are offerend by different sysfs handles, but share the same
registers at the sensor. Thus activity and inactivity, respectively, cannot
be configured with AC- and DC-coupling at the same time, e.g. configuring
DC-coupled and AC-coupled activity will result in AC-coupled activity, or
generally the most recent one being configured.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 362 +++++++++++++++++++++++++++++--
1 file changed, 346 insertions(+), 16 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index ba68f304035a..c6f75d6b0db9 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)
@@ -52,6 +54,9 @@
#define ADXL345_ACT_Y_EN BIT(5)
#define ADXL345_ACT_X_EN BIT(6)
+#define ADXL345_ACT_INACT_DC 0
+#define ADXL345_ACT_INACT_AC 1
+
/* single/double tap */
enum adxl345_tap_type {
ADXL345_SINGLE_TAP,
@@ -79,16 +84,29 @@ static const unsigned int adxl345_tap_time_reg[] = {
enum adxl345_activity_type {
ADXL345_ACTIVITY,
ADXL345_INACTIVITY,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
};
static const unsigned int adxl345_act_int_reg[] = {
[ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
[ADXL345_INACTIVITY] = ADXL345_INT_INACTIVITY,
+ [ADXL345_ACTIVITY_AC] = ADXL345_INT_ACTIVITY,
+ [ADXL345_INACTIVITY_AC] = ADXL345_INT_INACTIVITY,
};
static const unsigned int adxl345_act_thresh_reg[] = {
[ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
[ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
+ [ADXL345_ACTIVITY_AC] = ADXL345_REG_THRESH_ACT,
+ [ADXL345_INACTIVITY_AC] = 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,
+ [ADXL345_ACTIVITY_AC] = ADXL345_REG_ACT_ACDC_MSK,
+ [ADXL345_INACTIVITY_AC] = ADXL345_REG_INACT_ACDC_MSK,
};
enum adxl345_odr {
@@ -156,6 +174,14 @@ static const int adxl345_fullres_range_tbl[][2] = {
[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;
@@ -179,6 +205,13 @@ static struct iio_event_spec adxl345_events[] = {
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
},
+ {
+ /* activity, ac bit set */
+ .type = IIO_EV_TYPE_MAG_REFERENCED,
+ .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,
@@ -232,6 +265,14 @@ static const struct iio_event_spec adxl345_fake_chan_events[] = {
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_PERIOD),
},
+ {
+ /* inactivity, AC bit set */
+ .type = IIO_EV_TYPE_MAG_REFERENCED,
+ .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[] = {
@@ -295,19 +336,119 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
/* act/inact */
+/**
+ * adxl345_is_act_inact_ac() - Verify if AC or DC coupling is currently enabled.
+ *
+ * @st: The device data.
+ * @type: The activity or inactivity type.
+ * @en: Carries the result if this activity type is enabled.
+ *
+ * Given a type of activity / inactivity combined with either AC coupling set or
+ * default to DC, this function verifies if the combination is currently
+ * configured, hence enabled or not.
+ *
+ * Return: 0 if successful, else error value.
+ */
+static int adxl345_is_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type, bool *en)
+{
+ unsigned int regval;
+ bool ac;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
+ if (ret)
+ return ret;
+
+ if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC)
+ ac = FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval);
+ else
+ ac = FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval);
+
+ if (type == ADXL345_ACTIVITY || type == ADXL345_INACTIVITY)
+ *en = (ac == ADXL345_ACT_INACT_DC);
+ else
+ *en = (ac == ADXL345_ACT_INACT_AC);
+
+ return 0;
+}
+
+/**
+ * adxl345_set_act_inact_ac() - Configure AC coupling or DC coupling.
+ *
+ * @st: The device data.
+ * @type: Provide a type of activity or inactivity.
+ *
+ * Enables AC coupling or DC coupling depending on the provided type argument.
+ * Note: Activity and inactivity can be either AC coupled or DC coupled not
+ * both at the same time.
+ *
+ * Return: 0 if successful, else error value.
+ */
+static int adxl345_set_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type)
+{
+ unsigned int act_inact_ac;
+
+ if (type == ADXL345_ACTIVITY_AC || type == ADXL345_INACTIVITY_AC)
+ act_inact_ac = 0xff;
+ else
+ act_inact_ac = 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)
{
unsigned int regval;
u32 axis_ctrl;
+ bool coupling_en;
int ret;
ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl);
if (ret)
return ret;
- if (type == ADXL345_ACTIVITY) {
+ if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) {
switch (axis) {
case IIO_MOD_X:
*en = FIELD_GET(ADXL345_ACT_X_EN, axis_ctrl);
@@ -345,6 +486,12 @@ static int adxl345_is_act_inact_en(struct adxl345_state *st,
return ret;
*en = adxl345_act_int_reg[type] & regval;
+
+ ret = adxl345_is_act_inact_ac(st, type, &coupling_en);
+ if (ret)
+ return ret;
+
+ *en = adxl345_act_int_reg[type] && coupling_en;
}
return 0;
@@ -361,7 +508,7 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
u32 axis_ctrl = 0;
int ret;
- if (type == ADXL345_ACTIVITY) {
+ if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) {
switch (axis) {
case IIO_MOD_X:
axis_ctrl = ADXL345_ACT_X_EN;
@@ -395,7 +542,7 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
en = false;
- if (type == ADXL345_ACTIVITY) {
+ if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) {
en = FIELD_GET(ADXL345_REG_ACT_AXIS_MSK, axis_ctrl) &&
threshold;
} else {
@@ -413,6 +560,10 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
if (ret)
return ret;
+ ret = adxl345_set_act_inact_ac(st, type);
+ 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)
@@ -692,9 +843,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,
@@ -715,9 +873,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,
@@ -879,6 +1079,25 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG_REFERENCED:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = adxl345_is_act_inact_en(st, chan->channel2,
+ ADXL345_ACTIVITY_AC,
+ &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ case IIO_EV_DIR_FALLING:
+ ret = adxl345_is_act_inact_en(st, chan->channel2,
+ ADXL345_INACTIVITY_AC,
+ &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ default:
+ return -EINVAL;
+ }
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -923,6 +1142,19 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG_REFERENCED:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl345_set_act_inact_en(st, chan->channel2,
+ ADXL345_ACTIVITY_AC,
+ state);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_act_inact_en(st, chan->channel2,
+ ADXL345_INACTIVITY_AC,
+ state);
+ default:
+ return -EINVAL;
+ }
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -987,6 +1219,42 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG_REFERENCED:
+ 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_AC],
+ &act_threshold);
+ if (ret)
+ return ret;
+
+ *val = act_threshold;
+ return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY_AC],
+ &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;
+ }
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -1068,6 +1336,37 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
return -EINVAL;
}
break;
+ case IIO_EV_TYPE_MAG_REFERENCED:
+ 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_AC],
+ val);
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY_AC],
+ 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;
+ }
+ break;
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -1315,6 +1614,7 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
{
s64 ts = iio_get_time_ns(indio_dev);
struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int regval;
int samples;
int ret = -ENOENT;
@@ -1339,22 +1639,52 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
}
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_MAG,
- IIO_EV_DIR_RISING),
- ts);
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir,
+ IIO_EV_TYPE_MAG_REFERENCED,
+ IIO_EV_DIR_RISING),
+ ts);
+
+ } else {
+ /* DC coupled, relying on THRESH */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+ }
if (ret)
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_MAG,
- IIO_EV_DIR_FALLING),
- ts);
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG_REFERENCED,
+ IIO_EV_DIR_FALLING),
+ ts);
+ } else {
+ /* DC coupled, relying on THRESH */
+ 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;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v8 6/7] iio: accel: adxl345: add freefall feature
2025-05-10 22:43 [PATCH v8 0/7] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (4 preceding siblings ...)
2025-05-10 22:44 ` [PATCH v8 5/7] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
@ 2025-05-10 22:44 ` Lothar Rubusch
2025-05-25 17:19 ` Jonathan Cameron
2025-05-10 22:44 ` [PATCH v8 7/7] docs: iio: add documentation for adxl345 driver Lothar Rubusch
6 siblings, 1 reply; 13+ messages in thread
From: Lothar Rubusch @ 2025-05-10 22:44 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 magnitude 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.
The generated IIO event is a magnitude event on x&y&z and thus identical
to what inactivity (with DC-coupling default) would generate. Thus a
separate set of sysfs handles are setup to configure and enable freefall
events.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 226 +++++++++++++++++++++++++++++++
1 file changed, 226 insertions(+)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index c6f75d6b0db9..c35a5727852c 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -193,6 +193,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);
};
@@ -825,6 +826,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);
}
+/* free-fall */
+
+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_find_odr(struct adxl345_state *st, int val,
int val2, enum adxl345_odr *odr)
{
@@ -1689,6 +1747,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)
@@ -1763,7 +1832,156 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
return IRQ_HANDLED;
}
+/* free-fall sysfs */
+
+static ssize_t in_accel_mag_freefall_en_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl345_state *st = iio_priv(indio_dev);
+ bool en;
+ int val, ret;
+
+ ret = adxl345_is_ff_en(st, &en);
+ if (ret)
+ return ret;
+
+ val = en ? 1 : 0;
+
+ return iio_format_value(buf, IIO_VAL_INT, 1, &val);
+}
+
+static ssize_t in_accel_mag_freefall_en_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int val, ret;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_ff_en(st, val > 0);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_measure_en(st, true);
+ if (ret)
+ return ret;
+
+ return len;
+}
+static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_en, 0);
+
+static ssize_t in_accel_mag_freefall_value_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF, &val);
+ if (ret)
+ return ret;
+
+ return iio_format_value(buf, IIO_VAL_INT, 1, &val);
+}
+
+static ssize_t in_accel_mag_freefall_value_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int val, ret;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, val);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_measure_en(st, true);
+ if (ret)
+ return ret;
+
+ return len;
+}
+static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_value, 0);
+
+static ssize_t in_accel_mag_freefall_period_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int vals[2];
+
+ vals[0] = st->ff_time_ms;
+ vals[1] = 1000;
+
+ return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals);
+}
+
+static ssize_t in_accel_mag_freefall_period_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int val_int, val_fract_us, ret;
+
+ ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_ff_time(st, val_int, val_fract_us);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_measure_en(st, true);
+ if (ret)
+ return ret;
+
+ return len;
+}
+static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_period, 0);
+
+static struct attribute *adxl345_event_attrs[] = {
+ &iio_dev_attr_in_accel_mag_freefall_en.dev_attr.attr,
+ &iio_dev_attr_in_accel_mag_freefall_value.dev_attr.attr,
+ &iio_dev_attr_in_accel_mag_freefall_period.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group adxl345_event_attrs_group = {
+ .attrs = adxl345_event_attrs,
+};
+
static const struct iio_info adxl345_info = {
+ .event_attrs = &adxl345_event_attrs_group,
.read_raw = adxl345_read_raw,
.write_raw = adxl345_write_raw,
.read_avail = adxl345_read_avail,
@@ -1806,6 +2024,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));
@@ -1825,6 +2044,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;
@@ -1936,6 +2158,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] 13+ messages in thread
* Re: [PATCH v8 6/7] iio: accel: adxl345: add freefall feature
2025-05-10 22:44 ` [PATCH v8 6/7] iio: accel: adxl345: add freefall feature Lothar Rubusch
@ 2025-05-25 17:19 ` Jonathan Cameron
2025-05-27 7:31 ` Lothar Rubusch
0 siblings, 1 reply; 13+ messages in thread
From: Jonathan Cameron @ 2025-05-25 17:19 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Sat, 10 May 2025 22:44:04 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Add the freefall detection of the sensor together with a threshold and
> time parameter. A freefall magnitude 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.
>
> The generated IIO event is a magnitude event on x&y&z and thus identical
> to what inactivity (with DC-coupling default) would generate. Thus a
> separate set of sysfs handles are setup to configure and enable freefall
> events.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Hi Lothar,
Up until here all looked good to me but the custom ABI in here
would need documenting in Documentation/ABI/testing/sysfs-bus-iio* and
runs into the normal problem with custom ABI. It's basically pointless
because no generic code will use it. Current code assume freefall
is a standard magnitude falling threshold on X&Y&Z.
So it doesn't provide a solution for inactivity DC and freefall effectively
being the same type of detection with different timing parameters.
The only thing I have come up with that is at least close to standard
ABI in this annoying corner case is to use an additional fake channel
with it's own controls as in_accel1_X&Y&Z_*
We can use label to indicate to a user that it is meant for freefall but
labels are also effectively custom ABI (though ABI that is easy to just
pass through to a user interface) so that's not great. Also, so far
the concept of multiple 'fake' channels isn't a thing so userspace is
unlikely to cope with that well either.
The decision way back to not have multiple events of one type on a given
channel has bitten us a few times before, but this is a little unusual
in that it's not just stacked threshold like you get on hwmon chips where
people don't want to keep changing thresholds when warning levels are
passed because of that temporal element. Those can be dealt with via
more sophisticated userspace code.
No comments below. I haven't looked closely given this fundamental
ABI difference.
Maybe best bet is mere everything other than freefall detection and
basically kick this into the long grass until we figure out a solution that
can sit in the normal ABI without breaking old software (which a change
to a new free fall interface would unfortunately do.)
Sorry not to have a better idea :(
Jonathan
> ---
> drivers/iio/accel/adxl345_core.c | 226 +++++++++++++++++++++++++++++++
> 1 file changed, 226 insertions(+)
>
> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> index c6f75d6b0db9..c35a5727852c 100644
> --- a/drivers/iio/accel/adxl345_core.c
> +++ b/drivers/iio/accel/adxl345_core.c
> @@ -193,6 +193,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);
> };
> @@ -825,6 +826,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);
> }
>
> +/* free-fall */
> +
> +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_find_odr(struct adxl345_state *st, int val,
> int val2, enum adxl345_odr *odr)
> {
> @@ -1689,6 +1747,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)
> @@ -1763,7 +1832,156 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
> return IRQ_HANDLED;
> }
>
> +/* free-fall sysfs */
> +
> +static ssize_t in_accel_mag_freefall_en_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl345_state *st = iio_priv(indio_dev);
> + bool en;
> + int val, ret;
> +
> + ret = adxl345_is_ff_en(st, &en);
> + if (ret)
> + return ret;
> +
> + val = en ? 1 : 0;
> +
> + return iio_format_value(buf, IIO_VAL_INT, 1, &val);
> +}
> +
> +static ssize_t in_accel_mag_freefall_en_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl345_state *st = iio_priv(indio_dev);
> + int val, ret;
> +
> + ret = kstrtoint(buf, 0, &val);
> + if (ret)
> + return ret;
> +
> + ret = adxl345_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = adxl345_set_ff_en(st, val > 0);
> + if (ret)
> + return ret;
> +
> + ret = adxl345_set_measure_en(st, true);
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_en, 0);
> +
> +static ssize_t in_accel_mag_freefall_value_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl345_state *st = iio_priv(indio_dev);
> + unsigned int val;
> + int ret;
> +
> + ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF, &val);
> + if (ret)
> + return ret;
> +
> + return iio_format_value(buf, IIO_VAL_INT, 1, &val);
> +}
> +
> +static ssize_t in_accel_mag_freefall_value_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl345_state *st = iio_priv(indio_dev);
> + int val, ret;
> +
> + ret = kstrtoint(buf, 0, &val);
> + if (ret)
> + return ret;
> +
> + if (val < 0 || val > 255)
> + return -EINVAL;
> +
> + ret = adxl345_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, val);
> + if (ret)
> + return ret;
> +
> + ret = adxl345_set_measure_en(st, true);
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_value, 0);
> +
> +static ssize_t in_accel_mag_freefall_period_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl345_state *st = iio_priv(indio_dev);
> + int vals[2];
> +
> + vals[0] = st->ff_time_ms;
> + vals[1] = 1000;
> +
> + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals);
> +}
> +
> +static ssize_t in_accel_mag_freefall_period_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl345_state *st = iio_priv(indio_dev);
> + int val_int, val_fract_us, ret;
> +
> + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us);
> + if (ret)
> + return ret;
> +
> + ret = adxl345_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = adxl345_set_ff_time(st, val_int, val_fract_us);
> + if (ret)
> + return ret;
> +
> + ret = adxl345_set_measure_en(st, true);
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_period, 0);
> +
> +static struct attribute *adxl345_event_attrs[] = {
> + &iio_dev_attr_in_accel_mag_freefall_en.dev_attr.attr,
> + &iio_dev_attr_in_accel_mag_freefall_value.dev_attr.attr,
> + &iio_dev_attr_in_accel_mag_freefall_period.dev_attr.attr,
> + NULL
> +};
> +
> +static const struct attribute_group adxl345_event_attrs_group = {
> + .attrs = adxl345_event_attrs,
> +};
> +
> static const struct iio_info adxl345_info = {
> + .event_attrs = &adxl345_event_attrs_group,
> .read_raw = adxl345_read_raw,
> .write_raw = adxl345_write_raw,
> .read_avail = adxl345_read_avail,
> @@ -1806,6 +2024,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));
> @@ -1825,6 +2044,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;
> @@ -1936,6 +2158,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 [flat|nested] 13+ messages in thread
* Re: [PATCH v8 6/7] iio: accel: adxl345: add freefall feature
2025-05-25 17:19 ` Jonathan Cameron
@ 2025-05-27 7:31 ` Lothar Rubusch
2025-05-31 15:39 ` Jonathan Cameron
0 siblings, 1 reply; 13+ messages in thread
From: Lothar Rubusch @ 2025-05-27 7:31 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Sun, May 25, 2025 at 7:19 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Sat, 10 May 2025 22:44:04 +0000
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > Add the freefall detection of the sensor together with a threshold and
> > time parameter. A freefall magnitude 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.
> >
> > The generated IIO event is a magnitude event on x&y&z and thus identical
> > to what inactivity (with DC-coupling default) would generate. Thus a
> > separate set of sysfs handles are setup to configure and enable freefall
> > events.
> >
> > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> Hi Lothar,
>
> Up until here all looked good to me but the custom ABI in here
> would need documenting in Documentation/ABI/testing/sysfs-bus-iio* and
> runs into the normal problem with custom ABI. It's basically pointless
> because no generic code will use it. Current code assume freefall
> is a standard magnitude falling threshold on X&Y&Z.
>
> So it doesn't provide a solution for inactivity DC and freefall effectively
> being the same type of detection with different timing parameters.
>
> The only thing I have come up with that is at least close to standard
> ABI in this annoying corner case is to use an additional fake channel
> with it's own controls as in_accel1_X&Y&Z_*
> We can use label to indicate to a user that it is meant for freefall but
> labels are also effectively custom ABI (though ABI that is easy to just
> pass through to a user interface) so that's not great. Also, so far
> the concept of multiple 'fake' channels isn't a thing so userspace is
> unlikely to cope with that well either.
>
> The decision way back to not have multiple events of one type on a given
> channel has bitten us a few times before, but this is a little unusual
> in that it's not just stacked threshold like you get on hwmon chips where
> people don't want to keep changing thresholds when warning levels are
> passed because of that temporal element. Those can be dealt with via
> more sophisticated userspace code.
>
> No comments below. I haven't looked closely given this fundamental
> ABI difference.
>
> Maybe best bet is mere everything other than freefall detection and
> basically kick this into the long grass until we figure out a solution that
> can sit in the normal ABI without breaking old software (which a change
> to a new free fall interface would unfortunately do.)
>
> Sorry not to have a better idea :(
>
No problem, this answer was already expected. I will exclude freefall
from the current patch set.
Note, at the moment you still did not go through activity, inactivity
and link bit. Pls, don't apply them now. While working on the adxl313,
I found that the ADXL345 implementation still needs a small fix.
Aside, I'd like to use my new regmap favorite regmap_assign_bits()
here, too. Anyway I'd appreciate feedback on the current
activity/inactivity patches, though.
The "freefall inactivity dilemma" is a bit annoying for actually two
reasons. First, it does not feel well to surrender in front of two
simple events as such. Second, it's not just about dropping freefall,
but also leaves another question aside. Activity/inactivity are now
linked when both are enabled - shouldn't there be a way to allow for
activity / inactivity un-linked? I mean, just as events and not
coupled to auto-sleep? Actually this feels a bit like
MAG_EV_REFERENCED having MAG_EV_STATECHANGE or INFO_STATECHANGE. And
this smells probably too much like custom API... Just kidding, but I
just wanted to mention it, and like to shift this question also to a
separate patch set.
Some freefall idea: Practically given, a freefall is an inactivity MAG
event with low inactivity time, and inactivity is an inactivity MAG
event with larger inactivity time.
After reasoning a bit more, I can think of some exclusive
implementation based on the threshold times [this is nothing new, I
think you mentioned something into this direction already?]. Unsure if
this is possible, I may try this here and in case present it
separately: Say, if inactivity time obtained by sysfs is greater than
x seconds, a freefall event will be set up. If smaller, an inactivity
event will be set up. If freefall is enabled (and, in case, activity),
both will remain unlinked. If inactivity and activity are enabled,
i.e. the larger inactivity time setting - both events will be linked
with autosleep.
Best,
L
> Jonathan
>
>
> > ---
> > drivers/iio/accel/adxl345_core.c | 226 +++++++++++++++++++++++++++++++
> > 1 file changed, 226 insertions(+)
> >
> > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > index c6f75d6b0db9..c35a5727852c 100644
> > --- a/drivers/iio/accel/adxl345_core.c
> > +++ b/drivers/iio/accel/adxl345_core.c
> > @@ -193,6 +193,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);
> > };
> > @@ -825,6 +826,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);
> > }
> >
> > +/* free-fall */
> > +
> > +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_find_odr(struct adxl345_state *st, int val,
> > int val2, enum adxl345_odr *odr)
> > {
> > @@ -1689,6 +1747,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)
> > @@ -1763,7 +1832,156 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
> > return IRQ_HANDLED;
> > }
> >
> > +/* free-fall sysfs */
> > +
> > +static ssize_t in_accel_mag_freefall_en_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl345_state *st = iio_priv(indio_dev);
> > + bool en;
> > + int val, ret;
> > +
> > + ret = adxl345_is_ff_en(st, &en);
> > + if (ret)
> > + return ret;
> > +
> > + val = en ? 1 : 0;
> > +
> > + return iio_format_value(buf, IIO_VAL_INT, 1, &val);
> > +}
> > +
> > +static ssize_t in_accel_mag_freefall_en_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl345_state *st = iio_priv(indio_dev);
> > + int val, ret;
> > +
> > + ret = kstrtoint(buf, 0, &val);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl345_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl345_set_ff_en(st, val > 0);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl345_set_measure_en(st, true);
> > + if (ret)
> > + return ret;
> > +
> > + return len;
> > +}
> > +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_en, 0);
> > +
> > +static ssize_t in_accel_mag_freefall_value_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl345_state *st = iio_priv(indio_dev);
> > + unsigned int val;
> > + int ret;
> > +
> > + ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF, &val);
> > + if (ret)
> > + return ret;
> > +
> > + return iio_format_value(buf, IIO_VAL_INT, 1, &val);
> > +}
> > +
> > +static ssize_t in_accel_mag_freefall_value_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl345_state *st = iio_priv(indio_dev);
> > + int val, ret;
> > +
> > + ret = kstrtoint(buf, 0, &val);
> > + if (ret)
> > + return ret;
> > +
> > + if (val < 0 || val > 255)
> > + return -EINVAL;
> > +
> > + ret = adxl345_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, val);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl345_set_measure_en(st, true);
> > + if (ret)
> > + return ret;
> > +
> > + return len;
> > +}
> > +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_value, 0);
> > +
> > +static ssize_t in_accel_mag_freefall_period_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl345_state *st = iio_priv(indio_dev);
> > + int vals[2];
> > +
> > + vals[0] = st->ff_time_ms;
> > + vals[1] = 1000;
> > +
> > + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals);
> > +}
> > +
> > +static ssize_t in_accel_mag_freefall_period_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl345_state *st = iio_priv(indio_dev);
> > + int val_int, val_fract_us, ret;
> > +
> > + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl345_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl345_set_ff_time(st, val_int, val_fract_us);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl345_set_measure_en(st, true);
> > + if (ret)
> > + return ret;
> > +
> > + return len;
> > +}
> > +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_period, 0);
> > +
> > +static struct attribute *adxl345_event_attrs[] = {
> > + &iio_dev_attr_in_accel_mag_freefall_en.dev_attr.attr,
> > + &iio_dev_attr_in_accel_mag_freefall_value.dev_attr.attr,
> > + &iio_dev_attr_in_accel_mag_freefall_period.dev_attr.attr,
> > + NULL
> > +};
> > +
> > +static const struct attribute_group adxl345_event_attrs_group = {
> > + .attrs = adxl345_event_attrs,
> > +};
> > +
> > static const struct iio_info adxl345_info = {
> > + .event_attrs = &adxl345_event_attrs_group,
> > .read_raw = adxl345_read_raw,
> > .write_raw = adxl345_write_raw,
> > .read_avail = adxl345_read_avail,
> > @@ -1806,6 +2024,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));
> > @@ -1825,6 +2044,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;
> > @@ -1936,6 +2158,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 [flat|nested] 13+ messages in thread
* Re: [PATCH v8 6/7] iio: accel: adxl345: add freefall feature
2025-05-27 7:31 ` Lothar Rubusch
@ 2025-05-31 15:39 ` Jonathan Cameron
0 siblings, 0 replies; 13+ messages in thread
From: Jonathan Cameron @ 2025-05-31 15:39 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Tue, 27 May 2025 09:31:57 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> On Sun, May 25, 2025 at 7:19 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Sat, 10 May 2025 22:44:04 +0000
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > Add the freefall detection of the sensor together with a threshold and
> > > time parameter. A freefall magnitude 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.
> > >
> > > The generated IIO event is a magnitude event on x&y&z and thus identical
> > > to what inactivity (with DC-coupling default) would generate. Thus a
> > > separate set of sysfs handles are setup to configure and enable freefall
> > > events.
> > >
> > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > Hi Lothar,
> >
> > Up until here all looked good to me but the custom ABI in here
> > would need documenting in Documentation/ABI/testing/sysfs-bus-iio* and
> > runs into the normal problem with custom ABI. It's basically pointless
> > because no generic code will use it. Current code assume freefall
> > is a standard magnitude falling threshold on X&Y&Z.
> >
> > So it doesn't provide a solution for inactivity DC and freefall effectively
> > being the same type of detection with different timing parameters.
> >
> > The only thing I have come up with that is at least close to standard
> > ABI in this annoying corner case is to use an additional fake channel
> > with it's own controls as in_accel1_X&Y&Z_*
> > We can use label to indicate to a user that it is meant for freefall but
> > labels are also effectively custom ABI (though ABI that is easy to just
> > pass through to a user interface) so that's not great. Also, so far
> > the concept of multiple 'fake' channels isn't a thing so userspace is
> > unlikely to cope with that well either.
> >
> > The decision way back to not have multiple events of one type on a given
> > channel has bitten us a few times before, but this is a little unusual
> > in that it's not just stacked threshold like you get on hwmon chips where
> > people don't want to keep changing thresholds when warning levels are
> > passed because of that temporal element. Those can be dealt with via
> > more sophisticated userspace code.
> >
> > No comments below. I haven't looked closely given this fundamental
> > ABI difference.
> >
> > Maybe best bet is mere everything other than freefall detection and
> > basically kick this into the long grass until we figure out a solution that
> > can sit in the normal ABI without breaking old software (which a change
> > to a new free fall interface would unfortunately do.)
> >
> > Sorry not to have a better idea :(
> >
>
> No problem, this answer was already expected. I will exclude freefall
> from the current patch set.
>
> Note, at the moment you still did not go through activity, inactivity
> and link bit. Pls, don't apply them now. While working on the adxl313,
> I found that the ADXL345 implementation still needs a small fix.
> Aside, I'd like to use my new regmap favorite regmap_assign_bits()
> here, too. Anyway I'd appreciate feedback on the current
> activity/inactivity patches, though.
I did look through them but had nothing useful to say as they looked
good to me. I'll probably take a final look when you post the version
with the changes you mention though.
>
> The "freefall inactivity dilemma" is a bit annoying for actually two
> reasons. First, it does not feel well to surrender in front of two
> simple events as such. Second, it's not just about dropping freefall,
> but also leaves another question aside. Activity/inactivity are now
> linked when both are enabled - shouldn't there be a way to allow for
> activity / inactivity un-linked? I mean, just as events and not
> coupled to auto-sleep? Actually this feels a bit like
> MAG_EV_REFERENCED having MAG_EV_STATECHANGE or INFO_STATECHANGE. And
> this smells probably too much like custom API... Just kidding, but I
> just wanted to mention it, and like to shift this question also to a
> separate patch set.
(As you know) custom ABI is a pain for this and the only thing we have
that is close is hysteresis. If they were both on the same channel
boolean operation (say both were x&y&z) then we could just use that
with the hysteresis value being the difference between the two thresholds.
With one being x&y&z and the other x|y|z we can't really do this.
It might be possible to define a coupling ABI / chaining of events
style thing that would allow us to define this cleanly but it is likely
that would get complex to understand and we'd need to fully understand
the use cases across a range of devices to try and figure out the commonality.
The autosleep is kind of separate again from the link. That falls into
the common problem area of power saving functionality. There is a silly level
of complexity in what different devices do in the way of power saving
tricks so we either map them to something else (e.g. whatever
mode suits the sampling frequency) or try to make best attempt to auto
adjust them in the driver. Maybe here we should disable autosleep
if buffered mode is in use (on basis user is asking for consistent
data) and otherwise enable it because lower sampling frequency is
probably fine for monitoring. Ultimately we do have the option of
per event sampling frequencies in_accel_x&y&z_mag_rising_sampling_frequency
that could be used to control the frequency at which we check for
activity to be either the sleep mode mode or the normal one.
That's not a common bit of ABI but is there for the case of
devices that have a monitor mode but no dataready type signal
so the frequency is only relevant for events.
Note that the only documented version of that bit of the ABI today
is the one that applies to all events.
/sys/bus/iio/devices/iio:deviceX/events/sampling_frequency
I'd not be against adding per event versions as they are consistent
with the existing ABI. The quirk around what they mean are a bit
non-obvious as we define per channel sampling_frequency differently
from sampling_frequency when it comes to effective overall rates.
How that applies to events will need specific documentation.
See docs for /sys/bus/iio/devices/iio:deviceX/in_voltageY_sampling_frequency
for the info on that wrinkle of the existing ABI.
That's not really a problem if we only define one of these though
as no periods to add :)
>
> Some freefall idea: Practically given, a freefall is an inactivity MAG
> event with low inactivity time, and inactivity is an inactivity MAG
> event with larger inactivity time.
For this device yes that's the case for DC coupling anyway. Worth
keeping in mind that other sensors use entirely different things
for 'activity' detection.
> After reasoning a bit more, I can think of some exclusive
> implementation based on the threshold times [this is nothing new, I
> think you mentioned something into this direction already?]. Unsure if
> this is possible, I may try this here and in case present it
> separately: Say, if inactivity time obtained by sysfs is greater than
> x seconds, a freefall event will be set up. If smaller, an inactivity
> event will be set up. If freefall is enabled (and, in case, activity),
> both will remain unlinked. If inactivity and activity are enabled,
> i.e. the larger inactivity time setting - both events will be linked
> with autosleep.
That could definitely be done and I think makes reasonable sense.
If someone wants freefall detection they are unlikely to set a long
period (as the device will hit the ground!)
The link thing and autosleep is kind of us trying to do the right
thing for what we guess the user wants so that is fine too.
Jonathan
>
> Best,
> L
>
> > Jonathan
> >
> >
> > > ---
> > > drivers/iio/accel/adxl345_core.c | 226 +++++++++++++++++++++++++++++++
> > > 1 file changed, 226 insertions(+)
> > >
> > > diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> > > index c6f75d6b0db9..c35a5727852c 100644
> > > --- a/drivers/iio/accel/adxl345_core.c
> > > +++ b/drivers/iio/accel/adxl345_core.c
> > > @@ -193,6 +193,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);
> > > };
> > > @@ -825,6 +826,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);
> > > }
> > >
> > > +/* free-fall */
> > > +
> > > +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_find_odr(struct adxl345_state *st, int val,
> > > int val2, enum adxl345_odr *odr)
> > > {
> > > @@ -1689,6 +1747,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)
> > > @@ -1763,7 +1832,156 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
> > > return IRQ_HANDLED;
> > > }
> > >
> > > +/* free-fall sysfs */
> > > +
> > > +static ssize_t in_accel_mag_freefall_en_show(struct device *dev,
> > > + struct device_attribute *attr,
> > > + char *buf)
> > > +{
> > > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > + struct adxl345_state *st = iio_priv(indio_dev);
> > > + bool en;
> > > + int val, ret;
> > > +
> > > + ret = adxl345_is_ff_en(st, &en);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + val = en ? 1 : 0;
> > > +
> > > + return iio_format_value(buf, IIO_VAL_INT, 1, &val);
> > > +}
> > > +
> > > +static ssize_t in_accel_mag_freefall_en_store(struct device *dev,
> > > + struct device_attribute *attr,
> > > + const char *buf, size_t len)
> > > +{
> > > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > + struct adxl345_state *st = iio_priv(indio_dev);
> > > + int val, ret;
> > > +
> > > + ret = kstrtoint(buf, 0, &val);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adxl345_set_measure_en(st, false);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adxl345_set_ff_en(st, val > 0);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adxl345_set_measure_en(st, true);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return len;
> > > +}
> > > +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_en, 0);
> > > +
> > > +static ssize_t in_accel_mag_freefall_value_show(struct device *dev,
> > > + struct device_attribute *attr,
> > > + char *buf)
> > > +{
> > > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > + struct adxl345_state *st = iio_priv(indio_dev);
> > > + unsigned int val;
> > > + int ret;
> > > +
> > > + ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF, &val);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return iio_format_value(buf, IIO_VAL_INT, 1, &val);
> > > +}
> > > +
> > > +static ssize_t in_accel_mag_freefall_value_store(struct device *dev,
> > > + struct device_attribute *attr,
> > > + const char *buf, size_t len)
> > > +{
> > > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > + struct adxl345_state *st = iio_priv(indio_dev);
> > > + int val, ret;
> > > +
> > > + ret = kstrtoint(buf, 0, &val);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + if (val < 0 || val > 255)
> > > + return -EINVAL;
> > > +
> > > + ret = adxl345_set_measure_en(st, false);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, val);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adxl345_set_measure_en(st, true);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return len;
> > > +}
> > > +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_value, 0);
> > > +
> > > +static ssize_t in_accel_mag_freefall_period_show(struct device *dev,
> > > + struct device_attribute *attr,
> > > + char *buf)
> > > +{
> > > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > + struct adxl345_state *st = iio_priv(indio_dev);
> > > + int vals[2];
> > > +
> > > + vals[0] = st->ff_time_ms;
> > > + vals[1] = 1000;
> > > +
> > > + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals);
> > > +}
> > > +
> > > +static ssize_t in_accel_mag_freefall_period_store(struct device *dev,
> > > + struct device_attribute *attr,
> > > + const char *buf, size_t len)
> > > +{
> > > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > + struct adxl345_state *st = iio_priv(indio_dev);
> > > + int val_int, val_fract_us, ret;
> > > +
> > > + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adxl345_set_measure_en(st, false);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adxl345_set_ff_time(st, val_int, val_fract_us);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adxl345_set_measure_en(st, true);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return len;
> > > +}
> > > +static IIO_DEVICE_ATTR_RW(in_accel_mag_freefall_period, 0);
> > > +
> > > +static struct attribute *adxl345_event_attrs[] = {
> > > + &iio_dev_attr_in_accel_mag_freefall_en.dev_attr.attr,
> > > + &iio_dev_attr_in_accel_mag_freefall_value.dev_attr.attr,
> > > + &iio_dev_attr_in_accel_mag_freefall_period.dev_attr.attr,
> > > + NULL
> > > +};
> > > +
> > > +static const struct attribute_group adxl345_event_attrs_group = {
> > > + .attrs = adxl345_event_attrs,
> > > +};
> > > +
> > > static const struct iio_info adxl345_info = {
> > > + .event_attrs = &adxl345_event_attrs_group,
> > > .read_raw = adxl345_read_raw,
> > > .write_raw = adxl345_write_raw,
> > > .read_avail = adxl345_read_avail,
> > > @@ -1806,6 +2024,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));
> > > @@ -1825,6 +2044,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;
> > > @@ -1936,6 +2158,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 [flat|nested] 13+ messages in thread
* [PATCH v8 7/7] docs: iio: add documentation for adxl345 driver
2025-05-10 22:43 [PATCH v8 0/7] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (5 preceding siblings ...)
2025-05-10 22:44 ` [PATCH v8 6/7] iio: accel: adxl345: add freefall feature Lothar Rubusch
@ 2025-05-10 22:44 ` Lothar Rubusch
6 siblings, 0 replies; 13+ messages in thread
From: Lothar Rubusch @ 2025-05-10 22:44 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 | 458 ++++++++++++++++++++++++++++++++++
1 file changed, 458 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..e21b783153a6
--- /dev/null
+++ b/Documentation/iio/adxl345.rst
@@ -0,0 +1,458 @@
+.. 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``.
+Note, the default activity/inactivity is DC coupled. Thus only AC coupled
+activity and inactivity are mentioned explicitly.
+
++---------------------------------------------+---------------------------------------------+
+| 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_mag_falling_period | Inactivity time in seconds |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_falling_value | Inactivity threshold value in 62.5/LSB |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_freefall_en | Enable free-fall detection on all axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_freefall_period | Free-fall detection time in us |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_freefall_value | Free-fall threshold value in 62.5/LSB |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_referenced_falling_period | AC coupled inactivity time in seconds |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_referenced_falling_value | AC coupled inactivity threshold in 62.5/LSB |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_referenced_rising_value | AC coupled activity threshold in 62.5/LSB |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_rising_value | Activity threshold value in 62.5/LSB |
++---------------------------------------------+---------------------------------------------+
+| in_accel_x_gesture_singletap_en | Enable single tap detection on X axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_x_mag_referenced_rising_en | Enable AC coupled activity on X axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_x_mag_rising_en | Enable activity detection on X axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_x&y&z_mag_falling_en | Enable inactivity detection on all axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_x&y&z_mag_referenced_falling_en | Enable AC coupled inactivity on all axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_y_gesture_singletap_en | Enable single tap detection on Y axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_y_mag_referenced_rising_en | Enable AC coupled activity on Y axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_y_mag_rising_en | Enable activity detection on Y axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_z_gesture_singletap_en | Enable single tap detection on Z axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_z_mag_referenced_rising_en | Enable AC coupled activity on Z axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_z_mag_rising_en | Enable activity 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.
+Where DC-coupled is the default case detecting against the configured threshold,
+AC-coupled measurements are referenced against an internal filter depending
+on the configured threshold.
+Activity detection can be enabled on particular axis. Inactivity detection on
+the other side, is enabled or disabled on all axis.
+Note, AC-coupling and DC-coupling are individually set for activity and/or
+inactivity detection, and cannot be set both at the same time. Enabling
+AC-coupled activity detection, and then DC-coupled activity detection will
+result in performing DC-coupled activity detection only. Hence, only the most
+recent configuration will valid.
+
+**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 is detected if the measurement signal on all axis goes
+below the configured threshold for a configured period of time [us]. It is then
+reissued again and again. The generated magnitude event on the IIO channel is
+actually the same to the DC-coupled inactivity event. Since both can be
+considered as magnitude events on all axis (and'd). When using
+activity/inactivity detection and free-fall detection, it is probably the best
+approach o combine AC coupled activity/inactivity detection with free-fall.
+
+In situations with DC coupled activity/inactivity and free-fall enabled,
+activity/inactivity indicate the state change of the sensor with far higher
+periods [s], than the period taken for free-fall detection [us]. Probably the
+only way would be to verify the state change to inactive of the sensor in order
+to interprete a inactivity event rather than the typically more often happening
+free-fall event.
+
+Note, that activity/inactivy, as also free-fall is recommended for 12.5 Hz ODR
+up to 400 Hz. By the datasheet, free-fall threshold is recommended between 300mg
+and 600mg (0x05 to 0x09), and free-fall time is recommended to be set between
+100ms and 350ms (0x14 to 0x46).
+
+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
+
+ ## AC coupled activity, threshold [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 6 > ./events/in_accel_mag_referenced_rising_value
+
+ ## AC coupled inactivity, threshold, [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 4 > ./events/in_accel_mag_referenced_falling_value
+
+ ## AC coupled inactivity, time [s]
+ root:/sys/bus/iio/devices/iio:device0> echo 3 > ./events/in_accel_mag_referenced_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
+
+ ## free-fall, threshold [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 8 > ./events/in_accel_mag_freefall_value
+
+ ## free-fall, time [ms]
+ root:/sys/bus/iio/devices/iio:device0> echo 1.25 > ./events/in_accel_mag_freefall_period
+
+ ## activity, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x_mag_referenced_rising_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_y_mag_referenced_rising_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_z_mag_referenced_rising_en
+
+ ## inactivity, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x\&y\&z_mag_referenced_falling_en
+
+ ## free-fall, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_mag_freefall_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: mag, direction: rising
+ Event: time: 1739063415963770218, type: accel(z), channel: 0, evtype: mag, 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: mag, direction: falling
+ Event: time: 1739063436539080713, type: accel(x&y&z), channel: 0, evtype: mag, direction: falling
+ Event: time: 1739063438357970381, type: accel(z), channel: 0, evtype: mag, direction: rising
+ Event: time: 1739063446726161586, type: accel(z), channel: 0, evtype: mag, direction: rising
+ Event: time: 1739063446727892670, type: accel(z), channel: 0, evtype: mag, direction: rising
+ Event: time: 1739063446743019768, type: accel(z), channel: 0, evtype: mag, direction: rising
+ Event: time: 1739063446744650696, type: accel(z), channel: 0, evtype: mag, 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: mag, 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: mag, direction: rising
+ <after inactivity time elapsed>
+ Event: time: 1744648057724775499, type: accel(x&y&z), channel: 0, evtype: mag, 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] 13+ messages in thread