iio: accel: adxl345: add single tap feature
authorLothar Rubusch <l.rubusch@gmail.com>
Mon, 14 Apr 2025 18:42:36 +0000 (18:42 +0000)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Wed, 21 May 2025 13:20:27 +0000 (14:20 +0100)
Add the single tap feature with a threshold in 62.5mg/LSB points and a
scaled duration in us. Keep singletap threshold in regmap cache but
the scaled value of duration in us as member variable.

Both use IIO channels for individual enable of the x/y/z axis. Initializes
threshold and duration with reasonable content. When an interrupt is
caught it will be pushed to the according IIO channel.

Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Link: https://patch.msgid.link/20250414184245.100280-3-l.rubusch@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/accel/adxl345_core.c

index 7821491..2cda607 100644 (file)
@@ -8,6 +8,7 @@
  */
 
 #include <linux/bitfield.h>
+#include <linux/bitops.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/property.h>
@@ -17,6 +18,7 @@
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
 #include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
 #include <linux/iio/kfifo_buf.h>
 
 #include "adxl345.h"
 #define ADXL345_INT1                   0
 #define ADXL345_INT2                   1
 
+#define ADXL345_REG_TAP_AXIS_MSK       GENMASK(2, 0)
+
+#define ADXL345_TAP_Z_EN               BIT(0)
+#define ADXL345_TAP_Y_EN               BIT(1)
+#define ADXL345_TAP_X_EN               BIT(2)
+
+/* single/double tap */
+enum adxl345_tap_type {
+       ADXL345_SINGLE_TAP,
+};
+
+static const unsigned int adxl345_tap_int_reg[] = {
+       [ADXL345_SINGLE_TAP] = ADXL345_INT_SINGLE_TAP,
+};
+
+enum adxl345_tap_time_type {
+       ADXL345_TAP_TIME_DUR,
+};
+
+static const unsigned int adxl345_tap_time_reg[] = {
+       [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
+};
+
 struct adxl345_state {
        const struct adxl345_chip_info *info;
        struct regmap *regmap;
@@ -38,9 +63,23 @@ struct adxl345_state {
        int irq;
        u8 watermark;
        u8 fifo_mode;
+
+       u32 tap_duration_us;
+
        __le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
 };
 
+static struct iio_event_spec adxl345_events[] = {
+       {
+               /* single tap */
+               .type = IIO_EV_TYPE_GESTURE,
+               .dir = IIO_EV_DIR_SINGLETAP,
+               .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+               .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+                       BIT(IIO_EV_INFO_TIMEOUT),
+       },
+};
+
 #define ADXL345_CHANNEL(index, reg, axis) {                                    \
        .type = IIO_ACCEL,                                              \
        .modified = 1,                                                  \
@@ -57,6 +96,8 @@ struct adxl345_state {
                .storagebits = 16,                      \
                .endianness = IIO_LE,                   \
        },                                              \
+       .event_spec = adxl345_events,                   \
+       .num_event_specs = ARRAY_SIZE(adxl345_events),  \
 }
 
 enum adxl345_chans {
@@ -113,6 +154,157 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
        return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
 }
 
+/* tap */
+
+static int _adxl345_set_tap_int(struct adxl345_state *st,
+                               enum adxl345_tap_type type, bool state)
+{
+       unsigned int int_map = 0x00;
+       unsigned int tap_threshold;
+       bool axis_valid;
+       bool singletap_args_valid = false;
+       bool en = false;
+       u32 axis_ctrl;
+       int ret;
+
+       ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+       if (ret)
+               return ret;
+
+       axis_valid = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) > 0;
+
+       ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP, &tap_threshold);
+       if (ret)
+               return ret;
+
+       /*
+        * Note: A value of 0 for threshold and/or dur may result in undesirable
+        *       behavior if single tap/double tap interrupts are enabled.
+        */
+       singletap_args_valid = tap_threshold > 0 && st->tap_duration_us > 0;
+
+       if (type == ADXL345_SINGLE_TAP)
+               en = axis_valid && singletap_args_valid;
+
+       if (state && en)
+               int_map |= adxl345_tap_int_reg[type];
+
+       return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+                                 adxl345_tap_int_reg[type], int_map);
+}
+
+static int adxl345_is_tap_en(struct adxl345_state *st,
+                            enum iio_modifier axis,
+                            enum adxl345_tap_type type, bool *en)
+{
+       unsigned int regval;
+       u32 axis_ctrl;
+       int ret;
+
+       ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+       if (ret)
+               return ret;
+
+       /* Verify if axis is enabled for the tap detection. */
+       switch (axis) {
+       case IIO_MOD_X:
+               *en = FIELD_GET(ADXL345_TAP_X_EN, axis_ctrl);
+               break;
+       case IIO_MOD_Y:
+               *en = FIELD_GET(ADXL345_TAP_Y_EN, axis_ctrl);
+               break;
+       case IIO_MOD_Z:
+               *en = FIELD_GET(ADXL345_TAP_Z_EN, axis_ctrl);
+               break;
+       default:
+               *en = false;
+               return -EINVAL;
+       }
+
+       if (*en) {
+               /*
+                * If axis allow for tap detection, verify if the interrupt is
+                * enabled for tap detection.
+                */
+               ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, &regval);
+               if (ret)
+                       return ret;
+
+               *en = adxl345_tap_int_reg[type] & regval;
+       }
+
+       return 0;
+}
+
+static int adxl345_set_singletap_en(struct adxl345_state *st,
+                                   enum iio_modifier axis, bool en)
+{
+       int ret;
+       u32 axis_ctrl;
+
+       switch (axis) {
+       case IIO_MOD_X:
+               axis_ctrl = ADXL345_TAP_X_EN;
+               break;
+       case IIO_MOD_Y:
+               axis_ctrl = ADXL345_TAP_Y_EN;
+               break;
+       case IIO_MOD_Z:
+               axis_ctrl = ADXL345_TAP_Z_EN;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (en)
+               ret = regmap_set_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+                                     axis_ctrl);
+       else
+               ret = regmap_clear_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+                                       axis_ctrl);
+       if (ret)
+               return ret;
+
+       return _adxl345_set_tap_int(st, ADXL345_SINGLE_TAP, en);
+}
+
+static int _adxl345_set_tap_time(struct adxl345_state *st,
+                                enum adxl345_tap_time_type type, u32 val_us)
+{
+       unsigned int regval;
+
+       switch (type) {
+       case ADXL345_TAP_TIME_DUR:
+               st->tap_duration_us = val_us;
+               break;
+       }
+
+       /*
+        * The scale factor is 1250us / LSB for tap_window_us and tap_latent_us.
+        * For tap_duration_us the scale factor is 625us / LSB.
+        */
+       if (type == ADXL345_TAP_TIME_DUR)
+               regval = DIV_ROUND_CLOSEST(val_us, 625);
+       else
+               regval = DIV_ROUND_CLOSEST(val_us, 1250);
+
+       return regmap_write(st->regmap, adxl345_tap_time_reg[type], regval);
+}
+
+static int adxl345_set_tap_duration(struct adxl345_state *st, u32 val_int,
+                                   u32 val_fract_us)
+{
+       /*
+        * Max value is 255 * 625 us = 0.159375 seconds
+        *
+        * Note: the scaling is similar to the scaling in the ADXL380
+        */
+       if (val_int || val_fract_us > 159375)
+               return -EINVAL;
+
+       return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_DUR, val_fract_us);
+}
+
 static int adxl345_read_raw(struct iio_dev *indio_dev,
                            struct iio_chan_spec const *chan,
                            int *val, int *val2, long mask)
@@ -198,6 +390,131 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
        return -EINVAL;
 }
 
+static int adxl345_read_event_config(struct iio_dev *indio_dev,
+                                    const struct iio_chan_spec *chan,
+                                    enum iio_event_type type,
+                                    enum iio_event_direction dir)
+{
+       struct adxl345_state *st = iio_priv(indio_dev);
+       bool int_en;
+       int ret;
+
+       switch (type) {
+       case IIO_EV_TYPE_GESTURE:
+               switch (dir) {
+               case IIO_EV_DIR_SINGLETAP:
+                       ret = adxl345_is_tap_en(st, chan->channel2,
+                                               ADXL345_SINGLE_TAP, &int_en);
+                       if (ret)
+                               return ret;
+                       return int_en;
+               default:
+                       return -EINVAL;
+               }
+       default:
+               return -EINVAL;
+       }
+}
+
+static int adxl345_write_event_config(struct iio_dev *indio_dev,
+                                     const struct iio_chan_spec *chan,
+                                     enum iio_event_type type,
+                                     enum iio_event_direction dir,
+                                     bool state)
+{
+       struct adxl345_state *st = iio_priv(indio_dev);
+
+       switch (type) {
+       case IIO_EV_TYPE_GESTURE:
+               switch (dir) {
+               case IIO_EV_DIR_SINGLETAP:
+                       return adxl345_set_singletap_en(st, chan->channel2, state);
+               default:
+                       return -EINVAL;
+               }
+       default:
+               return -EINVAL;
+       }
+}
+
+static int adxl345_read_event_value(struct iio_dev *indio_dev,
+                                   const struct iio_chan_spec *chan,
+                                   enum iio_event_type type,
+                                   enum iio_event_direction dir,
+                                   enum iio_event_info info,
+                                   int *val, int *val2)
+{
+       struct adxl345_state *st = iio_priv(indio_dev);
+       unsigned int tap_threshold;
+       int ret;
+
+       switch (type) {
+       case IIO_EV_TYPE_GESTURE:
+               switch (info) {
+               case IIO_EV_INFO_VALUE:
+                       /*
+                        * The scale factor would be 62.5mg/LSB (i.e. 0xFF = 16g) but
+                        * not applied here. In context of this general purpose sensor,
+                        * what imports is rather signal intensity than the absolute
+                        * measured g value.
+                        */
+                       ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP,
+                                         &tap_threshold);
+                       if (ret)
+                               return ret;
+                       *val = sign_extend32(tap_threshold, 7);
+                       return IIO_VAL_INT;
+               case IIO_EV_INFO_TIMEOUT:
+                       *val = st->tap_duration_us;
+                       *val2 = 1000000;
+                       return IIO_VAL_FRACTIONAL;
+               default:
+                       return -EINVAL;
+               }
+       default:
+               return -EINVAL;
+       }
+}
+
+static int adxl345_write_event_value(struct iio_dev *indio_dev,
+                                    const struct iio_chan_spec *chan,
+                                    enum iio_event_type type,
+                                    enum iio_event_direction dir,
+                                    enum iio_event_info info,
+                                    int val, int val2)
+{
+       struct adxl345_state *st = iio_priv(indio_dev);
+       int ret;
+
+       ret = adxl345_set_measure_en(st, false);
+       if (ret)
+               return ret;
+
+       switch (type) {
+       case IIO_EV_TYPE_GESTURE:
+               switch (info) {
+               case IIO_EV_INFO_VALUE:
+                       ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP,
+                                          min(val, 0xFF));
+                       if (ret)
+                               return ret;
+                       break;
+               case IIO_EV_INFO_TIMEOUT:
+                       ret = adxl345_set_tap_duration(st, val, val2);
+                       if (ret)
+                               return ret;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return adxl345_set_measure_en(st, true);
+}
+
 static int adxl345_reg_access(struct iio_dev *indio_dev, unsigned int reg,
                              unsigned int writeval, unsigned int *readval)
 {
@@ -416,10 +733,23 @@ static int adxl345_fifo_push(struct iio_dev *indio_dev,
        return 0;
 }
 
-static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
+static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
+                             enum iio_modifier tap_dir)
 {
+       s64 ts = iio_get_time_ns(indio_dev);
        struct adxl345_state *st = iio_priv(indio_dev);
        int samples;
+       int ret = -ENOENT;
+
+       if (FIELD_GET(ADXL345_INT_SINGLE_TAP, int_stat)) {
+               ret = iio_push_event(indio_dev,
+                                    IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, tap_dir,
+                                                       IIO_EV_TYPE_GESTURE,
+                                                       IIO_EV_DIR_SINGLETAP),
+                                    ts);
+               if (ret)
+                       return ret;
+       }
 
        if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
                samples = adxl345_get_samples(st);
@@ -428,9 +758,11 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
 
                if (adxl345_fifo_push(indio_dev, samples) < 0)
                        return -EINVAL;
+
+               ret = 0;
        }
 
-       return 0;
+       return ret;
 }
 
 /**
@@ -444,12 +776,33 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
 {
        struct iio_dev *indio_dev = p;
        struct adxl345_state *st = iio_priv(indio_dev);
+       unsigned int regval;
+       enum iio_modifier tap_dir = IIO_NO_MOD;
+       u32 axis_ctrl;
        int int_stat;
+       int ret;
+
+       ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+       if (ret)
+               return IRQ_NONE;
+
+       if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl)) {
+               ret = regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, &regval);
+               if (ret)
+                       return IRQ_NONE;
+
+               if (FIELD_GET(ADXL345_TAP_Z_EN, regval))
+                       tap_dir = IIO_MOD_Z;
+               else if (FIELD_GET(ADXL345_TAP_Y_EN, regval))
+                       tap_dir = IIO_MOD_Y;
+               else if (FIELD_GET(ADXL345_TAP_X_EN, regval))
+                       tap_dir = IIO_MOD_X;
+       }
 
        if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat))
                return IRQ_NONE;
 
-       if (adxl345_push_event(indio_dev, int_stat))
+       if (adxl345_push_event(indio_dev, int_stat, tap_dir))
                goto err;
 
        if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat))
@@ -468,6 +821,10 @@ static const struct iio_info adxl345_info = {
        .read_raw       = adxl345_read_raw,
        .write_raw      = adxl345_write_raw,
        .write_raw_get_fmt      = adxl345_write_raw_get_fmt,
+       .read_event_config = adxl345_read_event_config,
+       .write_event_config = adxl345_write_event_config,
+       .read_event_value = adxl345_read_event_value,
+       .write_event_value = adxl345_write_event_value,
        .debugfs_reg_access = &adxl345_reg_access,
        .hwfifo_set_watermark = adxl345_set_watermark,
 };
@@ -501,6 +858,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
                                         ADXL345_DATA_FORMAT_JUSTIFY |
                                         ADXL345_DATA_FORMAT_FULL_RES |
                                         ADXL345_DATA_FORMAT_SELF_TEST);
+       unsigned int tap_threshold;
        int ret;
 
        indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
@@ -514,6 +872,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
                return -ENODEV;
        st->fifo_delay = fifo_delay_default;
 
+       /* Init with reasonable values */
+       tap_threshold = 48;                     /*   48 [0x30] -> ~3g     */
+       st->tap_duration_us = 16;               /*   16 [0x10] -> .010    */
+
        indio_dev->name = st->info->name;
        indio_dev->info = &adxl345_info;
        indio_dev->modes = INDIO_DIRECT_MODE;
@@ -586,6 +948,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
                if (ret)
                        return ret;
 
+               ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold);
+               if (ret)
+                       return ret;
+
                /* FIFO_STREAM mode is going to be activated later */
                ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &adxl345_buffer_ops);
                if (ret)