iio: accel: adxl345: add single tap feature

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>
This commit is contained in:
Lothar Rubusch
2025-04-14 18:42:36 +00:00
committed by Jonathan Cameron
parent 5aec2b6e19
commit 7478933f03

View 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"
@@ -31,6 +33,29 @@
#define ADXL345_INT1 0
#define ADXL345_INT2 1
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
#define ADXL345_TAP_Z_EN BIT(0)
#define ADXL345_TAP_Y_EN BIT(1)
#define ADXL345_TAP_X_EN BIT(2)
/* single/double tap */
enum adxl345_tap_type {
ADXL345_SINGLE_TAP,
};
static const unsigned int adxl345_tap_int_reg[] = {
[ADXL345_SINGLE_TAP] = ADXL345_INT_SINGLE_TAP,
};
enum adxl345_tap_time_type {
ADXL345_TAP_TIME_DUR,
};
static const unsigned int adxl345_tap_time_reg[] = {
[ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
};
struct adxl345_state {
const struct adxl345_chip_info *info;
struct regmap *regmap;
@@ -38,9 +63,23 @@ struct adxl345_state {
int irq;
u8 watermark;
u8 fifo_mode;
u32 tap_duration_us;
__le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
};
static struct iio_event_spec adxl345_events[] = {
{
/* single tap */
.type = IIO_EV_TYPE_GESTURE,
.dir = IIO_EV_DIR_SINGLETAP,
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_TIMEOUT),
},
};
#define ADXL345_CHANNEL(index, reg, axis) { \
.type = IIO_ACCEL, \
.modified = 1, \
@@ -57,6 +96,8 @@ struct adxl345_state {
.storagebits = 16, \
.endianness = IIO_LE, \
}, \
.event_spec = adxl345_events, \
.num_event_specs = ARRAY_SIZE(adxl345_events), \
}
enum adxl345_chans {
@@ -113,6 +154,157 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
}
/* tap */
static int _adxl345_set_tap_int(struct adxl345_state *st,
enum adxl345_tap_type type, bool state)
{
unsigned int int_map = 0x00;
unsigned int tap_threshold;
bool axis_valid;
bool singletap_args_valid = false;
bool en = false;
u32 axis_ctrl;
int ret;
ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
if (ret)
return ret;
axis_valid = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) > 0;
ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP, &tap_threshold);
if (ret)
return ret;
/*
* Note: A value of 0 for threshold and/or dur may result in undesirable
* behavior if single tap/double tap interrupts are enabled.
*/
singletap_args_valid = tap_threshold > 0 && st->tap_duration_us > 0;
if (type == ADXL345_SINGLE_TAP)
en = axis_valid && singletap_args_valid;
if (state && en)
int_map |= adxl345_tap_int_reg[type];
return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
adxl345_tap_int_reg[type], int_map);
}
static int adxl345_is_tap_en(struct adxl345_state *st,
enum iio_modifier axis,
enum adxl345_tap_type type, bool *en)
{
unsigned int regval;
u32 axis_ctrl;
int ret;
ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
if (ret)
return ret;
/* Verify if axis is enabled for the tap detection. */
switch (axis) {
case IIO_MOD_X:
*en = FIELD_GET(ADXL345_TAP_X_EN, axis_ctrl);
break;
case IIO_MOD_Y:
*en = FIELD_GET(ADXL345_TAP_Y_EN, axis_ctrl);
break;
case IIO_MOD_Z:
*en = FIELD_GET(ADXL345_TAP_Z_EN, axis_ctrl);
break;
default:
*en = false;
return -EINVAL;
}
if (*en) {
/*
* If axis allow for tap detection, verify if the interrupt is
* enabled for tap detection.
*/
ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, &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)