mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-10 10:20:17 -04:00
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:
committed by
Jonathan Cameron
parent
5aec2b6e19
commit
7478933f03
@@ -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, ®val);
|
||||
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, ®val);
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user