* [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events
@ 2025-07-02 23:03 Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 1/8] iio: accel: adxl345: simplify interrupt mapping Lothar Rubusch
` (7 more replies)
0 siblings, 8 replies; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, eraretuya, l.rubusch
Add several interrupt based sensor detection events:
- refactoring and fixes
- activity/inactivity linked and auto-sleep
- AC-coupled activity/inactivity
- Extend inactivity for inactivity under 1s (using free-fall register)
- documentation
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
v10 -> v11:
- [PATCH v10 1/7]: prefixed `_get_int_line()`
- [PATCH v10 3/7]: add `ADXL345_ACT_XYZ_EN` to reduce LoC occupation, in
cases of added features, the line formatting is actually kept the same as
in v10
- [PATCH v10 4/7]: add `ADXL345_INACT_XYZ_EN` to simplify and reduce LoC
ocupation
- [PATCH v10 5/7]: Break the variable declaration and assignment into two
distinct lines
- [PATCH v10 6/7]: `adxl345_set_inact_time()`: Extract conditional cases
into separate helper functions; Use a nested if/else to eliminate
redundant val_int == 0sec checks
- [PATCH v10 7/7]: Separate the documentation sections for free-fall
detection and inactivity detection into anothe patch
v9 -> v10:
- [PATCH v9 01/11]: Dropped
- [PATCH v9 02/11]: Applied
- [PATCH v9 03/11]: Applied
- [PATCH v9 05/11]: Replace literal number 2 by a `sizeof()` statement.
- [PATCH v9 06/11]: Applied
- [PATCH v9 01/11]: Tap scaling patch is kept unmodified, since the
threshold has been in raw register values, the patch scales that to
g/LSB. There was no other advice (so far/to my understanding) to drop or
modify this patch
- all (remaing) patches: Rephrased commit messages
- `adxl345_is_act_inact_en()`: Refactor the function; Check for a valid
threshold earlier based on the enable flag; incrementally construct the
switch/case conditions
- `adxl345_set_act_inact_en()`: Refactor the function; implement helper
functions; group checks at the beginning, return soon in case of `false`,
group `cmd_en` using calls to the end
- `adxl345_read/write_mag_config()`: Introduce the helper functions when
activity is added, and prefer using switch/case statements. However,
retain the internal nested switch/case structure, as it appears more
appropriate in the context of handling other channel values and
configurations
- `adxl345_read/write_mag_value()`: Introduce helpers when activity is
added (keep internal nested switch/case)
- `adxl345_core_probe()`: Rephrase comment on default values
- `adxl345_set_inact_time()`: Rephrase function description
- `adxl345_set_act_inact_linkbit()`: Factor link-bit and auto-sleep
handling in a separate function
- `adxl345_set_inact_time()`: Use `clamp()` and convert `min_boundary` and
`max_boundary` to int; in cases of `>max_boundary` stay with the clamp
behavior for inactive period
- `adxl345_set_odr()`: The return statement is kept as is, due to several
refacs it won't change so `return adxl345_set_inact_time(st, 0);` only
becomes `return adxl345_set_inact_time(st, 0, 0);` throughout the patch
series
- `adxl345_set_act_inact_linkbit()`: Apply `ADXL345_POWER_CTL_INACT_MSK`
directly
- `adxl345_is_act_inact_ac()`: Change to use switch/case for clarity
- `adxl345_set_act_inact_ac()`: Additionally accept `cmd_en` to turn
coupling on/off
- `adxl345_set_inact_time()`: Return from within the conditional cases as
of the last source patch [PATCH v9 10/11]
- documentation: rephrased major parts (ai based rephrasing)
v8 -> v9:
- githook: apply codespell checks, extend by spellcheck using ispell
- refac: apply `regmap_assing_bits()` in several places
- refac: remove ADXL345_POWER_CTL_STANDBY in adxl345.h not needed anymore
- refac: rename `ADXL345_ACT_INACT_DC/AC` to `ADXL345_COUPLING_DC/AC`
- refac: remove variable irq from `struct adxl345_state`
- refac: apply expressions, such as MILLI or MICRO from linux/units.h
- refac: apply (missing) scaling factor 62.5mg/LSB to tap detection
- change `IIO_EV_TYPE_MAG_REFERENCED` to `IIO_EV_TYPE_MAG_ADAPTIVE`
- `adxl345_fifo_transfer()`: eliminate variable for expression count / 2
- `adxl345_is_act_inact_ac()`: make return boolean, or negative error
- make activity enable cover x, y and z axis together, while signals come
on particular axis
- `adxl345_read_mag_value()`: separate `MAG` and `MAG_ADAPTIVE` event
value read and write function to reduce redundant code
- `adxl345_read_mag_config()`: separate `MAG` and `MAG_ADAPTIVE` event
config read and write functions to reduce redundant code
- `adxl345_set_act_inact_en()`: move linkbit detection out to
`adxl345_set_act_inact_linkbit()`
- `adxl345_set_act_inact_en()`: fix unsetting register INT ENABLE at
disabling feature(s)
- apply scaling factor 62.5mg/LSB to activity/inactivity
- apply scaling factor 62.5mg/LSB to activity AC/inactivity AC
- drop dedicated freefall patch
- add patch: fix missing scale factor for tap detection
- add patch: make irq a function local variable
- add patch: simplify measure enable
- add patch: simplify interrupt mapping
- add patch: simplify FIFO reading
- add patch: replace magic numbers by unit expressions
- rename `adxl345_set_inact_time_s()` to `adxl345_set_inact_time()`
- `adxl345_set_inact_time()`: implement inactivity using inactivity
register (period 1s or higher), freefall register (below 1s) or let it
setup a period based on given ODR (0s provided)
- doc: update documentation
v7 -> v8:
- activity/inactivity are MAG events
- separate AC coupled activity/inactivity events as MAG_REFERENCED events,
since AC coupling introduces a (some kind of) reference relation
- since freefall and inactivity (DC coupled) are then actually identical,
this results in a challenging situation for the freefall patch. Thus,
the freefall patch is moved to end of this series (before documentation)
- freefall: provide separate sysfs handles to configure and enable freefall
- documentation: update sections on activity/inactivity, freefall, event
names and examples
v6 -> v7:
- freefall: add a virtual channel, replace OR'ing the axis by AND'ing them
- inactivity: add a virtual channel, replace OR'ing the axis by AND'ing them
v5 -> v6:
- replace bool axis_en for tap and activity/inactivity
- apply freefall bit mask
- change `measure_en` to use `regmap_update_bits()` for POWER_CTL register
- fix comments and update documentation, particularly on inactivity time
v4 -> v5:
- read_config_value() and write_config_value() now use direct returns,
in case of a failure, measurement stays turned off
- fifo evaluation returns 0 in case of success
- axis enum turned into three different set of defines for tap, act and inact
- turn the suppress bit into a separate define macro
- variable naming, generally use axis_ctrl for similar variables
v3 -> v4:
- rename patch "add double tap suppress bit" to
"set the tap suppress bit permanently" to make it more comprehensive
- added patch "cleanup regmap return values"
- added patch "introduce adxl345_push_event function", as a solution
to the return value problem, group all int_stat evaluating pieces
in the same function
- tap, act and inact axis enabling are using now regmap cache
- activity enable depending on individual axis now, as the sensor offers
such feature
- inactivity enable depending on individual axis now, as the sensor offers
such feature
- fix bug in previous patch: separate axis direction in interrupt handler
sharing the same variable for tap and activity, if tap and activity
enabled together
- refac of the direction identification of previous patch: only read
act/tap axis once now in interrupt handler if both is enabled
- fix bug in previous patch: return value of pushed event in interrupt
handler
- several cleanups
v2 -> v3:
- generally introduction of regmap cache for all directly stored 8-bit
values, specification of volatile regs and cleanup
- moving thresholds, unchanged values and flags to regmap cache, in
consequence removal of corresponding member values of the state
instance
- removal of intio and int_map member fields due to regmap cache, thus
split of set_interrupts() patches in two parts
- rework documentation
- rework of ac-bit comment
v1 -> v2:
- implementation of all events (but tap2 suppress bit) by means IIO ABI
- add sample frequency / ODR configuration
- add g ranges configuration
- add activity/inactivity using auto-sleep and powersave
- add dynamic adjustment of default values for
activity/inactivity thresholds and time for inactivity based on ODR
and g range (can be overwritten)
- add sensor documentation
---
Lothar Rubusch (8):
iio: accel: adxl345: simplify interrupt mapping
iio: accel: adxl345: simplify reading the FIFO
iio: accel: adxl345: add activity event feature
iio: accel: adxl345: add inactivity feature
iio: accel: adxl345: add coupling detection for activity/inactivity
iio: accel: adxl345: extend inactivity time for less than 1s
docs: iio: add documentation for adxl345 driver
docs: iio: describe inactivity and free-fall detection on the ADXL345
Documentation/iio/adxl345.rst | 443 +++++++++++++++++
Documentation/iio/index.rst | 1 +
drivers/iio/accel/adxl345_core.c | 816 ++++++++++++++++++++++++++++++-
3 files changed, 1237 insertions(+), 23 deletions(-)
create mode 100644 Documentation/iio/adxl345.rst
base-commit: 42498420746a4db923f03d048a0ebc9bd2371f56
prerequisite-patch-id: c3c61d8d9cbb12a2d79a094519bf07dfece74318
--
2.39.5
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v11 1/8] iio: accel: adxl345: simplify interrupt mapping
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
@ 2025-07-02 23:03 ` Lothar Rubusch
2025-07-06 16:10 ` Jonathan Cameron
2025-07-02 23:03 ` [PATCH v11 2/8] iio: accel: adxl345: simplify reading the FIFO Lothar Rubusch
` (6 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, eraretuya, l.rubusch,
Andy Shevchenko
Refactor the sensor interrupt mapping by utilizing regmap_assign_bits(),
which accepts a boolean value directly. Introduce a helper function to
streamline the identification of the configured interrupt line pin. Also,
use identifiers from units.h to represent the full 8-bit register when
setting bits.
This is a purely refactoring change and does not affect functionality.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/iio/accel/adxl345_core.c | 34 +++++++++++++++++++-------------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index e21ec6c15d15..6c437d7a59ed 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -1088,6 +1088,19 @@ static const struct iio_info adxl345_info = {
.hwfifo_set_watermark = adxl345_set_watermark,
};
+static int adxl345_get_int_line(struct device *dev, int *irq)
+{
+ *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
+ if (*irq > 0)
+ return ADXL345_INT1;
+
+ *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
+ if (*irq > 0)
+ return ADXL345_INT2;
+
+ return ADXL345_INT_NONE;
+}
+
/**
* adxl345_core_probe() - Probe and setup for the accelerometer.
* @dev: Driver model representation of the device
@@ -1203,23 +1216,16 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
- irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
- if (irq < 0) {
- intio = ADXL345_INT2;
- irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
- if (irq < 0)
- intio = ADXL345_INT_NONE;
- }
-
+ intio = adxl345_get_int_line(dev, &irq);
if (intio != ADXL345_INT_NONE) {
/*
- * Any bits set to 0 in the INT map register send their respective
- * interrupts to the INT1 pin, whereas bits set to 1 send their respective
- * interrupts to the INT2 pin. The intio shall convert this accordingly.
+ * In the INT map register, bits set to 0 route their
+ * corresponding interrupts to the INT1 pin, while bits set to 1
+ * route them to the INT2 pin. The intio should handle this
+ * mapping accordingly.
*/
- regval = intio ? 0xff : 0;
-
- ret = regmap_write(st->regmap, ADXL345_REG_INT_MAP, regval);
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_MAP,
+ U8_MAX, intio);
if (ret)
return ret;
--
2.39.5
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v11 2/8] iio: accel: adxl345: simplify reading the FIFO
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 1/8] iio: accel: adxl345: simplify interrupt mapping Lothar Rubusch
@ 2025-07-02 23:03 ` Lothar Rubusch
2025-07-06 16:11 ` Jonathan Cameron
2025-07-02 23:03 ` [PATCH v11 3/8] iio: accel: adxl345: add activity event feature Lothar Rubusch
` (5 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, eraretuya, l.rubusch
Bulk FIFO reading can be streamlined by eliminating redundant variables and
simplifying the process of reading x-, y-, and z-axis measurement sets.
This is a refactoring change with no expected impact on functionality.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 6c437d7a59ed..b7dfd0007aa0 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -885,15 +885,12 @@ static int adxl345_get_samples(struct adxl345_state *st)
*/
static int adxl345_fifo_transfer(struct adxl345_state *st, int samples)
{
- size_t count;
int i, ret = 0;
- /* count is the 3x the fifo_buf element size, hence 6B */
- count = sizeof(st->fifo_buf[0]) * ADXL345_DIRS;
for (i = 0; i < samples; i++) {
- /* read 3x 2 byte elements from base address into next fifo_buf position */
ret = regmap_bulk_read(st->regmap, ADXL345_REG_XYZ_BASE,
- st->fifo_buf + (i * count / 2), count);
+ st->fifo_buf + (i * ADXL345_DIRS),
+ sizeof(st->fifo_buf[0]) * ADXL345_DIRS);
if (ret)
return ret;
--
2.39.5
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v11 3/8] iio: accel: adxl345: add activity event feature
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 1/8] iio: accel: adxl345: simplify interrupt mapping Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 2/8] iio: accel: adxl345: simplify reading the FIFO Lothar Rubusch
@ 2025-07-02 23:03 ` Lothar Rubusch
2025-07-03 14:24 ` Andy Shevchenko
2025-07-02 23:03 ` [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature Lothar Rubusch
` (4 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, eraretuya, l.rubusch
Enable the sensor to detect activity and trigger interrupts accordingly.
Activity events are determined based on a threshold, which is initialized
to a sensible default during probe. This default value is adopted from the
legacy ADXL345 input driver to maintain consistent behavior.
The combination of activity detection, ODR configuration, and range
settings lays the groundwork for the activity/inactivity hysteresis
mechanism, which will be implemented in a subsequent patch. As such,
portions of this patch prepare switch-case structures to support those
upcoming changes.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 242 ++++++++++++++++++++++++++++++-
1 file changed, 239 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index b7dfd0007aa0..eb24ee7c4251 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -36,11 +36,17 @@
#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)
+#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
+
/* single/double tap */
enum adxl345_tap_type {
ADXL345_SINGLE_TAP,
@@ -64,6 +70,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,
@@ -144,6 +163,13 @@ struct adxl345_state {
};
static const struct iio_event_spec adxl345_events[] = {
+ {
+ /* activity */
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
{
/* single tap */
.type = IIO_EV_TYPE_GESTURE,
@@ -237,6 +263,87 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
ADXL345_POWER_CTL_MEASURE, en);
}
+/* activity / inactivity */
+
+static int adxl345_is_act_inact_en(struct adxl345_state *st,
+ enum adxl345_activity_type type)
+{
+ unsigned int axis_ctrl;
+ unsigned int regval;
+ bool en;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ /* Check if axis for activity are enabled */
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ en = FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl);
+ if (!en)
+ return false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if specific interrupt is enabled */
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val);
+ if (ret)
+ return ret;
+
+ return adxl345_act_int_reg[type] & regval;
+}
+
+static int adxl345_set_act_inact_en(struct adxl345_state *st,
+ enum adxl345_activity_type type,
+ bool cmd_en)
+{
+ unsigned int axis_ctrl;
+ unsigned int threshold;
+ int ret;
+
+ if (cmd_en) {
+ /* When turning on, check if threshold is valid */
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[type],
+ &threshold);
+ if (ret)
+ return ret;
+
+ if (!threshold) /* Just ignore the command if threshold is 0 */
+ return 0;
+ }
+
+ /* Start modifying configuration registers */
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ /* Enable axis according to the command */
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ axis_ctrl = ADXL345_ACT_XYZ_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
+ axis_ctrl, cmd_en);
+ if (ret)
+ return ret;
+
+ /* Enable the interrupt line, according to the command */
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_act_int_reg[type], cmd_en);
+ if (ret)
+ return ret;
+
+ return adxl345_set_measure_en(st, true);
+}
+
/* tap */
static int _adxl345_set_tap_int(struct adxl345_state *st,
@@ -624,6 +731,31 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
return adxl345_set_measure_en(st, true);
}
+static int adxl345_read_mag_config(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum adxl345_activity_type type_act)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return !!adxl345_is_act_inact_en(st, type_act);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_write_mag_config(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum adxl345_activity_type type_act,
+ bool state)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl345_set_act_inact_en(st, type_act, state);
+ default:
+ return -EINVAL;
+ }
+}
+
static int adxl345_read_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
@@ -634,6 +766,9 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
int ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl345_read_mag_config(st, dir,
+ ADXL345_ACTIVITY);
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -665,6 +800,10 @@ 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:
+ return adxl345_write_mag_config(st, dir,
+ ADXL345_ACTIVITY,
+ state);
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -679,6 +818,58 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
}
}
+static int adxl345_read_mag_value(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ enum adxl345_activity_type type_act,
+ int *val, int *val2)
+{
+ unsigned int threshold;
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[type_act],
+ &threshold);
+ if (ret)
+ return ret;
+ *val = 62500 * threshold;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_write_mag_value(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ enum adxl345_activity_type type_act,
+ int val, int val2)
+{
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /* Scaling factor 62.5mg/LSB, i.e. ~16g corresponds to 0xff */
+ val = DIV_ROUND_CLOSEST(val * MICRO + val2, 62500);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return regmap_write(st->regmap,
+ adxl345_act_thresh_reg[type_act],
+ val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
static int adxl345_read_event_value(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
@@ -691,6 +882,10 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
int ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl345_read_mag_value(st, dir, info,
+ ADXL345_ACTIVITY,
+ val, val2);
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -741,6 +936,13 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
return ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ ret = adxl345_write_mag_value(st, dir, info,
+ ADXL345_ACTIVITY,
+ val, val2);
+ if (ret)
+ return ret;
+ break;
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -980,7 +1182,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);
@@ -1007,6 +1210,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)
@@ -1034,6 +1247,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;
@@ -1042,7 +1256,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;
@@ -1053,12 +1268,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))
@@ -1226,6 +1448,20 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ /*
+ * Initialized with sensible default values to streamline
+ * sensor operation. These defaults are partly derived from
+ * the previous input driver for the ADXL345 and partly
+ * based on the recommendations provided 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] 22+ messages in thread
* [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (2 preceding siblings ...)
2025-07-02 23:03 ` [PATCH v11 3/8] iio: accel: adxl345: add activity event feature Lothar Rubusch
@ 2025-07-02 23:03 ` Lothar Rubusch
2025-07-03 14:26 ` Andy Shevchenko
2025-07-02 23:03 ` [PATCH v11 5/8] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
` (3 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, eraretuya, l.rubusch
Add support for the sensor’s inactivity feature in the driver. When both
activity and inactivity detection are enabled, the sensor sets a link bit
that ties the two functions together. This also enables auto-sleep mode,
allowing the sensor to automatically enter sleep state upon detecting
inactivity.
Inactivity detection relies on a configurable threshold and a specified
time period. If sensor measurements remain below the threshold for the
defined duration, the sensor transitions to the inactivity state.
When an Output Data Rate (ODR) is set, the inactivity time period is
automatically adjusted to a sensible default. Higher ODRs result in shorter
inactivity timeouts, while lower ODRs allow longer durations-within
reasonable upper and lower bounds. This is important because features like
auto-sleep operate effectively only between 12.5 Hz and 400 Hz. These
defaults are applied when the sample rate is modified, but users can
override them by explicitly setting a custom inactivity timeout.
Similarly, configuring the g-range provides default threshold values for
both activity and inactivity detection. These are implicit defaults meant
to simplify configuration, but they can also be manually overridden as
needed.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 238 ++++++++++++++++++++++++++++++-
1 file changed, 234 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index eb24ee7c4251..90fef42ac021 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -37,11 +37,18 @@
#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_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN)
+
#define ADXL345_ACT_Z_EN BIT(4)
#define ADXL345_ACT_Y_EN BIT(5)
#define ADXL345_ACT_X_EN BIT(6)
@@ -73,14 +80,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 {
@@ -148,6 +158,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;
@@ -214,10 +232,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[] = {
@@ -265,6 +302,52 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
/* activity / inactivity */
+/**
+ * adxl345_set_inact_time - 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 seconds. If a user sets
+ * val_s to 0, a default inactivity time is calculated automatically (since 0 is
+ * also invalid and undefined by the sensor).
+ *
+ * In such cases, power consumption should be considered: the inactivity period
+ * should be shorter at higher sampling frequencies and longer at lower ones.
+ * Specifically, for frequencies above 255 Hz, the default is set to 10 seconds;
+ * for frequencies below 10 Hz, it defaults to 255 seconds.
+ *
+ * The calculation method subtracts the integer part of the configured sample
+ * frequency from 255 to estimate the inactivity time in seconds. Sub-Hertz
+ * values are ignored in this approximation. Since the recommended output data
+ * rates (ODRs) for features like activity/inactivity detection, sleep modes,
+ * and free fall range between 12.5 Hz and 400 Hz, frequencies outside this
+ * range will either use the defined boundary defaults or require explicit
+ * configuration via val_s.
+ *
+ * Return: 0 or error value.
+ */
+static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_s)
+{
+ int max_boundary = U8_MAX;
+ int min_boundary = 10;
+ unsigned int val = min(val_s, U8_MAX);
+ 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 = clamp(max_boundary - adxl345_odr_tbl[odr][0],
+ min_boundary, max_boundary);
+ }
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val);
+}
+
static int adxl345_is_act_inact_en(struct adxl345_state *st,
enum adxl345_activity_type type)
{
@@ -284,6 +367,11 @@ static int adxl345_is_act_inact_en(struct adxl345_state *st,
if (!en)
return false;
break;
+ case ADXL345_INACTIVITY:
+ en = FIELD_GET(ADXL345_INACT_XYZ_EN, axis_ctrl);
+ if (!en)
+ return false;
+ break;
default:
return -EINVAL;
}
@@ -296,12 +384,32 @@ static int adxl345_is_act_inact_en(struct adxl345_state *st,
return adxl345_act_int_reg[type] & regval;
}
+static int adxl345_set_act_inact_linkbit(struct adxl345_state *st,
+ enum adxl345_activity_type type,
+ bool en)
+{
+ int act_en, inact_en;
+
+ act_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY);
+ if (act_en < 0)
+ return act_en;
+
+ inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY);
+ if (inact_en < 0)
+ return inact_en;
+
+ return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL,
+ ADXL345_POWER_CTL_INACT_MSK,
+ en && act_en && inact_en);
+}
+
static int adxl345_set_act_inact_en(struct adxl345_state *st,
enum adxl345_activity_type type,
bool cmd_en)
{
unsigned int axis_ctrl;
unsigned int threshold;
+ unsigned int period;
int ret;
if (cmd_en) {
@@ -314,6 +422,18 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
if (!threshold) /* Just ignore the command if threshold is 0 */
return 0;
+
+ /* When turning on inactivity, check if inact time is valid */
+ if (type == ADXL345_INACTIVITY) {
+ ret = regmap_read(st->regmap,
+ ADXL345_REG_TIME_INACT,
+ &period);
+ if (ret)
+ return ret;
+
+ if (!period)
+ return 0;
+ }
}
/* Start modifying configuration registers */
@@ -326,6 +446,9 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
case ADXL345_ACTIVITY:
axis_ctrl = ADXL345_ACT_XYZ_EN;
break;
+ case ADXL345_INACTIVITY:
+ axis_ctrl = ADXL345_INACT_XYZ_EN;
+ break;
default:
return -EINVAL;
}
@@ -341,6 +464,11 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
if (ret)
return ret;
+ /* Set link-bit and auto-sleep only when ACT and INACT are enabled */
+ ret = adxl345_set_act_inact_linkbit(st, type, cmd_en);
+ if (ret)
+ return ret;
+
return adxl345_set_measure_en(st, true);
}
@@ -573,9 +701,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(st, 0);
}
static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
@@ -596,9 +731,49 @@ 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(U8_MAX, max(1, act_threshold));
+
+ inact_threshold = inact_threshold * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ inact_threshold = min(U8_MAX, 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,
@@ -733,11 +908,14 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
static int adxl345_read_mag_config(struct adxl345_state *st,
enum iio_event_direction dir,
- enum adxl345_activity_type type_act)
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact)
{
switch (dir) {
case IIO_EV_DIR_RISING:
return !!adxl345_is_act_inact_en(st, type_act);
+ case IIO_EV_DIR_FALLING:
+ return !!adxl345_is_act_inact_en(st, type_inact);
default:
return -EINVAL;
}
@@ -746,11 +924,14 @@ static int adxl345_read_mag_config(struct adxl345_state *st,
static int adxl345_write_mag_config(struct adxl345_state *st,
enum iio_event_direction dir,
enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
bool state)
{
switch (dir) {
case IIO_EV_DIR_RISING:
return adxl345_set_act_inact_en(st, type_act, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_act_inact_en(st, type_inact, state);
default:
return -EINVAL;
}
@@ -768,7 +949,8 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
switch (type) {
case IIO_EV_TYPE_MAG:
return adxl345_read_mag_config(st, dir,
- ADXL345_ACTIVITY);
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY);
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -803,6 +985,7 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
case IIO_EV_TYPE_MAG:
return adxl345_write_mag_config(st, dir,
ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
state);
case IIO_EV_TYPE_GESTURE:
switch (dir) {
@@ -822,9 +1005,11 @@ static int adxl345_read_mag_value(struct adxl345_state *st,
enum iio_event_direction dir,
enum iio_event_info info,
enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
int *val, int *val2)
{
unsigned int threshold;
+ unsigned int period;
int ret;
switch (info) {
@@ -839,9 +1024,26 @@ static int adxl345_read_mag_value(struct adxl345_state *st,
*val = 62500 * threshold;
*val2 = MICRO;
return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[type_inact],
+ &threshold);
+ if (ret)
+ return ret;
+ *val = 62500 * threshold;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
+ case IIO_EV_INFO_PERIOD:
+ ret = regmap_read(st->regmap,
+ ADXL345_REG_TIME_INACT,
+ &period);
+ if (ret)
+ return ret;
+ *val = period;
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
@@ -851,6 +1053,7 @@ static int adxl345_write_mag_value(struct adxl345_state *st,
enum iio_event_direction dir,
enum iio_event_info info,
enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
int val, int val2)
{
switch (info) {
@@ -862,9 +1065,15 @@ static int adxl345_write_mag_value(struct adxl345_state *st,
return regmap_write(st->regmap,
adxl345_act_thresh_reg[type_act],
val);
+ case IIO_EV_DIR_FALLING:
+ return regmap_write(st->regmap,
+ adxl345_act_thresh_reg[type_inact],
+ val);
default:
return -EINVAL;
}
+ case IIO_EV_INFO_PERIOD:
+ return adxl345_set_inact_time(st, val);
default:
return -EINVAL;
}
@@ -885,6 +1094,7 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
case IIO_EV_TYPE_MAG:
return adxl345_read_mag_value(st, dir, info,
ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
val, val2);
case IIO_EV_TYPE_GESTURE:
switch (info) {
@@ -939,6 +1149,7 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
case IIO_EV_TYPE_MAG:
ret = adxl345_write_mag_value(st, dir, info,
ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
val, val2);
if (ret)
return ret;
@@ -1220,6 +1431,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)
@@ -1458,10 +1680,18 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ ret = regmap_write(st->regmap, ADXL345_REG_TIME_INACT, 0x37);
+ 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] 22+ messages in thread
* [PATCH v11 5/8] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (3 preceding siblings ...)
2025-07-02 23:03 ` [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature Lothar Rubusch
@ 2025-07-02 23:03 ` Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 6/8] iio: accel: adxl345: extend inactivity time for less than 1s Lothar Rubusch
` (2 subsequent siblings)
7 siblings, 0 replies; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, eraretuya, l.rubusch
Enable AC/DC coupling configuration for activity and inactivity detection
by setting the AC/DC bit. Extend existing magnitude-based detection with
adaptive AC-coupled mode.
Use DC-coupled mode to compare acceleration samples directly against
configured thresholds. Use AC-coupled mode to compare samples against a
reference taken at the start of activity detection. Implement DC-coupled
events using MAG, and AC-coupled events using MAG_ADAPTIVE.
Expose configuration of thresholds and periods via separate sysfs handles.
Note that both coupling modes share the same sensor registers, so activity
or inactivity detection cannot be configured for both AC and DC
simultaneously. Apply the most recently configured mode.
Simplify event handling and support adaptive AC-coupling.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 252 +++++++++++++++++++++++++++++--
1 file changed, 238 insertions(+), 14 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 90fef42ac021..7038a4effd08 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -38,6 +38,8 @@
#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_REG_ACT_ACDC_MSK BIT(7)
+#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)
@@ -54,6 +56,9 @@
#define ADXL345_ACT_X_EN BIT(6)
#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
+#define ADXL345_COUPLING_DC 0
+#define ADXL345_COUPLING_AC 1
+
/* single/double tap */
enum adxl345_tap_type {
ADXL345_SINGLE_TAP,
@@ -81,16 +86,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 {
@@ -188,6 +206,13 @@ static const struct iio_event_spec adxl345_events[] = {
.mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
BIT(IIO_EV_INFO_VALUE),
},
+ {
+ /* activity, ac bit set */
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
{
/* single tap */
.type = IIO_EV_TYPE_GESTURE,
@@ -241,6 +266,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_ADAPTIVE,
+ .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[] = {
@@ -348,12 +381,114 @@ static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_s)
return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val);
}
+/**
+ * adxl345_is_act_inact_ac() - Verify if AC or DC coupling is currently enabled.
+ *
+ * @st: The device data.
+ * @type: The activity or inactivity type.
+ *
+ * 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: true if configured coupling matches the provided type, else a negative
+ * error value.
+ */
+static int adxl345_is_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type)
+{
+ unsigned int regval;
+ bool coupling;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
+ if (ret)
+ return ret;
+
+ coupling = adxl345_act_acdc_msk[type] & regval;
+
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ case ADXL345_INACTIVITY:
+ return coupling == ADXL345_COUPLING_DC;
+ case ADXL345_ACTIVITY_AC:
+ case ADXL345_INACTIVITY_AC:
+ return coupling == ADXL345_COUPLING_AC;
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * adxl345_set_act_inact_ac() - Configure AC coupling or DC coupling.
+ *
+ * @st: The device data.
+ * @type: Provide a type of activity or inactivity.
+ * @cmd_en: enable or disable AC coupling.
+ *
+ * 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,
+ bool cmd_en)
+{
+ unsigned int act_inact_ac;
+
+ if (type == ADXL345_ACTIVITY_AC || type == ADXL345_INACTIVITY_AC)
+ act_inact_ac = ADXL345_COUPLING_AC && cmd_en;
+ else
+ act_inact_ac = ADXL345_COUPLING_DC && cmd_en;
+
+ /*
+ * 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_assign_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 adxl345_activity_type type)
{
unsigned int axis_ctrl;
unsigned int regval;
- bool en;
+ bool int_en, en;
int ret;
ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl);
@@ -363,11 +498,13 @@ static int adxl345_is_act_inact_en(struct adxl345_state *st,
/* Check if axis for activity are enabled */
switch (type) {
case ADXL345_ACTIVITY:
+ case ADXL345_ACTIVITY_AC:
en = FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl);
if (!en)
return false;
break;
case ADXL345_INACTIVITY:
+ case ADXL345_INACTIVITY_AC:
en = FIELD_GET(ADXL345_INACT_XYZ_EN, axis_ctrl);
if (!en)
return false;
@@ -381,23 +518,40 @@ static int adxl345_is_act_inact_en(struct adxl345_state *st,
if (ret)
return ret;
- return adxl345_act_int_reg[type] & regval;
+ int_en = adxl345_act_int_reg[type] & regval;
+ if (!int_en)
+ return false;
+
+ /* Check if configured coupling matches provided type */
+ return adxl345_is_act_inact_ac(st, type);
}
static int adxl345_set_act_inact_linkbit(struct adxl345_state *st,
enum adxl345_activity_type type,
bool en)
{
+ int act_ac_en, inact_ac_en;
int act_en, inact_en;
act_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY);
if (act_en < 0)
return act_en;
+ act_ac_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY_AC);
+ if (act_ac_en < 0)
+ return act_ac_en;
+
inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY);
if (inact_en < 0)
return inact_en;
+ inact_ac_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY_AC);
+ if (inact_ac_en < 0)
+ return inact_ac_en;
+
+ inact_en = inact_en || inact_ac_en;
+ act_en = act_en || act_ac_en;
+
return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL,
ADXL345_POWER_CTL_INACT_MSK,
en && act_en && inact_en);
@@ -424,7 +578,7 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
return 0;
/* When turning on inactivity, check if inact time is valid */
- if (type == ADXL345_INACTIVITY) {
+ if (type == ADXL345_INACTIVITY || type == ADXL345_INACTIVITY_AC) {
ret = regmap_read(st->regmap,
ADXL345_REG_TIME_INACT,
&period);
@@ -434,6 +588,16 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
if (!period)
return 0;
}
+ } else {
+ /*
+ * When turning off an activity, ensure that the correct
+ * coupling event is specified. This step helps prevent misuse -
+ * for example, if an AC-coupled activity is active and the
+ * current call attempts to turn off a DC-coupled activity, this
+ * inconsistency should be detected here.
+ */
+ if (adxl345_is_act_inact_ac(st, type) <= 0)
+ return 0;
}
/* Start modifying configuration registers */
@@ -444,9 +608,11 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
/* Enable axis according to the command */
switch (type) {
case ADXL345_ACTIVITY:
+ case ADXL345_ACTIVITY_AC:
axis_ctrl = ADXL345_ACT_XYZ_EN;
break;
case ADXL345_INACTIVITY:
+ case ADXL345_INACTIVITY_AC:
axis_ctrl = ADXL345_INACT_XYZ_EN;
break;
default:
@@ -458,6 +624,11 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
if (ret)
return ret;
+ /* Update AC/DC-coupling according to the command */
+ ret = adxl345_set_act_inact_ac(st, type, cmd_en);
+ if (ret)
+ return ret;
+
/* Enable the interrupt line, according to the command */
ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_ENABLE,
adxl345_act_int_reg[type], cmd_en);
@@ -951,6 +1122,10 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
return adxl345_read_mag_config(st, dir,
ADXL345_ACTIVITY,
ADXL345_INACTIVITY);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_read_mag_config(st, dir,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC);
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -987,6 +1162,11 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
ADXL345_ACTIVITY,
ADXL345_INACTIVITY,
state);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_write_mag_config(st, dir,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ state);
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -1096,6 +1276,11 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
ADXL345_ACTIVITY,
ADXL345_INACTIVITY,
val, val2);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_read_mag_value(st, dir, info,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ val, val2);
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -1154,6 +1339,14 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
if (ret)
return ret;
break;
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ ret = adxl345_write_mag_value(st, dir, info,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ val, val2);
+ if (ret)
+ return ret;
+ break;
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -1398,6 +1591,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;
@@ -1422,22 +1616,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_ADAPTIVE,
+ 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_ADAPTIVE,
+ 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] 22+ messages in thread
* [PATCH v11 6/8] iio: accel: adxl345: extend inactivity time for less than 1s
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (4 preceding siblings ...)
2025-07-02 23:03 ` [PATCH v11 5/8] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
@ 2025-07-02 23:03 ` Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 7/8] docs: iio: add documentation for adxl345 driver Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 8/8] docs: iio: describe inactivity and free-fall detection on the ADXL345 Lothar Rubusch
7 siblings, 0 replies; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, eraretuya, l.rubusch
Inactivity and free-fall events are essentially the same type of sensor
events. Therefore, inactivity detection (normally set for periods between 1
and 255 seconds) can be extended for shorter durations to support free-fall
detection.
For periods shorter than 1 second, the driver automatically configures the
threshold and duration using the free-fall register. For periods longer
than 1 second, it uses the inactivity threshold and duration using the
inactivity registers.
When using the free-fall register, the link bit is not set, which means
auto-sleep cannot be enabled if activity detection is also active.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 201 +++++++++++++++++++++----------
1 file changed, 139 insertions(+), 62 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 7038a4effd08..614d29e3ed2e 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -40,12 +40,15 @@
#define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
#define ADXL345_REG_ACT_ACDC_MSK BIT(7)
#define ADXL345_REG_INACT_ACDC_MSK BIT(3)
+#define ADXL345_REG_NO_ACDC_MSK 0x00
#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_ACT_INACT_NO_AXIS_EN 0x00
+
#define ADXL345_INACT_Z_EN BIT(0)
#define ADXL345_INACT_Y_EN BIT(1)
#define ADXL345_INACT_X_EN BIT(2)
@@ -88,6 +91,7 @@ enum adxl345_activity_type {
ADXL345_INACTIVITY,
ADXL345_ACTIVITY_AC,
ADXL345_INACTIVITY_AC,
+ ADXL345_INACTIVITY_FF,
};
static const unsigned int adxl345_act_int_reg[] = {
@@ -95,6 +99,7 @@ static const unsigned int adxl345_act_int_reg[] = {
[ADXL345_INACTIVITY] = ADXL345_INT_INACTIVITY,
[ADXL345_ACTIVITY_AC] = ADXL345_INT_ACTIVITY,
[ADXL345_INACTIVITY_AC] = ADXL345_INT_INACTIVITY,
+ [ADXL345_INACTIVITY_FF] = ADXL345_INT_FREE_FALL,
};
static const unsigned int adxl345_act_thresh_reg[] = {
@@ -102,6 +107,7 @@ static const unsigned int adxl345_act_thresh_reg[] = {
[ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
[ADXL345_ACTIVITY_AC] = ADXL345_REG_THRESH_ACT,
[ADXL345_INACTIVITY_AC] = ADXL345_REG_THRESH_INACT,
+ [ADXL345_INACTIVITY_FF] = ADXL345_REG_THRESH_FF,
};
static const unsigned int adxl345_act_acdc_msk[] = {
@@ -109,6 +115,7 @@ static const unsigned int adxl345_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,
+ [ADXL345_INACTIVITY_FF] = ADXL345_REG_NO_ACDC_MSK,
};
enum adxl345_odr {
@@ -191,6 +198,9 @@ struct adxl345_state {
u8 watermark;
u8 fifo_mode;
+ u8 inact_threshold;
+ u32 inact_time_ms;
+
u32 tap_duration_us;
u32 tap_latent_us;
u32 tap_window_us;
@@ -335,10 +345,72 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
/* activity / inactivity */
+static int adxl345_set_inact_threshold(struct adxl345_state *st,
+ unsigned int threshold)
+{
+ int ret;
+
+ st->inact_threshold = min(U8_MAX, threshold);
+
+ ret = regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ st->inact_threshold);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY_FF],
+ st->inact_threshold);
+}
+
+static int adxl345_set_default_time(struct adxl345_state *st)
+{
+ int max_boundary = U8_MAX;
+ int min_boundary = 10;
+ enum adxl345_odr odr;
+ unsigned int regval;
+ unsigned int val;
+ int ret;
+
+ /* Generated inactivity time based on ODR */
+ ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val);
+ if (ret)
+ return ret;
+
+ odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval);
+ val = clamp(max_boundary - adxl345_odr_tbl[odr][0],
+ min_boundary, max_boundary);
+ st->inact_time_ms = MILLI * val;
+
+ /* Inactivity time in s */
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val);
+}
+
+static int adxl345_set_inactivity_time(struct adxl345_state *st, u32 val_int)
+{
+ st->inact_time_ms = MILLI * val_int;
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val_int);
+}
+
+static int adxl345_set_freefall_time(struct adxl345_state *st, u32 val_fract)
+{
+ /*
+ * Datasheet max. value is 255 * 5000 us = 1.275000 seconds.
+ *
+ * Recommended values between 100ms and 350ms (0x14 to 0x46)
+ */
+ st->inact_time_ms = DIV_ROUND_UP(val_fract, MILLI);
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_FF,
+ DIV_ROUND_CLOSEST(val_fract, 5));
+}
+
/**
* adxl345_set_inact_time - Configure inactivity time explicitly or by ODR.
* @st: The sensor state instance.
- * @val_s: A desired time value, between 0 and 255.
+ * @val_int: The inactivity time, integer part.
+ * @val_fract: The inactivity time, fractional part when val_int is 0.
*
* Inactivity time can be configured between 1 and 255 seconds. If a user sets
* val_s to 0, a default inactivity time is calculated automatically (since 0 is
@@ -359,26 +431,24 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
*
* Return: 0 or error value.
*/
-static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_s)
+static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_int,
+ u32 val_fract)
{
- int max_boundary = U8_MAX;
- int min_boundary = 10;
- unsigned int val = min(val_s, U8_MAX);
- 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 = clamp(max_boundary - adxl345_odr_tbl[odr][0],
- min_boundary, max_boundary);
+ if (val_int > 0) {
+ /* Time >= 1s, inactivity */
+ return adxl345_set_inactivity_time(st, val_int);
+ } else if (val_int == 0) {
+ if (val_fract > 0) {
+ /* Time < 1s, free-fall */
+ return adxl345_set_freefall_time(st, val_fract);
+ } else if (val_fract == 0) {
+ /* Time == 0.0s */
+ return adxl345_set_default_time(st);
+ }
}
- return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val);
+ /* Do not support negative or wrong input. */
+ return -EINVAL;
}
/**
@@ -401,6 +471,9 @@ static int adxl345_is_act_inact_ac(struct adxl345_state *st,
bool coupling;
int ret;
+ if (type == ADXL345_INACTIVITY_FF)
+ return true;
+
ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
if (ret)
return ret;
@@ -509,6 +582,9 @@ static int adxl345_is_act_inact_en(struct adxl345_state *st,
if (!en)
return false;
break;
+ case ADXL345_INACTIVITY_FF:
+ en = true;
+ break;
default:
return -EINVAL;
}
@@ -541,15 +617,20 @@ static int adxl345_set_act_inact_linkbit(struct adxl345_state *st,
if (act_ac_en < 0)
return act_ac_en;
- inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY);
- if (inact_en < 0)
- return inact_en;
+ if (type == ADXL345_INACTIVITY_FF) {
+ inact_en = false;
+ } else {
+ inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY);
+ if (inact_en < 0)
+ return inact_en;
+
+ inact_ac_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY_AC);
+ if (inact_ac_en < 0)
+ return inact_ac_en;
- inact_ac_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY_AC);
- if (inact_ac_en < 0)
- return inact_ac_en;
+ inact_en = inact_en || inact_ac_en;
+ }
- inact_en = inact_en || inact_ac_en;
act_en = act_en || act_ac_en;
return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL,
@@ -568,11 +649,15 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
if (cmd_en) {
/* When turning on, check if threshold is valid */
- ret = regmap_read(st->regmap,
- adxl345_act_thresh_reg[type],
- &threshold);
- if (ret)
- return ret;
+ if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) {
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[type],
+ &threshold);
+ if (ret)
+ return ret;
+ } else {
+ threshold = st->inact_threshold;
+ }
if (!threshold) /* Just ignore the command if threshold is 0 */
return 0;
@@ -615,6 +700,9 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
case ADXL345_INACTIVITY_AC:
axis_ctrl = ADXL345_INACT_XYZ_EN;
break;
+ case ADXL345_INACTIVITY_FF:
+ axis_ctrl = ADXL345_ACT_INACT_NO_AXIS_EN;
+ break;
default:
return -EINVAL;
}
@@ -881,7 +969,7 @@ static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
return ret;
/* update inactivity time by ODR */
- return adxl345_set_inact_time(st, 0);
+ return adxl345_set_inact_time(st, 0, 0);
}
static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
@@ -918,12 +1006,6 @@ static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
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));
@@ -934,6 +1016,7 @@ static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
/ adxl345_range_factor_tbl[range];
act_threshold = min(U8_MAX, max(1, act_threshold));
+ inact_threshold = st->inact_threshold;
inact_threshold = inact_threshold * adxl345_range_factor_tbl[range_old]
/ adxl345_range_factor_tbl[range];
inact_threshold = min(U8_MAX, max(1, inact_threshold));
@@ -943,8 +1026,7 @@ static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
if (ret)
return ret;
- return regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_INACTIVITY],
- inact_threshold);
+ return adxl345_set_inact_threshold(st, inact_threshold);
}
static int adxl345_read_avail(struct iio_dev *indio_dev,
@@ -1189,7 +1271,6 @@ static int adxl345_read_mag_value(struct adxl345_state *st,
int *val, int *val2)
{
unsigned int threshold;
- unsigned int period;
int ret;
switch (info) {
@@ -1205,25 +1286,16 @@ static int adxl345_read_mag_value(struct adxl345_state *st,
*val2 = MICRO;
return IIO_VAL_FRACTIONAL;
case IIO_EV_DIR_FALLING:
- ret = regmap_read(st->regmap,
- adxl345_act_thresh_reg[type_inact],
- &threshold);
- if (ret)
- return ret;
- *val = 62500 * threshold;
+ *val = 62500 * st->inact_threshold;
*val2 = MICRO;
return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
case IIO_EV_INFO_PERIOD:
- ret = regmap_read(st->regmap,
- ADXL345_REG_TIME_INACT,
- &period);
- if (ret)
- return ret;
- *val = period;
- return IIO_VAL_INT;
+ *val = st->inact_time_ms;
+ *val2 = MILLI;
+ return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
@@ -1246,14 +1318,12 @@ static int adxl345_write_mag_value(struct adxl345_state *st,
adxl345_act_thresh_reg[type_act],
val);
case IIO_EV_DIR_FALLING:
- return regmap_write(st->regmap,
- adxl345_act_thresh_reg[type_inact],
- val);
+ return adxl345_set_inact_threshold(st, val);
default:
return -EINVAL;
}
case IIO_EV_INFO_PERIOD:
- return adxl345_set_inact_time(st, val);
+ return adxl345_set_inact_time(st, val, val2);
default:
return -EINVAL;
}
@@ -1666,6 +1736,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)
@@ -1904,15 +1985,11 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
- ret = regmap_write(st->regmap, ADXL345_REG_TIME_INACT, 0x37);
- 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);
+ ret = adxl345_set_inact_threshold(st, 4);
if (ret)
return ret;
--
2.39.5
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v11 7/8] docs: iio: add documentation for adxl345 driver
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (5 preceding siblings ...)
2025-07-02 23:03 ` [PATCH v11 6/8] iio: accel: adxl345: extend inactivity time for less than 1s Lothar Rubusch
@ 2025-07-02 23:03 ` Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 8/8] docs: iio: describe inactivity and free-fall detection on the ADXL345 Lothar Rubusch
7 siblings, 0 replies; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, 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 | 418 ++++++++++++++++++++++++++++++++++
Documentation/iio/index.rst | 1 +
2 files changed, 419 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..8ee01b8b87f4
--- /dev/null
+++ b/Documentation/iio/adxl345.rst
@@ -0,0 +1,418 @@
+.. 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
+-------------
+
+Specific IIO events are triggered by their corresponding interrupts. The sensor
+driver supports either none or a single active interrupt (INT) line, selectable
+from the two available options: INT1 or INT2. The active INT line should be
+specified in the device tree. If no INT line is configured, the sensor defaults
+to FIFO bypass mode, where event detection is disabled and only X, Y, and Z axis
+measurements are available.
+
+The table below lists the ADXL345-related device files located in the
+device-specific path: ``/sys/bus/iio/devices/iio:deviceX/events``.
+Note that activity and inactivity detection are DC-coupled by default;
+therefore, only the AC-coupled activity and inactivity events are explicitly
+listed.
+
++---------------------------------------------+---------------------------------------------+
+| 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_adaptive_rising_en | Enable AC coupled activity on X axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_adaptive_falling_period | AC coupled inactivity time in seconds |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_adaptive_falling_value | AC coupled inactivity threshold in 62.5/LSB |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_adaptive_rising_value | AC coupled activity threshold in 62.5/LSB |
++---------------------------------------------+---------------------------------------------+
+| in_accel_mag_rising_en | Enable activity detection on X axis |
++---------------------------------------------+---------------------------------------------+
+| 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&y&z_mag_falling_en | Enable inactivity detection on all axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_x&y&z_mag_adaptive_falling_en | Enable AC coupled inactivity on all axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_y_gesture_singletap_en | Enable single tap detection on Y axis |
++---------------------------------------------+---------------------------------------------+
+| in_accel_z_gesture_singletap_en | Enable single tap detection on Z axis |
++---------------------------------------------+---------------------------------------------+
+
+Please refer to the sensor's datasheet for a detailed description of this
+functionality.
+
+Manually setting the **ODR** will cause the driver to estimate default values
+for inactivity detection timing, where higher ODR values correspond to longer
+default wait times, and lower ODR values to shorter ones. If these defaults do
+not meet your application’s needs, you can explicitly configure the inactivity
+wait time. Setting this value to 0 will revert to the default behavior.
+
+When changing the **g range** configuration, the driver attempts to estimate
+appropriate activity and inactivity thresholds by scaling the default values
+based on the ratio of the previous range to the new one. The resulting threshold
+will never be zero and will always fall between 1 and 255, corresponding to up
+to 62.5 g/LSB as specified in the datasheet. However, you can override these
+estimated thresholds by setting explicit values.
+
+When **activity** and **inactivity** events are enabled, the driver
+automatically manages hysteresis behavior by setting the **link** and
+**auto-sleep** bits. The link bit connects the activity and inactivity
+functions, so that one follows the other. The auto-sleep function puts the
+sensor into sleep mode when inactivity is detected, reducing power consumption
+to the sub-12.5 Hz rate.
+
+In DC-coupled mode, the current acceleration magnitude is directly compared to
+the values in the THRESH_ACT and THRESH_INACT registers to determine activity or
+inactivity. In contrast, AC-coupled activity detection uses the acceleration
+value at the start of detection as a reference point, and subsequent samples are
+compared against this reference. While DC-coupling is the default mode-comparing
+live values to fixed thresholds-AC-coupling relies on an internal filter
+relative to the configured threshold.
+
+AC and DC coupling modes are configured separately for activity and inactivity
+detection, but only one mode can be active at a time for each. For example, if
+AC-coupled activity detection is enabled and then DC-coupled mode is set, only
+DC-coupled activity detection will be active. In other words, only the most
+recent configuration is applied.
+
+**Single tap** detection can be configured per the datasheet by setting the
+threshold and duration parameters. When only single tap detection is enabled,
+the single tap interrupt triggers as soon as the acceleration exceeds the
+threshold (marking the start of the duration) and then falls below it, provided
+the duration limit is not exceeded. If both single tap and double tap detections
+are enabled, the single tap interrupt is triggered only after the double tap
+event has been either confirmed or dismissed.
+
+To configure **double tap** detection, you must also set the window and latency
+parameters in microseconds (µs). The latency period begins once the single tap
+signal drops below the threshold and acts as a waiting time during which any
+spikes are ignored for double tap detection. After the latency period ends, the
+detection window starts. If the acceleration rises above the threshold and then
+falls below it again within this window, a double tap event is triggered upon
+the fall below the threshold.
+
+Double tap event detection is thoroughly explained in the datasheet. After a
+single tap event is detected, a double tap event may follow, provided the signal
+meets certain criteria. However, double tap detection can be invalidated for
+three reasons:
+
+* If the **suppress bit** is set, any acceleration spike above the tap
+ threshold during the tap latency period immediately invalidates the double tap
+ detection. In other words, no spikes are allowed during latency when the
+ suppress bit is active.
+
+* The double tap event is invalid if the acceleration is above the threshold at
+ the start of the double tap window.
+
+* Double tap detection is also invalidated if the acceleration duration exceeds
+ the limit set by the duration register.
+
+For double tap detection, the same duration applies as for single tap: the
+acceleration must rise above the threshold and then fall below it within the
+specified duration. Note that the suppress bit is typically enabled when double
+tap detection is active.
+
+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 formula:
+
+.. 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_adaptive_rising_value
+
+ ## AC coupled inactivity, threshold, [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 4 > ./events/in_accel_mag_adaptive_falling_value
+
+ ## AC coupled inactivity, time [s]
+ root:/sys/bus/iio/devices/iio:device0> echo 3 > ./events/in_accel_mag_adaptive_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
+
+ ## AC coupled activity, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_mag_adaptive_rising_en
+
+ ## AC coupled inactivity, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x\&y\&z_mag_adaptive_falling_en
+
+ ## singletap, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x_gesture_singletap_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_y_gesture_singletap_en
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_z_gesture_singletap_en
+
+ ## doubletap, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_gesture_doubletap_en
+
+Verify incoming events:
+
+.. code-block:: bash
+
+ root:# iio_event_monitor adxl345
+ Found IIO device with name adxl345 with device number 0
+ Event: time: 1739063415957073383, type: accel(z), channel: 0, evtype: 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.
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index 2d6afc5a8ed5..c333720c1c9f 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -32,6 +32,7 @@ Industrial I/O Kernel Drivers
adis16480
adis16550
adxl380
+ adxl345
bno055
ep93xx_adc
opt4060
--
2.39.5
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v11 8/8] docs: iio: describe inactivity and free-fall detection on the ADXL345
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (6 preceding siblings ...)
2025-07-02 23:03 ` [PATCH v11 7/8] docs: iio: add documentation for adxl345 driver Lothar Rubusch
@ 2025-07-02 23:03 ` Lothar Rubusch
2025-07-06 16:16 ` Jonathan Cameron
7 siblings, 1 reply; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-02 23:03 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet
Cc: linux-iio, linux-kernel, linux-doc, eraretuya, l.rubusch
Describe the inactivity detection additionally using the free-fall
register. Due to the controversial discussions on the mailing list, this
section of the documentation will be committed separately to allow for a
more focused and detailed elaboration of the topic.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
Documentation/iio/adxl345.rst | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/Documentation/iio/adxl345.rst b/Documentation/iio/adxl345.rst
index 8ee01b8b87f4..c5525267ea12 100644
--- a/Documentation/iio/adxl345.rst
+++ b/Documentation/iio/adxl345.rst
@@ -150,6 +150,31 @@ functions, so that one follows the other. The auto-sleep function puts the
sensor into sleep mode when inactivity is detected, reducing power consumption
to the sub-12.5 Hz rate.
+The inactivity time is configurable between 1 and 255 seconds. In addition to
+inactivity detection, the sensor also supports free-fall detection, which, from
+the IIO perspective, is treated as a fall in magnitude across all axes. In
+sensor terms, free-fall is defined using an inactivity period ranging from 0.000
+to 1.000 seconds.
+
+The driver behaves as follows:
+* If the configured inactivity period is 1 second or more, the driver uses the
+ sensor's inactivity register. This allows the event to be linked with
+ activity detection, use auto-sleep, and be either AC- or DC-coupled.
+
+* If the inactivity period is less than 1 second, the event is treated as plain
+ inactivity or free-fall detection. In this case, auto-sleep and coupling
+ (AC/DC) are not applied.
+
+* If an inactivity time of 0 seconds is configured, the driver selects a
+ heuristically determined default period (greater than 1 second) to optimize
+ power consumption. This also uses the inactivity register.
+
+Note: It is recommended to use the activity, inactivity, or free-fall registers
+when operating with an ODR between 12.5 Hz and 400 Hz. According to the
+datasheet, the recommended free-fall threshold is between 300 mg and 600 mg
+(register values 0x05 to 0x09), and the suggested free-fall time ranges from
+100 ms to 350 ms (register values 0x14 to 0x46).
+
In DC-coupled mode, the current acceleration magnitude is directly compared to
the values in the THRESH_ACT and THRESH_INACT registers to determine activity or
inactivity. In contrast, AC-coupled activity detection uses the acceleration
--
2.39.5
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v11 3/8] iio: accel: adxl345: add activity event feature
2025-07-02 23:03 ` [PATCH v11 3/8] iio: accel: adxl345: add activity event feature Lothar Rubusch
@ 2025-07-03 14:24 ` Andy Shevchenko
2025-07-06 16:09 ` Jonathan Cameron
0 siblings, 1 reply; 22+ messages in thread
From: Andy Shevchenko @ 2025-07-03 14:24 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet,
linux-iio, linux-kernel, linux-doc, eraretuya
On Wed, Jul 02, 2025 at 11:03:10PM +0000, Lothar Rubusch wrote:
> Enable the sensor to detect activity and trigger interrupts accordingly.
> Activity events are determined based on a threshold, which is initialized
> to a sensible default during probe. This default value is adopted from the
> legacy ADXL345 input driver to maintain consistent behavior.
>
> The combination of activity detection, ODR configuration, and range
> settings lays the groundwork for the activity/inactivity hysteresis
> mechanism, which will be implemented in a subsequent patch. As such,
> portions of this patch prepare switch-case structures to support those
> upcoming changes.
> #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)
> +#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
I'm trying to understand the logic behind the placement of the masks and bits.
To me it sounds that the above should be rather
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
#define ADXL345_TAP_Z_EN BIT(0)
#define ADXL345_TAP_Y_EN BIT(1)
#define ADXL345_TAP_X_EN BIT(2)
#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3) // Do we need this at all?
#define ADXL345_REG_TAP_SUPPRESS BIT(3) // or actually this? One is enough, no?
#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
#define ADXL345_ACT_Z_EN BIT(4)
#define ADXL345_ACT_Y_EN BIT(5)
#define ADXL345_ACT_X_EN BIT(6)
#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
(Yes, I know that the mess is preexisted, but try to keep some order in the
pieces you add here.)
...
> + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
> + BIT(IIO_EV_INFO_VALUE),
I would expect one of the below (indentation) styles
.mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
BIT(IIO_EV_INFO_VALUE),
.mask_shared_by_type =
BIT(IIO_EV_INFO_ENABLE) |
BIT(IIO_EV_INFO_VALUE),
...
> 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)
Hmm... Why not
static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
enum iio_modifier act_dir,
enum iio_modifier tap_dir)
?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature
2025-07-02 23:03 ` [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature Lothar Rubusch
@ 2025-07-03 14:26 ` Andy Shevchenko
2025-07-03 14:59 ` Lothar Rubusch
0 siblings, 1 reply; 22+ messages in thread
From: Andy Shevchenko @ 2025-07-03 14:26 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet,
linux-iio, linux-kernel, linux-doc, eraretuya
On Wed, Jul 02, 2025 at 11:03:11PM +0000, Lothar Rubusch wrote:
> Add support for the sensor’s inactivity feature in the driver. When both
> activity and inactivity detection are enabled, the sensor sets a link bit
> that ties the two functions together. This also enables auto-sleep mode,
> allowing the sensor to automatically enter sleep state upon detecting
> inactivity.
>
> Inactivity detection relies on a configurable threshold and a specified
> time period. If sensor measurements remain below the threshold for the
> defined duration, the sensor transitions to the inactivity state.
>
> When an Output Data Rate (ODR) is set, the inactivity time period is
> automatically adjusted to a sensible default. Higher ODRs result in shorter
> inactivity timeouts, while lower ODRs allow longer durations-within
> reasonable upper and lower bounds. This is important because features like
> auto-sleep operate effectively only between 12.5 Hz and 400 Hz. These
> defaults are applied when the sample rate is modified, but users can
> override them by explicitly setting a custom inactivity timeout.
>
> Similarly, configuring the g-range provides default threshold values for
> both activity and inactivity detection. These are implicit defaults meant
> to simplify configuration, but they can also be manually overridden as
> needed.
...
> #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_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN)
> +
> #define ADXL345_ACT_Z_EN BIT(4)
> #define ADXL345_ACT_Y_EN BIT(5)
> #define ADXL345_ACT_X_EN BIT(6)
Now it's even more mess. I am lost in understanding which bits/masks are from
the same offset and which are not.
...
> + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_PERIOD),
Same comment about indentation style.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature
2025-07-03 14:26 ` Andy Shevchenko
@ 2025-07-03 14:59 ` Lothar Rubusch
2025-07-03 15:45 ` Andy Shevchenko
0 siblings, 1 reply; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-03 14:59 UTC (permalink / raw)
To: Andy Shevchenko
Cc: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet,
linux-iio, linux-kernel, linux-doc, eraretuya
Hi Andy, thank you so much for the (almost) immediate feedback!
On Thu, Jul 3, 2025 at 4:26 PM Andy Shevchenko
<andriy.shevchenko@intel.com> wrote:
>
> On Wed, Jul 02, 2025 at 11:03:11PM +0000, Lothar Rubusch wrote:
> > Add support for the sensor’s inactivity feature in the driver. When both
> > activity and inactivity detection are enabled, the sensor sets a link bit
> > that ties the two functions together. This also enables auto-sleep mode,
> > allowing the sensor to automatically enter sleep state upon detecting
> > inactivity.
> >
> > Inactivity detection relies on a configurable threshold and a specified
> > time period. If sensor measurements remain below the threshold for the
> > defined duration, the sensor transitions to the inactivity state.
> >
> > When an Output Data Rate (ODR) is set, the inactivity time period is
> > automatically adjusted to a sensible default. Higher ODRs result in shorter
> > inactivity timeouts, while lower ODRs allow longer durations-within
> > reasonable upper and lower bounds. This is important because features like
> > auto-sleep operate effectively only between 12.5 Hz and 400 Hz. These
> > defaults are applied when the sample rate is modified, but users can
> > override them by explicitly setting a custom inactivity timeout.
> >
> > Similarly, configuring the g-range provides default threshold values for
> > both activity and inactivity detection. These are implicit defaults meant
> > to simplify configuration, but they can also be manually overridden as
> > needed.
>
> ...
>
> > #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_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN)
> > +
> > #define ADXL345_ACT_Z_EN BIT(4)
> > #define ADXL345_ACT_Y_EN BIT(5)
> > #define ADXL345_ACT_X_EN BIT(6)
>
> Now it's even more mess. I am lost in understanding which bits/masks are from
> the same offset and which are not.
>
I'm sorry for that. I mean, while the above is supposed to make it
clear where the "values" are coming from, I also could setup something
like the following which is shorter.
+#define ADXL345_INACT_XYZ_EN GENMASK(2,0)
+#define ADXL345_ACT_XYZ_EN GENMASK(6,4)
As I understand you, you'd rather prefer to see the latter one in the kernel?
> ...
>
> > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
> > + BIT(IIO_EV_INFO_PERIOD),
>
> Same comment about indentation style.
>
I'll take it on the list. As I mentioned, "checkpatch --strict" seems
to complain about function arguments not being aligned, but
assignments this way are passing nicely, like the one above. Depending
on what else comes up, I'll put this CR on the list. Thank you.
> --
> With Best Regards,
> Andy Shevchenko
>
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature
2025-07-03 14:59 ` Lothar Rubusch
@ 2025-07-03 15:45 ` Andy Shevchenko
2025-07-06 12:08 ` Jonathan Cameron
0 siblings, 1 reply; 22+ messages in thread
From: Andy Shevchenko @ 2025-07-03 15:45 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, jic23, dlechner, nuno.sa, andy, corbet,
linux-iio, linux-kernel, linux-doc, eraretuya
On Thu, Jul 03, 2025 at 04:59:50PM +0200, Lothar Rubusch wrote:
> On Thu, Jul 3, 2025 at 4:26 PM Andy Shevchenko
> <andriy.shevchenko@intel.com> wrote:
> > On Wed, Jul 02, 2025 at 11:03:11PM +0000, Lothar Rubusch wrote:
...
> > > #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_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN)
> > > +
> > > #define ADXL345_ACT_Z_EN BIT(4)
> > > #define ADXL345_ACT_Y_EN BIT(5)
> > > #define ADXL345_ACT_X_EN BIT(6)
> >
> > Now it's even more mess. I am lost in understanding which bits/masks are from
> > the same offset and which are not.
> >
>
> I'm sorry for that. I mean, while the above is supposed to make it
> clear where the "values" are coming from, I also could setup something
> like the following which is shorter.
> +#define ADXL345_INACT_XYZ_EN GENMASK(2,0)
> +#define ADXL345_ACT_XYZ_EN GENMASK(6,4)
>
> As I understand you, you'd rather prefer to see the latter one in the kernel?
My personal preference can be found, for example, in
drivers/pinctrl/intel/pinctrl-intel.c. But I'm not insisting to use
_my_ schema. Just find a way how to group them semantically.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature
2025-07-03 15:45 ` Andy Shevchenko
@ 2025-07-06 12:08 ` Jonathan Cameron
0 siblings, 0 replies; 22+ messages in thread
From: Jonathan Cameron @ 2025-07-06 12:08 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lothar Rubusch, lars, Michael.Hennerich, dlechner, nuno.sa, andy,
corbet, linux-iio, linux-kernel, linux-doc, eraretuya
On Thu, 3 Jul 2025 18:45:21 +0300
Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> On Thu, Jul 03, 2025 at 04:59:50PM +0200, Lothar Rubusch wrote:
> > On Thu, Jul 3, 2025 at 4:26 PM Andy Shevchenko
> > <andriy.shevchenko@intel.com> wrote:
> > > On Wed, Jul 02, 2025 at 11:03:11PM +0000, Lothar Rubusch wrote:
>
> ...
>
> > > > #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_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN)
> > > > +
> > > > #define ADXL345_ACT_Z_EN BIT(4)
> > > > #define ADXL345_ACT_Y_EN BIT(5)
> > > > #define ADXL345_ACT_X_EN BIT(6)
> > >
> > > Now it's even more mess. I am lost in understanding which bits/masks are from
> > > the same offset and which are not.
> > >
> >
> > I'm sorry for that. I mean, while the above is supposed to make it
> > clear where the "values" are coming from, I also could setup something
> > like the following which is shorter.
> > +#define ADXL345_INACT_XYZ_EN GENMASK(2,0)
> > +#define ADXL345_ACT_XYZ_EN GENMASK(6,4)
Definitely not for those. They aren't a mask, but rather 3 only somewhat
related bits.
> >
> > As I understand you, you'd rather prefer to see the latter one in the kernel?
>
> My personal preference can be found, for example, in
> drivers/pinctrl/intel/pinctrl-intel.c. But I'm not insisting to use
> _my_ schema. Just find a way how to group them semantically.
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 3/8] iio: accel: adxl345: add activity event feature
2025-07-03 14:24 ` Andy Shevchenko
@ 2025-07-06 16:09 ` Jonathan Cameron
2025-07-20 18:36 ` Lothar Rubusch
0 siblings, 1 reply; 22+ messages in thread
From: Jonathan Cameron @ 2025-07-06 16:09 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lothar Rubusch, lars, Michael.Hennerich, dlechner, nuno.sa, andy,
corbet, linux-iio, linux-kernel, linux-doc, eraretuya
On Thu, 3 Jul 2025 17:24:17 +0300
Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> On Wed, Jul 02, 2025 at 11:03:10PM +0000, Lothar Rubusch wrote:
> > Enable the sensor to detect activity and trigger interrupts accordingly.
> > Activity events are determined based on a threshold, which is initialized
> > to a sensible default during probe. This default value is adopted from the
> > legacy ADXL345 input driver to maintain consistent behavior.
> >
> > The combination of activity detection, ODR configuration, and range
> > settings lays the groundwork for the activity/inactivity hysteresis
> > mechanism, which will be implemented in a subsequent patch. As such,
> > portions of this patch prepare switch-case structures to support those
> > upcoming changes.
>
> > #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)
> > +#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
>
> I'm trying to understand the logic behind the placement of the masks and bits.
> To me it sounds that the above should be rather
>
> #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> #define ADXL345_TAP_Z_EN BIT(0)
> #define ADXL345_TAP_Y_EN BIT(1)
> #define ADXL345_TAP_X_EN BIT(2)
> #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3) // Do we need this at all?
> #define ADXL345_REG_TAP_SUPPRESS BIT(3) // or actually this? One is enough, no?
> #define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> #define ADXL345_ACT_Z_EN BIT(4)
> #define ADXL345_ACT_Y_EN BIT(5)
> #define ADXL345_ACT_X_EN BIT(6)
> #define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
>
> (Yes, I know that the mess is preexisted, but try to keep some order in the
> pieces you add here.)
FWIW I fully agree on keeping field definitions and field break up together.
The ACT_MSK is a little odd as thing as then we'd expect there to be bits
within that. So that FIELD_GET(a, ADXL345_REG_ACT_AXIS_MSK) would return
a value from a list of things like
ADXL345_REG_ACT_AXIS_VALUE_A and similar.
So I'd not define that as a mask a tall but just use the
ACT_XYZ_EN for it as then it's clear you are checking for any of the
3 bits being set.
Jonathan
>
> ...
>
> > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
> > + BIT(IIO_EV_INFO_VALUE),
>
> I would expect one of the below (indentation) styles
>
> .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
> BIT(IIO_EV_INFO_VALUE),
>
> .mask_shared_by_type =
> BIT(IIO_EV_INFO_ENABLE) |
> BIT(IIO_EV_INFO_VALUE),
>
> ...
>
> > 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)
>
> Hmm... Why not
>
> static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
> enum iio_modifier act_dir,
> enum iio_modifier tap_dir)
>
> ?
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 1/8] iio: accel: adxl345: simplify interrupt mapping
2025-07-02 23:03 ` [PATCH v11 1/8] iio: accel: adxl345: simplify interrupt mapping Lothar Rubusch
@ 2025-07-06 16:10 ` Jonathan Cameron
0 siblings, 0 replies; 22+ messages in thread
From: Jonathan Cameron @ 2025-07-06 16:10 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, dlechner, nuno.sa, andy, corbet,
linux-iio, linux-kernel, linux-doc, eraretuya, Andy Shevchenko
On Wed, 2 Jul 2025 23:03:08 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Refactor the sensor interrupt mapping by utilizing regmap_assign_bits(),
> which accepts a boolean value directly. Introduce a helper function to
> streamline the identification of the configured interrupt line pin. Also,
> use identifiers from units.h to represent the full 8-bit register when
> setting bits.
>
> This is a purely refactoring change and does not affect functionality.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
I'd really like to reduce the number of patches outstanding so I'll
apply a few of these now.
Applied this one.
Jonathan
> ---
> drivers/iio/accel/adxl345_core.c | 34 +++++++++++++++++++-------------
> 1 file changed, 20 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> index e21ec6c15d15..6c437d7a59ed 100644
> --- a/drivers/iio/accel/adxl345_core.c
> +++ b/drivers/iio/accel/adxl345_core.c
> @@ -1088,6 +1088,19 @@ static const struct iio_info adxl345_info = {
> .hwfifo_set_watermark = adxl345_set_watermark,
> };
>
> +static int adxl345_get_int_line(struct device *dev, int *irq)
> +{
> + *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
> + if (*irq > 0)
> + return ADXL345_INT1;
> +
> + *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
> + if (*irq > 0)
> + return ADXL345_INT2;
> +
> + return ADXL345_INT_NONE;
> +}
> +
> /**
> * adxl345_core_probe() - Probe and setup for the accelerometer.
> * @dev: Driver model representation of the device
> @@ -1203,23 +1216,16 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
> if (ret)
> return ret;
>
> - irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
> - if (irq < 0) {
> - intio = ADXL345_INT2;
> - irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
> - if (irq < 0)
> - intio = ADXL345_INT_NONE;
> - }
> -
> + intio = adxl345_get_int_line(dev, &irq);
> if (intio != ADXL345_INT_NONE) {
> /*
> - * Any bits set to 0 in the INT map register send their respective
> - * interrupts to the INT1 pin, whereas bits set to 1 send their respective
> - * interrupts to the INT2 pin. The intio shall convert this accordingly.
> + * In the INT map register, bits set to 0 route their
> + * corresponding interrupts to the INT1 pin, while bits set to 1
> + * route them to the INT2 pin. The intio should handle this
> + * mapping accordingly.
> */
> - regval = intio ? 0xff : 0;
> -
> - ret = regmap_write(st->regmap, ADXL345_REG_INT_MAP, regval);
> + ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_MAP,
> + U8_MAX, intio);
> if (ret)
> return ret;
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 2/8] iio: accel: adxl345: simplify reading the FIFO
2025-07-02 23:03 ` [PATCH v11 2/8] iio: accel: adxl345: simplify reading the FIFO Lothar Rubusch
@ 2025-07-06 16:11 ` Jonathan Cameron
0 siblings, 0 replies; 22+ messages in thread
From: Jonathan Cameron @ 2025-07-06 16:11 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, dlechner, nuno.sa, andy, corbet,
linux-iio, linux-kernel, linux-doc, eraretuya
On Wed, 2 Jul 2025 23:03:09 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Bulk FIFO reading can be streamlined by eliminating redundant variables and
> simplifying the process of reading x-, y-, and z-axis measurement sets.
>
> This is a refactoring change with no expected impact on functionality.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Applied.
> ---
> drivers/iio/accel/adxl345_core.c | 7 ++-----
> 1 file changed, 2 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> index 6c437d7a59ed..b7dfd0007aa0 100644
> --- a/drivers/iio/accel/adxl345_core.c
> +++ b/drivers/iio/accel/adxl345_core.c
> @@ -885,15 +885,12 @@ static int adxl345_get_samples(struct adxl345_state *st)
> */
> static int adxl345_fifo_transfer(struct adxl345_state *st, int samples)
> {
> - size_t count;
> int i, ret = 0;
>
> - /* count is the 3x the fifo_buf element size, hence 6B */
> - count = sizeof(st->fifo_buf[0]) * ADXL345_DIRS;
> for (i = 0; i < samples; i++) {
> - /* read 3x 2 byte elements from base address into next fifo_buf position */
> ret = regmap_bulk_read(st->regmap, ADXL345_REG_XYZ_BASE,
> - st->fifo_buf + (i * count / 2), count);
> + st->fifo_buf + (i * ADXL345_DIRS),
> + sizeof(st->fifo_buf[0]) * ADXL345_DIRS);
> if (ret)
> return ret;
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 8/8] docs: iio: describe inactivity and free-fall detection on the ADXL345
2025-07-02 23:03 ` [PATCH v11 8/8] docs: iio: describe inactivity and free-fall detection on the ADXL345 Lothar Rubusch
@ 2025-07-06 16:16 ` Jonathan Cameron
2025-07-20 18:49 ` Lothar Rubusch
0 siblings, 1 reply; 22+ messages in thread
From: Jonathan Cameron @ 2025-07-06 16:16 UTC (permalink / raw)
To: Lothar Rubusch, eraretuya
Cc: lars, Michael.Hennerich, dlechner, nuno.sa, andy, corbet,
linux-iio, linux-kernel, linux-doc
On Wed, 2 Jul 2025 23:03:15 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Describe the inactivity detection additionally using the free-fall
> register. Due to the controversial discussions on the mailing list, this
> section of the documentation will be committed separately to allow for a
> more focused and detailed elaboration of the topic.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> ---
> Documentation/iio/adxl345.rst | 25 +++++++++++++++++++++++++
> 1 file changed, 25 insertions(+)
>
> diff --git a/Documentation/iio/adxl345.rst b/Documentation/iio/adxl345.rst
> index 8ee01b8b87f4..c5525267ea12 100644
> --- a/Documentation/iio/adxl345.rst
> +++ b/Documentation/iio/adxl345.rst
> @@ -150,6 +150,31 @@ functions, so that one follows the other. The auto-sleep function puts the
> sensor into sleep mode when inactivity is detected, reducing power consumption
> to the sub-12.5 Hz rate.
>
> +The inactivity time is configurable between 1 and 255 seconds. In addition to
> +inactivity detection, the sensor also supports free-fall detection, which, from
> +the IIO perspective, is treated as a fall in magnitude across all axes. In
> +sensor terms, free-fall is defined using an inactivity period ranging from 0.000
> +to 1.000 seconds.
> +
> +The driver behaves as follows:
> +* If the configured inactivity period is 1 second or more, the driver uses the
> + sensor's inactivity register. This allows the event to be linked with
> + activity detection, use auto-sleep, and be either AC- or DC-coupled.
> +
> +* If the inactivity period is less than 1 second, the event is treated as plain
> + inactivity or free-fall detection. In this case, auto-sleep and coupling
> + (AC/DC) are not applied.
> +
> +* If an inactivity time of 0 seconds is configured, the driver selects a
> + heuristically determined default period (greater than 1 second) to optimize
> + power consumption. This also uses the inactivity register.
> +
> +Note: It is recommended to use the activity, inactivity, or free-fall registers
> +when operating with an ODR between 12.5 Hz and 400 Hz.
This seems a tiny bit backwards. It is recommend that the activity, inactivity or
free-fall registers are only used when operating with an ODR...
As currently written it seems to be recommending that if you want those sampling
frequencies you should also enable one of these detectors.
Reminds me of the classic London underground sign that said "Dogs must be
carried." which raised the question of how people with out dogs were meant to travel.
Otherwise this new section looks good to me. Thanks,
Jonathan
> According to the
> +datasheet, the recommended free-fall threshold is between 300 mg and 600 mg
> +(register values 0x05 to 0x09), and the suggested free-fall time ranges from
> +100 ms to 350 ms (register values 0x14 to 0x46).
> +
> In DC-coupled mode, the current acceleration magnitude is directly compared to
> the values in the THRESH_ACT and THRESH_INACT registers to determine activity or
> inactivity. In contrast, AC-coupled activity detection uses the acceleration
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 3/8] iio: accel: adxl345: add activity event feature
2025-07-06 16:09 ` Jonathan Cameron
@ 2025-07-20 18:36 ` Lothar Rubusch
2025-07-24 13:43 ` Jonathan Cameron
0 siblings, 1 reply; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-20 18:36 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Andy Shevchenko, lars, Michael.Hennerich, dlechner, nuno.sa, andy,
corbet, linux-iio, linux-kernel, linux-doc, eraretuya
Hi, I appologize for late replying on this topic.
On Sun, Jul 6, 2025 at 6:09 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Thu, 3 Jul 2025 17:24:17 +0300
> Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
>
> > On Wed, Jul 02, 2025 at 11:03:10PM +0000, Lothar Rubusch wrote:
> > > Enable the sensor to detect activity and trigger interrupts accordingly.
> > > Activity events are determined based on a threshold, which is initialized
> > > to a sensible default during probe. This default value is adopted from the
> > > legacy ADXL345 input driver to maintain consistent behavior.
> > >
> > > The combination of activity detection, ODR configuration, and range
> > > settings lays the groundwork for the activity/inactivity hysteresis
> > > mechanism, which will be implemented in a subsequent patch. As such,
> > > portions of this patch prepare switch-case structures to support those
> > > upcoming changes.
> >
> > > #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)
> > > +#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
> >
> > I'm trying to understand the logic behind the placement of the masks and bits.
> > To me it sounds that the above should be rather
> >
> > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > #define ADXL345_TAP_Z_EN BIT(0)
> > #define ADXL345_TAP_Y_EN BIT(1)
> > #define ADXL345_TAP_X_EN BIT(2)
> > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3) // Do we need this at all?
> > #define ADXL345_REG_TAP_SUPPRESS BIT(3) // or actually this? One is enough, no?
> > #define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > #define ADXL345_ACT_Z_EN BIT(4)
> > #define ADXL345_ACT_Y_EN BIT(5)
> > #define ADXL345_ACT_X_EN BIT(6)
> > #define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
> >
> > (Yes, I know that the mess is preexisted, but try to keep some order in the
> > pieces you add here.)
>
> FWIW I fully agree on keeping field definitions and field break up together.
>
> The ACT_MSK is a little odd as thing as then we'd expect there to be bits
> within that. So that FIELD_GET(a, ADXL345_REG_ACT_AXIS_MSK) would return
> a value from a list of things like
> ADXL345_REG_ACT_AXIS_VALUE_A and similar.
>
> So I'd not define that as a mask a tall but just use the
> ACT_XYZ_EN for it as then it's clear you are checking for any of the
> 3 bits being set.
>
The reason is that ADXL345_REG_ACT_AXIS_MSK is used in the evaluation
of the incoming interrupt status register for "activity" events, and
ADXL345_ACT_XYZ_EN is supposed to group the enabled axis, when
enabling the sensor feature "activity" in the enable register. At the
end of the day, using only one of them would work for both, but
there's a semantic difference.
Given this explanation, would you prefer to see a separate
ADXL345_REG_ACT_AXIS_MSK and ADXL345_ACT_XYZ_EN as presented here, or
just one ADXL345_ACT_XYZ_EN covering both cases, i.e. the evaluation
of the interrupt status, and enabling activity axis?
> Jonathan
>
...
Best,
L
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 8/8] docs: iio: describe inactivity and free-fall detection on the ADXL345
2025-07-06 16:16 ` Jonathan Cameron
@ 2025-07-20 18:49 ` Lothar Rubusch
2025-07-24 13:47 ` Jonathan Cameron
0 siblings, 1 reply; 22+ messages in thread
From: Lothar Rubusch @ 2025-07-20 18:49 UTC (permalink / raw)
To: Jonathan Cameron
Cc: eraretuya, lars, Michael.Hennerich, dlechner, nuno.sa, andy,
corbet, linux-iio, linux-kernel, linux-doc
Hi
On Sun, Jul 6, 2025 at 6:16 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Wed, 2 Jul 2025 23:03:15 +0000
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > Describe the inactivity detection additionally using the free-fall
> > register. Due to the controversial discussions on the mailing list, this
> > section of the documentation will be committed separately to allow for a
> > more focused and detailed elaboration of the topic.
> >
> > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > ---
> > Documentation/iio/adxl345.rst | 25 +++++++++++++++++++++++++
> > 1 file changed, 25 insertions(+)
> >
> > diff --git a/Documentation/iio/adxl345.rst b/Documentation/iio/adxl345.rst
> > index 8ee01b8b87f4..c5525267ea12 100644
> > --- a/Documentation/iio/adxl345.rst
> > +++ b/Documentation/iio/adxl345.rst
> > @@ -150,6 +150,31 @@ functions, so that one follows the other. The auto-sleep function puts the
> > sensor into sleep mode when inactivity is detected, reducing power consumption
> > to the sub-12.5 Hz rate.
> >
> > +The inactivity time is configurable between 1 and 255 seconds. In addition to
> > +inactivity detection, the sensor also supports free-fall detection, which, from
> > +the IIO perspective, is treated as a fall in magnitude across all axes. In
> > +sensor terms, free-fall is defined using an inactivity period ranging from 0.000
> > +to 1.000 seconds.
> > +
> > +The driver behaves as follows:
> > +* If the configured inactivity period is 1 second or more, the driver uses the
> > + sensor's inactivity register. This allows the event to be linked with
> > + activity detection, use auto-sleep, and be either AC- or DC-coupled.
> > +
> > +* If the inactivity period is less than 1 second, the event is treated as plain
> > + inactivity or free-fall detection. In this case, auto-sleep and coupling
> > + (AC/DC) are not applied.
> > +
> > +* If an inactivity time of 0 seconds is configured, the driver selects a
> > + heuristically determined default period (greater than 1 second) to optimize
> > + power consumption. This also uses the inactivity register.
> > +
> > +Note: It is recommended to use the activity, inactivity, or free-fall registers
> > +when operating with an ODR between 12.5 Hz and 400 Hz.
>
> This seems a tiny bit backwards. It is recommend that the activity, inactivity or
> free-fall registers are only used when operating with an ODR...
>
Ehm, Doesn't the sensor always have an ODR? The real question is which
ODR should be configured. There are recommendations for specific
features. I may have either misunderstood or misdocumented this part.
> As currently written it seems to be recommending that if you want those sampling
> frequencies you should also enable one of these detectors.
>
Ah, no. The other way around, when someone wants one of events
detected, the recommended frequencies should be used. I'll have a look
at it.
> Reminds me of the classic London underground sign that said "Dogs must be
> carried." which raised the question of how people with out dogs were meant to travel.
>
> Otherwise this new section looks good to me. Thanks,
>
> Jonathan
>...
Best,
Lothar
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 3/8] iio: accel: adxl345: add activity event feature
2025-07-20 18:36 ` Lothar Rubusch
@ 2025-07-24 13:43 ` Jonathan Cameron
0 siblings, 0 replies; 22+ messages in thread
From: Jonathan Cameron @ 2025-07-24 13:43 UTC (permalink / raw)
To: Lothar Rubusch
Cc: Andy Shevchenko, lars, Michael.Hennerich, dlechner, nuno.sa, andy,
corbet, linux-iio, linux-kernel, linux-doc, eraretuya
On Sun, 20 Jul 2025 20:36:09 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Hi, I appologize for late replying on this topic.
>
> On Sun, Jul 6, 2025 at 6:09 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Thu, 3 Jul 2025 17:24:17 +0300
> > Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> >
> > > On Wed, Jul 02, 2025 at 11:03:10PM +0000, Lothar Rubusch wrote:
> > > > Enable the sensor to detect activity and trigger interrupts accordingly.
> > > > Activity events are determined based on a threshold, which is initialized
> > > > to a sensible default during probe. This default value is adopted from the
> > > > legacy ADXL345 input driver to maintain consistent behavior.
> > > >
> > > > The combination of activity detection, ODR configuration, and range
> > > > settings lays the groundwork for the activity/inactivity hysteresis
> > > > mechanism, which will be implemented in a subsequent patch. As such,
> > > > portions of this patch prepare switch-case structures to support those
> > > > upcoming changes.
> > >
> > > > #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)
> > > > +#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
> > >
> > > I'm trying to understand the logic behind the placement of the masks and bits.
> > > To me it sounds that the above should be rather
> > >
> > > #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > #define ADXL345_TAP_Z_EN BIT(0)
> > > #define ADXL345_TAP_Y_EN BIT(1)
> > > #define ADXL345_TAP_X_EN BIT(2)
> > > #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3) // Do we need this at all?
> > > #define ADXL345_REG_TAP_SUPPRESS BIT(3) // or actually this? One is enough, no?
> > > #define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
> > > #define ADXL345_ACT_Z_EN BIT(4)
> > > #define ADXL345_ACT_Y_EN BIT(5)
> > > #define ADXL345_ACT_X_EN BIT(6)
> > > #define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
> > >
> > > (Yes, I know that the mess is preexisted, but try to keep some order in the
> > > pieces you add here.)
> >
> > FWIW I fully agree on keeping field definitions and field break up together.
> >
> > The ACT_MSK is a little odd as thing as then we'd expect there to be bits
> > within that. So that FIELD_GET(a, ADXL345_REG_ACT_AXIS_MSK) would return
> > a value from a list of things like
> > ADXL345_REG_ACT_AXIS_VALUE_A and similar.
> >
> > So I'd not define that as a mask a tall but just use the
> > ACT_XYZ_EN for it as then it's clear you are checking for any of the
> > 3 bits being set.
> >
>
> The reason is that ADXL345_REG_ACT_AXIS_MSK is used in the evaluation
> of the incoming interrupt status register for "activity" events, and
> ADXL345_ACT_XYZ_EN is supposed to group the enabled axis, when
> enabling the sensor feature "activity" in the enable register. At the
> end of the day, using only one of them would work for both, but
> there's a semantic difference.
>
> Given this explanation, would you prefer to see a separate
> ADXL345_REG_ACT_AXIS_MSK and ADXL345_ACT_XYZ_EN as presented here, or
> just one ADXL345_ACT_XYZ_EN covering both cases, i.e. the evaluation
> of the interrupt status, and enabling activity axis?
I think just using the XYZ_EN is clear enough as we are checking for
'any of' those.
>
> > Jonathan
> >
> ...
> Best,
> L
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11 8/8] docs: iio: describe inactivity and free-fall detection on the ADXL345
2025-07-20 18:49 ` Lothar Rubusch
@ 2025-07-24 13:47 ` Jonathan Cameron
0 siblings, 0 replies; 22+ messages in thread
From: Jonathan Cameron @ 2025-07-24 13:47 UTC (permalink / raw)
To: Lothar Rubusch
Cc: eraretuya, lars, Michael.Hennerich, dlechner, nuno.sa, andy,
corbet, linux-iio, linux-kernel, linux-doc
On Sun, 20 Jul 2025 20:49:48 +0200
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Hi
>
> On Sun, Jul 6, 2025 at 6:16 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Wed, 2 Jul 2025 23:03:15 +0000
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > Describe the inactivity detection additionally using the free-fall
> > > register. Due to the controversial discussions on the mailing list, this
> > > section of the documentation will be committed separately to allow for a
> > > more focused and detailed elaboration of the topic.
> > >
> > > Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> > > ---
> > > Documentation/iio/adxl345.rst | 25 +++++++++++++++++++++++++
> > > 1 file changed, 25 insertions(+)
> > >
> > > diff --git a/Documentation/iio/adxl345.rst b/Documentation/iio/adxl345.rst
> > > index 8ee01b8b87f4..c5525267ea12 100644
> > > --- a/Documentation/iio/adxl345.rst
> > > +++ b/Documentation/iio/adxl345.rst
> > > @@ -150,6 +150,31 @@ functions, so that one follows the other. The auto-sleep function puts the
> > > sensor into sleep mode when inactivity is detected, reducing power consumption
> > > to the sub-12.5 Hz rate.
> > >
> > > +The inactivity time is configurable between 1 and 255 seconds. In addition to
> > > +inactivity detection, the sensor also supports free-fall detection, which, from
> > > +the IIO perspective, is treated as a fall in magnitude across all axes. In
> > > +sensor terms, free-fall is defined using an inactivity period ranging from 0.000
> > > +to 1.000 seconds.
> > > +
> > > +The driver behaves as follows:
> > > +* If the configured inactivity period is 1 second or more, the driver uses the
> > > + sensor's inactivity register. This allows the event to be linked with
> > > + activity detection, use auto-sleep, and be either AC- or DC-coupled.
> > > +
> > > +* If the inactivity period is less than 1 second, the event is treated as plain
> > > + inactivity or free-fall detection. In this case, auto-sleep and coupling
> > > + (AC/DC) are not applied.
> > > +
> > > +* If an inactivity time of 0 seconds is configured, the driver selects a
> > > + heuristically determined default period (greater than 1 second) to optimize
> > > + power consumption. This also uses the inactivity register.
> > > +
> > > +Note: It is recommended to use the activity, inactivity, or free-fall registers
> > > +when operating with an ODR between 12.5 Hz and 400 Hz.
> >
> > This seems a tiny bit backwards. It is recommend that the activity, inactivity or
> > free-fall registers are only used when operating with an ODR...
> >
>
> Ehm, Doesn't the sensor always have an ODR?
I was lazy. The ODR... was meant to be ODR between 12.5 Hz and 400 Hz (so original text
for that bit).
> The real question is which
> ODR should be configured. There are recommendations for specific
> features. I may have either misunderstood or misdocumented this part.
>
> > As currently written it seems to be recommending that if you want those sampling
> > frequencies you should also enable one of these detectors.
> >
>
> Ah, no. The other way around, when someone wants one of events
> detected, the recommended frequencies should be used. I'll have a look
> at it.
Sounds like you got my rubbish explanation.
>
> > Reminds me of the classic London underground sign that said "Dogs must be
> > carried." which raised the question of how people with out dogs were meant to travel.
> >
> > Otherwise this new section looks good to me. Thanks,
> >
> > Jonathan
> >...
>
> Best,
> Lothar
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2025-07-24 13:47 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-02 23:03 [PATCH v11 0/8] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 1/8] iio: accel: adxl345: simplify interrupt mapping Lothar Rubusch
2025-07-06 16:10 ` Jonathan Cameron
2025-07-02 23:03 ` [PATCH v11 2/8] iio: accel: adxl345: simplify reading the FIFO Lothar Rubusch
2025-07-06 16:11 ` Jonathan Cameron
2025-07-02 23:03 ` [PATCH v11 3/8] iio: accel: adxl345: add activity event feature Lothar Rubusch
2025-07-03 14:24 ` Andy Shevchenko
2025-07-06 16:09 ` Jonathan Cameron
2025-07-20 18:36 ` Lothar Rubusch
2025-07-24 13:43 ` Jonathan Cameron
2025-07-02 23:03 ` [PATCH v11 4/8] iio: accel: adxl345: add inactivity feature Lothar Rubusch
2025-07-03 14:26 ` Andy Shevchenko
2025-07-03 14:59 ` Lothar Rubusch
2025-07-03 15:45 ` Andy Shevchenko
2025-07-06 12:08 ` Jonathan Cameron
2025-07-02 23:03 ` [PATCH v11 5/8] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 6/8] iio: accel: adxl345: extend inactivity time for less than 1s Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 7/8] docs: iio: add documentation for adxl345 driver Lothar Rubusch
2025-07-02 23:03 ` [PATCH v11 8/8] docs: iio: describe inactivity and free-fall detection on the ADXL345 Lothar Rubusch
2025-07-06 16:16 ` Jonathan Cameron
2025-07-20 18:49 ` Lothar Rubusch
2025-07-24 13:47 ` Jonathan Cameron
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).