[PATCH v4 10/11] iio: accel: adxl313: add AC coupled activity/inactivity events

From: Lothar Rubusch
Date: Sun Jun 01 2025 - 13:24:25 EST


Add AC coupling activity and inactivity as MAG_ADAPTIVE events. This adds
up an additional set of threshold and period handles, verifies matching
disabling functionality and extends setting the link bit to complementary
event configurations.

This means, e.g. either ACTIVITY or ACTIVITY_AC can be enabled. The most
recent set will remain configured. Disabling ACTIVITY where ACTIVITY_AC was
enabled is ignored, since it does not match (should be disabling
ACTIVITY_AC). When INACTIVITY or INACTIVITY_AC is also enabled, the link
bit will be set. Note, having the link bit and auto-sleep in place activity
and inactivity indicate the power save state change and thus will only be
triggered once a state transition occurs. Since there is a separate AC bit
for ACTIVITY and for INACTIVITY, events can be linked independently from
each other i.e. ACTIVITY can be linked to INACTIVITY_AC for instance.

When one of both is disabled, the link bit will be removed. Hence, the
remaining event will not indicate a plain state change anymore, but occur
as a periodically triggered inactivity event or for each activity event
above the threshold.

Signed-off-by: Lothar Rubusch <l.rubusch@xxxxxxxxx>
---
drivers/iio/accel/adxl313_core.c | 414 +++++++++++++++++++++++++------
1 file changed, 333 insertions(+), 81 deletions(-)

diff --git a/drivers/iio/accel/adxl313_core.c b/drivers/iio/accel/adxl313_core.c
index 1598562a38e2..9a0905e30de3 100644
--- a/drivers/iio/accel/adxl313_core.c
+++ b/drivers/iio/accel/adxl313_core.c
@@ -30,20 +30,38 @@
#define ADXL313_ACT_XYZ_EN GENMASK(6, 4)
#define ADXL313_INACT_XYZ_EN GENMASK(2, 0)

+#define ADXL313_REG_ACT_ACDC_MSK BIT(7)
+#define ADXL313_REG_INACT_ACDC_MSK BIT(3)
+#define ADXL313_COUPLING_DC 0
+#define ADXL313_COUPLING_AC 1
+
/* activity/inactivity */
enum adxl313_activity_type {
ADXL313_ACTIVITY,
ADXL313_INACTIVITY,
+ ADXL313_ACTIVITY_AC,
+ ADXL313_INACTIVITY_AC,
};

static const unsigned int adxl313_act_int_reg[] = {
[ADXL313_ACTIVITY] = ADXL313_INT_ACTIVITY,
[ADXL313_INACTIVITY] = ADXL313_INT_INACTIVITY,
+ [ADXL313_ACTIVITY_AC] = ADXL313_INT_ACTIVITY,
+ [ADXL313_INACTIVITY_AC] = ADXL313_INT_INACTIVITY,
};

static const unsigned int adxl313_act_thresh_reg[] = {
[ADXL313_ACTIVITY] = ADXL313_REG_THRESH_ACT,
[ADXL313_INACTIVITY] = ADXL313_REG_THRESH_INACT,
+ [ADXL313_ACTIVITY_AC] = ADXL313_REG_THRESH_ACT,
+ [ADXL313_INACTIVITY_AC] = ADXL313_REG_THRESH_INACT,
+};
+
+static const unsigned int adxl313_act_acdc_msk[] = {
+ [ADXL313_ACTIVITY] = ADXL313_REG_ACT_ACDC_MSK,
+ [ADXL313_INACTIVITY] = ADXL313_REG_INACT_ACDC_MSK,
+ [ADXL313_ACTIVITY_AC] = ADXL313_REG_ACT_ACDC_MSK,
+ [ADXL313_INACTIVITY_AC] = ADXL313_REG_INACT_ACDC_MSK,
};

static const struct regmap_range adxl312_readable_reg_range[] = {
@@ -255,6 +273,13 @@ static const struct iio_event_spec adxl313_activity_events[] = {
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
},
+ {
+ /* activity, AC bit set */
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+ },
};

static const struct iio_event_spec adxl313_inactivity_events[] = {
@@ -265,6 +290,14 @@ static const struct iio_event_spec adxl313_inactivity_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),
+ },
};

enum adxl313_chans {
@@ -362,11 +395,58 @@ static int adxl313_set_inact_time_s(struct adxl313_data *data,
return regmap_write(data->regmap, ADXL313_REG_TIME_INACT, val);
}

+/**
+ * adxl313_is_act_inact_ac() - Check if AC coupling is enabled.
+ *
+ * @data: The device data.
+ * @type: The activity or inactivity type.
+ *
+ * Provide a type of activity or inactivity, combined with either AC coupling
+ * set, or default to DC coupling. This function verifies, if the combination is
+ * currently enabled or not.
+ *
+ * Return if the provided activity type has AC coupling enabled or a negative
+ * error value.
+ */
+static int adxl313_is_act_inact_ac(struct adxl313_data *data,
+ enum adxl313_activity_type type)
+{
+ unsigned int regval;
+ bool coupling;
+ int ret;
+
+ ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
+ if (ret)
+ return ret;
+
+ coupling = adxl313_act_acdc_msk[type] & regval;
+
+ if (type == ADXL313_ACTIVITY || type == ADXL313_INACTIVITY)
+ return coupling == ADXL313_COUPLING_DC;
+ else
+ return coupling == ADXL313_COUPLING_AC;
+}
+
+static int adxl313_set_act_inact_ac(struct adxl313_data *data,
+ enum adxl313_activity_type type)
+{
+ unsigned int coupling;
+
+ if (type == ADXL313_ACTIVITY_AC || type == ADXL313_INACTIVITY_AC)
+ coupling = ADXL313_COUPLING_AC;
+ else
+ coupling = ADXL313_COUPLING_DC;
+
+ return regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL,
+ adxl313_act_acdc_msk[type], coupling);
+}
+
static int adxl313_is_act_inact_en(struct adxl313_data *data,
enum adxl313_activity_type type)
{
unsigned int axis_ctrl;
unsigned int regval;
+ int coupling;
int axis_en, int_en, ret;

ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &axis_ctrl);
@@ -374,7 +454,7 @@ static int adxl313_is_act_inact_en(struct adxl313_data *data,
return ret;

/* Check if axis for activity are enabled */
- if (type == ADXL313_ACTIVITY)
+ if (type == ADXL313_ACTIVITY || type == ADXL313_ACTIVITY_AC)
axis_en = FIELD_GET(ADXL313_ACT_XYZ_EN, axis_ctrl);
else
axis_en = FIELD_GET(ADXL313_INACT_XYZ_EN, axis_ctrl);
@@ -386,7 +466,12 @@ static int adxl313_is_act_inact_en(struct adxl313_data *data,

int_en = adxl313_act_int_reg[type] & regval;

- return axis_en && int_en;
+ /* Return true if configured coupling matches provided type */
+ coupling = adxl313_is_act_inact_ac(data, type);
+ if (coupling < 0)
+ return coupling;
+
+ return axis_en && int_en && coupling;
}

static int adxl313_set_act_inact_en(struct adxl313_data *data,
@@ -396,15 +481,26 @@ static int adxl313_set_act_inact_en(struct adxl313_data *data,
unsigned int axis_ctrl;
unsigned int threshold;
unsigned int inact_time_s;
- int act_en, inact_en;
- bool en;
+ int act_en, inact_en, act_ac_en, inact_ac_en;
+ bool en, act_inact_ac;
int ret;

- if (type == ADXL313_ACTIVITY)
+ /*
+ * In case of turning off, assure turning off a correspondent coupling
+ * event. In case of not matching coupling, simply return.
+ */
+ if (!cmd_en) {
+ /* Expected positive true if coupling matches coupling type */
+ if (adxl313_is_act_inact_ac(data, type) <= 0)
+ return 0;
+ }
+
+ if (type == ADXL313_ACTIVITY || type == ADXL313_ACTIVITY_AC)
axis_ctrl = ADXL313_ACT_XYZ_EN;
else
axis_ctrl = ADXL313_INACT_XYZ_EN;

+ /* Start modifying configuration registers */
ret = adxl313_set_measure_en(data, false);
if (ret)
return ret;
@@ -414,12 +510,16 @@ static int adxl313_set_act_inact_en(struct adxl313_data *data,
if (ret)
return ret;

+ act_inact_ac = type == ADXL313_ACTIVITY_AC || ADXL313_INACTIVITY_AC;
+ ret = regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL,
+ adxl313_act_acdc_msk[type], act_inact_ac);
+
ret = regmap_read(data->regmap, adxl313_act_thresh_reg[type], &threshold);
if (ret)
return ret;

en = cmd_en && threshold;
- if (type == ADXL313_INACTIVITY) {
+ if (type == ADXL313_INACTIVITY || type == ADXL313_INACTIVITY_AC) {
ret = regmap_read(data->regmap, ADXL313_REG_TIME_INACT, &inact_time_s);
if (ret)
return ret;
@@ -427,6 +527,10 @@ static int adxl313_set_act_inact_en(struct adxl313_data *data,
en = en && inact_time_s;
}

+ ret = adxl313_set_act_inact_ac(data, type);
+ if (ret)
+ return ret;
+
ret = regmap_assign_bits(data->regmap, ADXL313_REG_INT_ENABLE,
adxl313_act_int_reg[type], en);
if (ret)
@@ -439,10 +543,22 @@ static int adxl313_set_act_inact_en(struct adxl313_data *data,
if (act_en < 0)
return act_en;

+ act_ac_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY_AC);
+ if (act_ac_en < 0)
+ return act_ac_en;
+
+ act_en = act_en || act_ac_en;
+
inact_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY);
if (inact_en < 0)
return inact_en;

+ inact_ac_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY_AC);
+ if (inact_ac_en < 0)
+ return inact_ac_en;
+
+ inact_en = inact_en || inact_ac_en;
+
en = en && act_en && inact_en;

ret = regmap_assign_bits(data->regmap, ADXL313_REG_POWER_CTL,
@@ -534,14 +650,25 @@ static int adxl313_read_event_config(struct iio_dev *indio_dev,
{
struct adxl313_data *data = iio_priv(indio_dev);

- if (type != IIO_EV_TYPE_MAG)
- return -EINVAL;
-
- switch (dir) {
- case IIO_EV_DIR_RISING:
- return adxl313_is_act_inact_en(data, ADXL313_ACTIVITY);
- case IIO_EV_DIR_FALLING:
- return adxl313_is_act_inact_en(data, ADXL313_INACTIVITY);
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl313_is_act_inact_en(data, ADXL313_ACTIVITY);
+ case IIO_EV_DIR_FALLING:
+ return adxl313_is_act_inact_en(data, ADXL313_INACTIVITY);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl313_is_act_inact_en(data, ADXL313_ACTIVITY_AC);
+ case IIO_EV_DIR_FALLING:
+ return adxl313_is_act_inact_en(data, ADXL313_INACTIVITY_AC);
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -555,14 +682,33 @@ static int adxl313_write_event_config(struct iio_dev *indio_dev,
{
struct adxl313_data *data = iio_priv(indio_dev);

- if (type != IIO_EV_TYPE_MAG)
- return -EINVAL;
-
- switch (dir) {
- case IIO_EV_DIR_RISING:
- return adxl313_set_act_inact_en(data, ADXL313_ACTIVITY, state);
- case IIO_EV_DIR_FALLING:
- return adxl313_set_act_inact_en(data, ADXL313_INACTIVITY, state);
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl313_set_act_inact_en(data,
+ ADXL313_ACTIVITY,
+ state);
+ case IIO_EV_DIR_FALLING:
+ return adxl313_set_act_inact_en(data,
+ ADXL313_INACTIVITY,
+ state);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl313_set_act_inact_en(data,
+ ADXL313_ACTIVITY_AC,
+ state);
+ case IIO_EV_DIR_FALLING:
+ return adxl313_set_act_inact_en(data,
+ ADXL313_INACTIVITY_AC,
+ state);
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -583,41 +729,79 @@ static int adxl313_read_event_value(struct iio_dev *indio_dev,

/* Measurement stays enabled, reading from regmap cache */

- if (type != IIO_EV_TYPE_MAG)
- return -EINVAL;
-
- switch (info) {
- case IIO_EV_INFO_VALUE:
- switch (dir) {
- case IIO_EV_DIR_RISING:
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(data->regmap,
+ adxl313_act_thresh_reg[ADXL313_ACTIVITY],
+ &act_threshold);
+ if (ret)
+ return ret;
+ *val = act_threshold * 15625;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(data->regmap,
+ adxl313_act_thresh_reg[ADXL313_INACTIVITY],
+ &inact_threshold);
+ if (ret)
+ return ret;
+ *val = inact_threshold * 15625;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
ret = regmap_read(data->regmap,
- adxl313_act_thresh_reg[ADXL313_ACTIVITY],
- &act_threshold);
+ ADXL313_REG_TIME_INACT,
+ &inact_time_s);
if (ret)
return ret;
- *val = act_threshold * 15625;
- *val2 = MICRO;
- return IIO_VAL_FRACTIONAL;
- case IIO_EV_DIR_FALLING:
+ *val = inact_time_s;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(data->regmap,
+ adxl313_act_thresh_reg[ADXL313_ACTIVITY_AC],
+ &act_threshold);
+ if (ret)
+ return ret;
+ *val = act_threshold * 15625;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(data->regmap,
+ adxl313_act_thresh_reg[ADXL313_INACTIVITY_AC],
+ &inact_threshold);
+ if (ret)
+ return ret;
+ *val = inact_threshold * 15625;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
ret = regmap_read(data->regmap,
- adxl313_act_thresh_reg[ADXL313_INACTIVITY],
- &inact_threshold);
+ ADXL313_REG_TIME_INACT,
+ &inact_time_s);
if (ret)
return ret;
- *val = inact_threshold * 15625;
- *val2 = MICRO;
- return IIO_VAL_FRACTIONAL;
+ *val = inact_time_s;
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
- case IIO_EV_INFO_PERIOD:
- ret = regmap_read(data->regmap,
- ADXL313_REG_TIME_INACT,
- &inact_time_s);
- if (ret)
- return ret;
- *val = inact_time_s;
- return IIO_VAL_INT;
default:
return -EINVAL;
}
@@ -638,36 +822,69 @@ static int adxl313_write_event_value(struct iio_dev *indio_dev,
if (ret)
return ret;

- if (type != IIO_EV_TYPE_MAG)
- return -EINVAL;
-
- switch (info) {
- case IIO_EV_INFO_VALUE:
- /* Scale factor 15.625 mg/LSB */
- regval = DIV_ROUND_CLOSEST(MICRO * val + val2, 15625);
- switch (dir) {
- case IIO_EV_DIR_RISING:
- ret = regmap_write(data->regmap,
- adxl313_act_thresh_reg[ADXL313_ACTIVITY],
- regval);
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /* Scale factor 15.625 mg/LSB */
+ regval = DIV_ROUND_CLOSEST(MICRO * val + val2, 15625);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_write(data->regmap,
+ adxl313_act_thresh_reg[ADXL313_ACTIVITY],
+ regval);
+ if (ret)
+ return ret;
+ return adxl313_set_measure_en(data, true);
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_write(data->regmap,
+ adxl313_act_thresh_reg[ADXL313_INACTIVITY],
+ regval);
+ if (ret)
+ return ret;
+ return adxl313_set_measure_en(data, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ ret = adxl313_set_inact_time_s(data, val);
if (ret)
return ret;
return adxl313_set_measure_en(data, true);
- case IIO_EV_DIR_FALLING:
- ret = regmap_write(data->regmap,
- adxl313_act_thresh_reg[ADXL313_INACTIVITY],
- regval);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /* Scale factor 15.625 mg/LSB */
+ regval = DIV_ROUND_CLOSEST(MICRO * val + val2, 15625);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_write(data->regmap,
+ adxl313_act_thresh_reg[ADXL313_ACTIVITY_AC],
+ regval);
+ if (ret)
+ return ret;
+ return adxl313_set_measure_en(data, true);
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_write(data->regmap,
+ adxl313_act_thresh_reg[ADXL313_INACTIVITY_AC],
+ regval);
+ if (ret)
+ return ret;
+ return adxl313_set_measure_en(data, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ ret = adxl313_set_inact_time_s(data, val);
if (ret)
return ret;
return adxl313_set_measure_en(data, true);
default:
return -EINVAL;
}
- case IIO_EV_INFO_PERIOD:
- ret = adxl313_set_inact_time_s(data, val);
- if (ret)
- return ret;
- return adxl313_set_measure_en(data, true);
default:
return -EINVAL;
}
@@ -807,29 +1024,64 @@ static int adxl313_push_event(struct iio_dev *indio_dev, int int_stat)
{
s64 ts = iio_get_time_ns(indio_dev);
struct adxl313_data *data = iio_priv(indio_dev);
+ unsigned int regval;
int samples;
int ret = -ENOENT;

if (FIELD_GET(ADXL313_INT_ACTIVITY, int_stat)) {
- ret = iio_push_event(indio_dev,
- IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
- IIO_MOD_X_OR_Y_OR_Z,
- IIO_EV_TYPE_MAG,
- IIO_EV_DIR_RISING),
- ts);
+ ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
if (ret)
return ret;
+
+ if (FIELD_GET(ADXL313_REG_ACT_ACDC_MSK, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_MAG_ADAPTIVE,
+ IIO_EV_DIR_RISING),
+ ts);
+ if (ret)
+ return ret;
+ } else {
+ /* DC coupled, relying on THRESH */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+ if (ret)
+ return ret;
+ }
}

if (FIELD_GET(ADXL313_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(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
if (ret)
return ret;
+
+ if (FIELD_GET(ADXL313_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);
+ if (ret)
+ return ret;
+ } 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;
+ }
}

if (FIELD_GET(ADXL313_INT_WATERMARK, int_stat)) {
--
2.39.5