* [PATCH v4 01/14] iio: accel: adxl345: use regmap cache for INT mapping
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:06 ` Jonathan Cameron
2025-03-13 16:50 ` [PATCH v4 02/14] iio: accel: adxl345: move INT enable to regmap cache Lothar Rubusch
` (12 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Use regmap cache to replace the maintenance of the interrupt mapping
state by a member variable intio. The interrupt mapping is initialized
when the driver is probed, and it is perfectly cacheable.
The patch will still leave the function set_interrupts(). A follow up
patch takes care of it, when cleaning up the INT enable register
variable.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345.h | 4 ++
drivers/iio/accel/adxl345_core.c | 64 ++++++++++++++++++++------------
drivers/iio/accel/adxl345_i2c.c | 2 +
drivers/iio/accel/adxl345_spi.c | 2 +
4 files changed, 49 insertions(+), 23 deletions(-)
diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h
index bc6d634bd85c..7d482dd595fa 100644
--- a/drivers/iio/accel/adxl345.h
+++ b/drivers/iio/accel/adxl345.h
@@ -111,6 +111,10 @@
*/
#define ADXL375_USCALE 480000
+struct regmap;
+
+bool adxl345_is_volatile_reg(struct device *dev, unsigned int reg);
+
struct adxl345_chip_info {
const char *name;
int uscale;
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 22c5a9c08459..6f337b26999a 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -36,7 +36,6 @@ struct adxl345_state {
struct regmap *regmap;
bool fifo_delay; /* delay: delay is needed for SPI */
int irq;
- u8 intio;
u8 int_map;
u8 watermark;
u8 fifo_mode;
@@ -76,6 +75,25 @@ static const unsigned long adxl345_scan_masks[] = {
0
};
+bool adxl345_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADXL345_REG_DATA_AXIS(0):
+ case ADXL345_REG_DATA_AXIS(1):
+ case ADXL345_REG_DATA_AXIS(2):
+ case ADXL345_REG_DATA_AXIS(3):
+ case ADXL345_REG_DATA_AXIS(4):
+ case ADXL345_REG_DATA_AXIS(5):
+ case ADXL345_REG_ACT_TAP_STATUS:
+ case ADXL345_REG_FIFO_STATUS:
+ case ADXL345_REG_INT_SOURCE:
+ return true;
+ default:
+ return false;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(adxl345_is_volatile_reg, "IIO_ADXL345");
+
/**
* adxl345_set_measure_en() - Enable and disable measuring.
*
@@ -98,22 +116,7 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
static int adxl345_set_interrupts(struct adxl345_state *st)
{
- int ret;
- unsigned int int_enable = st->int_map;
- unsigned int int_map;
-
- /*
- * 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.
- */
- int_map = st->intio ? st->int_map : ~st->int_map;
-
- ret = regmap_write(st->regmap, ADXL345_REG_INT_MAP, int_map);
- if (ret)
- return ret;
-
- return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, int_enable);
+ return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, st->int_map);
}
static int adxl345_read_raw(struct iio_dev *indio_dev,
@@ -265,6 +268,7 @@ static const struct attribute_group adxl345_attrs_group = {
static int adxl345_set_fifo(struct adxl345_state *st)
{
+ unsigned int intio;
int ret;
/* FIFO should only be configured while in standby mode */
@@ -272,11 +276,14 @@ static int adxl345_set_fifo(struct adxl345_state *st)
if (ret < 0)
return ret;
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_MAP, &intio);
+ if (ret)
+ return ret;
+
ret = regmap_write(st->regmap, ADXL345_REG_FIFO_CTL,
FIELD_PREP(ADXL345_FIFO_CTL_SAMPLES_MSK,
st->watermark) |
- FIELD_PREP(ADXL345_FIFO_CTL_TRIGGER_MSK,
- st->intio) |
+ FIELD_PREP(ADXL345_FIFO_CTL_TRIGGER_MSK, intio) |
FIELD_PREP(ADXL345_FIFO_CTL_MODE_MSK,
st->fifo_mode));
if (ret < 0)
@@ -492,6 +499,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
struct adxl345_state *st;
struct iio_dev *indio_dev;
u32 regval;
+ u8 intio = ADXL345_INT1;
unsigned int data_format_mask = (ADXL345_DATA_FORMAT_RANGE |
ADXL345_DATA_FORMAT_JUSTIFY |
ADXL345_DATA_FORMAT_FULL_RES |
@@ -556,16 +564,26 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret < 0)
return ret;
- st->intio = ADXL345_INT1;
st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
if (st->irq < 0) {
- st->intio = ADXL345_INT2;
+ intio = ADXL345_INT2;
st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
if (st->irq < 0)
- st->intio = ADXL345_INT_NONE;
+ intio = ADXL345_INT_NONE;
}
- if (st->intio != ADXL345_INT_NONE) {
+ 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.
+ */
+ regval = intio ? 0xff : 0;
+
+ ret = regmap_write(st->regmap, ADXL345_REG_INT_MAP, regval);
+ 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)
diff --git a/drivers/iio/accel/adxl345_i2c.c b/drivers/iio/accel/adxl345_i2c.c
index eb3e0aadf51d..6ce567fd3ba6 100644
--- a/drivers/iio/accel/adxl345_i2c.c
+++ b/drivers/iio/accel/adxl345_i2c.c
@@ -17,6 +17,8 @@
static const struct regmap_config adxl345_i2c_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
+ .volatile_reg = adxl345_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
};
static int adxl345_i2c_probe(struct i2c_client *client)
diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c
index e03915ece8b6..200bdf975518 100644
--- a/drivers/iio/accel/adxl345_spi.c
+++ b/drivers/iio/accel/adxl345_spi.c
@@ -19,6 +19,8 @@ static const struct regmap_config adxl345_spi_regmap_config = {
.val_bits = 8,
/* Setting bits 7 and 6 enables multiple-byte read */
.read_flag_mask = BIT(7) | BIT(6),
+ .volatile_reg = adxl345_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
};
static int adxl345_spi_setup(struct device *dev, struct regmap *regmap)
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH v4 01/14] iio: accel: adxl345: use regmap cache for INT mapping
2025-03-13 16:50 ` [PATCH v4 01/14] iio: accel: adxl345: use regmap cache for INT mapping Lothar Rubusch
@ 2025-03-16 11:06 ` Jonathan Cameron
0 siblings, 0 replies; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-16 11:06 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 13 Mar 2025 16:50:36 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Use regmap cache to replace the maintenance of the interrupt mapping
> state by a member variable intio. The interrupt mapping is initialized
> when the driver is probed, and it is perfectly cacheable.
>
> The patch will still leave the function set_interrupts(). A follow up
> patch takes care of it, when cleaning up the INT enable register
> variable.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
I'm going to nibble away at this series applying what seems ready to me.
Applied this one.
Jonathan
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v4 02/14] iio: accel: adxl345: move INT enable to regmap cache
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 01/14] iio: accel: adxl345: use regmap cache for INT mapping Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:11 ` Jonathan Cameron
2025-03-13 16:50 ` [PATCH v4 03/14] iio: accel: adxl345: cleanup regmap return values Lothar Rubusch
` (11 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Replace the interrupt enable member variable to the regmap cache. This
makes the function set_interrupts() obsolete. The interrupt enable
register is written when the driver is probed. Thus it is perfectly
cacheable.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 26 +++++++++++---------------
1 file changed, 11 insertions(+), 15 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 6f337b26999a..10e2da7de17e 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -36,7 +36,6 @@ struct adxl345_state {
struct regmap *regmap;
bool fifo_delay; /* delay: delay is needed for SPI */
int irq;
- u8 int_map;
u8 watermark;
u8 fifo_mode;
__le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
@@ -114,11 +113,6 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
}
-static int adxl345_set_interrupts(struct adxl345_state *st)
-{
- return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, st->int_map);
-}
-
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -217,7 +211,7 @@ static int adxl345_reg_access(struct iio_dev *indio_dev, unsigned int reg,
static int adxl345_set_watermark(struct iio_dev *indio_dev, unsigned int value)
{
struct adxl345_state *st = iio_priv(indio_dev);
- unsigned int fifo_mask = 0x1F;
+ const unsigned int fifo_mask = 0x1F, watermark_mask = 0x02;
int ret;
value = min(value, ADXL345_FIFO_SIZE - 1);
@@ -227,7 +221,10 @@ static int adxl345_set_watermark(struct iio_dev *indio_dev, unsigned int value)
return ret;
st->watermark = value;
- st->int_map |= ADXL345_INT_WATERMARK;
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE, watermark_mask,
+ ADXL345_INT_WATERMARK);
+ if (ret)
+ return ret;
return 0;
}
@@ -381,11 +378,6 @@ static void adxl345_fifo_reset(struct adxl345_state *st)
static int adxl345_buffer_postenable(struct iio_dev *indio_dev)
{
struct adxl345_state *st = iio_priv(indio_dev);
- int ret;
-
- ret = adxl345_set_interrupts(st);
- if (ret < 0)
- return ret;
st->fifo_mode = ADXL345_FIFO_STREAM;
return adxl345_set_fifo(st);
@@ -401,8 +393,7 @@ static int adxl345_buffer_predisable(struct iio_dev *indio_dev)
if (ret < 0)
return ret;
- st->int_map = 0x00;
- return adxl345_set_interrupts(st);
+ return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
}
static const struct iio_buffer_setup_ops adxl345_buffer_ops = {
@@ -524,6 +515,11 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
indio_dev->num_channels = ARRAY_SIZE(adxl345_channels);
indio_dev->available_scan_masks = adxl345_scan_masks;
+ /* Reset interrupts at start up */
+ ret = regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
+ if (ret)
+ return ret;
+
if (setup) {
/* Perform optional initial bus specific configuration */
ret = setup(dev, st->regmap);
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH v4 02/14] iio: accel: adxl345: move INT enable to regmap cache
2025-03-13 16:50 ` [PATCH v4 02/14] iio: accel: adxl345: move INT enable to regmap cache Lothar Rubusch
@ 2025-03-16 11:11 ` Jonathan Cameron
0 siblings, 0 replies; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-16 11:11 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 13 Mar 2025 16:50:37 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Replace the interrupt enable member variable to the regmap cache. This
> makes the function set_interrupts() obsolete. The interrupt enable
> register is written when the driver is probed. Thus it is perfectly
> cacheable.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Applied with a small tweak. I don't think you touch set_watermark
again later in the series so this shouldn't cause too much impact.
> ---
> drivers/iio/accel/adxl345_core.c | 26 +++++++++++---------------
> 1 file changed, 11 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
> index 6f337b26999a..10e2da7de17e 100644
> --- a/drivers/iio/accel/adxl345_core.c
> +++ b/drivers/iio/accel/adxl345_core.c
> @@ -36,7 +36,6 @@ struct adxl345_state {
> struct regmap *regmap;
> bool fifo_delay; /* delay: delay is needed for SPI */
> int irq;
> - u8 int_map;
> u8 watermark;
> u8 fifo_mode;
> __le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
> @@ -114,11 +113,6 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
> return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
> }
>
> -static int adxl345_set_interrupts(struct adxl345_state *st)
> -{
> - return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, st->int_map);
> -}
> -
> static int adxl345_read_raw(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> int *val, int *val2, long mask)
> @@ -217,7 +211,7 @@ static int adxl345_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> static int adxl345_set_watermark(struct iio_dev *indio_dev, unsigned int value)
> {
> struct adxl345_state *st = iio_priv(indio_dev);
> - unsigned int fifo_mask = 0x1F;
> + const unsigned int fifo_mask = 0x1F, watermark_mask = 0x02;
> int ret;
>
> value = min(value, ADXL345_FIFO_SIZE - 1);
> @@ -227,7 +221,10 @@ static int adxl345_set_watermark(struct iio_dev *indio_dev, unsigned int value)
> return ret;
>
> st->watermark = value;
> - st->int_map |= ADXL345_INT_WATERMARK;
> + ret = regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE, watermark_mask,
> + ADXL345_INT_WATERMARK);
> + if (ret)
> + return ret;
tweaked to
return regmap.
>
> return 0;
> }
> @@ -381,11 +378,6 @@ static void adxl345_fifo_reset(struct adxl345_state *st)
> static int adxl345_buffer_postenable(struct iio_dev *indio_dev)
> {
> struct adxl345_state *st = iio_priv(indio_dev);
> - int ret;
> -
> - ret = adxl345_set_interrupts(st);
> - if (ret < 0)
> - return ret;
>
> st->fifo_mode = ADXL345_FIFO_STREAM;
> return adxl345_set_fifo(st);
> @@ -401,8 +393,7 @@ static int adxl345_buffer_predisable(struct iio_dev *indio_dev)
> if (ret < 0)
> return ret;
>
> - st->int_map = 0x00;
> - return adxl345_set_interrupts(st);
> + return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
> }
>
> static const struct iio_buffer_setup_ops adxl345_buffer_ops = {
> @@ -524,6 +515,11 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
> indio_dev->num_channels = ARRAY_SIZE(adxl345_channels);
> indio_dev->available_scan_masks = adxl345_scan_masks;
>
> + /* Reset interrupts at start up */
> + ret = regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
> + if (ret)
> + return ret;
> +
> if (setup) {
> /* Perform optional initial bus specific configuration */
> ret = setup(dev, st->regmap);
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v4 03/14] iio: accel: adxl345: cleanup regmap return values
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 01/14] iio: accel: adxl345: use regmap cache for INT mapping Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 02/14] iio: accel: adxl345: move INT enable to regmap cache Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:12 ` Jonathan Cameron
2025-03-13 16:50 ` [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function Lothar Rubusch
` (10 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Regmap return values sometimes are checked being less than zero
to trigger error handling. Sometimes this is checked for being not
zero. Unify the situation and check for not being zero.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 10e2da7de17e..eec3f0e17e1e 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -133,7 +133,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
ret = regmap_bulk_read(st->regmap,
ADXL345_REG_DATA_AXIS(chan->address),
&accel, sizeof(accel));
- if (ret < 0)
+ if (ret)
return ret;
*val = sign_extend32(le16_to_cpu(accel), 12);
@@ -145,7 +145,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_CALIBBIAS:
ret = regmap_read(st->regmap,
ADXL345_REG_OFS_AXIS(chan->address), ®val);
- if (ret < 0)
+ if (ret)
return ret;
/*
* 8-bit resolution at +/- 2g, that is 4x accel data scale
@@ -156,7 +156,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
case IIO_CHAN_INFO_SAMP_FREQ:
ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val);
- if (ret < 0)
+ if (ret)
return ret;
samp_freq_nhz = ADXL345_BASE_RATE_NANO_HZ <<
@@ -270,7 +270,7 @@ static int adxl345_set_fifo(struct adxl345_state *st)
/* FIFO should only be configured while in standby mode */
ret = adxl345_set_measure_en(st, false);
- if (ret < 0)
+ if (ret)
return ret;
ret = regmap_read(st->regmap, ADXL345_REG_INT_MAP, &intio);
@@ -283,7 +283,7 @@ static int adxl345_set_fifo(struct adxl345_state *st)
FIELD_PREP(ADXL345_FIFO_CTL_TRIGGER_MSK, intio) |
FIELD_PREP(ADXL345_FIFO_CTL_MODE_MSK,
st->fifo_mode));
- if (ret < 0)
+ if (ret)
return ret;
return adxl345_set_measure_en(st, true);
@@ -304,7 +304,7 @@ static int adxl345_get_samples(struct adxl345_state *st)
int ret;
ret = regmap_read(st->regmap, ADXL345_REG_FIFO_STATUS, ®val);
- if (ret < 0)
+ if (ret)
return ret;
return FIELD_GET(ADXL345_REG_FIFO_STATUS_MSK, regval);
@@ -332,7 +332,7 @@ static int adxl345_fifo_transfer(struct adxl345_state *st, int samples)
/* 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);
- if (ret < 0)
+ if (ret)
return ret;
/*
@@ -390,7 +390,7 @@ static int adxl345_buffer_predisable(struct iio_dev *indio_dev)
st->fifo_mode = ADXL345_FIFO_BYPASS;
ret = adxl345_set_fifo(st);
- if (ret < 0)
+ if (ret)
return ret;
return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
@@ -544,7 +544,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
}
ret = regmap_read(st->regmap, ADXL345_REG_DEVID, ®val);
- if (ret < 0)
+ if (ret)
return dev_err_probe(dev, ret, "Error reading device ID\n");
if (regval != ADXL345_DEVID)
@@ -553,11 +553,11 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
/* Enable measurement mode */
ret = adxl345_set_measure_en(st, true);
- if (ret < 0)
+ if (ret)
return dev_err_probe(dev, ret, "Failed to enable measurement mode\n");
ret = devm_add_action_or_reset(dev, adxl345_powerdown, st);
- if (ret < 0)
+ if (ret)
return ret;
st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
@@ -595,7 +595,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
ret = regmap_write(st->regmap, ADXL345_REG_FIFO_CTL,
FIELD_PREP(ADXL345_FIFO_CTL_MODE_MSK,
ADXL345_FIFO_BYPASS));
- if (ret < 0)
+ if (ret)
return ret;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (2 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 03/14] iio: accel: adxl345: cleanup regmap return values Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:14 ` Jonathan Cameron
2025-03-16 19:58 ` Andy Shevchenko
2025-03-13 16:50 ` [PATCH v4 05/14] iio: accel: adxl345: add single tap feature Lothar Rubusch
` (9 subsequent siblings)
13 siblings, 2 replies; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Move the fifo handling into a separate function. This is a preparation
for a generic handling of the interrupt status register results.
The interrupt status register is read into a variable int_stat. It carries
status for various sensor events, handling of which is added in follow up
patches. Evaluation of the int_stat variable is common for sensor events,
such as tap detection, freefall, activity,... and for fifo events, such as
data ready, overrun, watermark,... Also, dealing with in case error
returns shall be common to all events. Thus migrate fifo read-out and push
fifo content to iio channels into this function to be built up with
additional event handling.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index eec3f0e17e1e..d4c1a6f1559f 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -420,6 +420,24 @@ 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)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int samples;
+ int ret = -ENOENT;
+
+ if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
+ samples = adxl345_get_samples(st);
+ if (samples < 0)
+ return -EINVAL;
+
+ if (adxl345_fifo_push(indio_dev, samples) < 0)
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
/**
* adxl345_irq_handler() - Handle irqs of the ADXL345.
* @irq: The irq being handled.
@@ -432,19 +450,12 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
struct iio_dev *indio_dev = p;
struct adxl345_state *st = iio_priv(indio_dev);
int int_stat;
- int samples;
if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat))
return IRQ_NONE;
- if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
- samples = adxl345_get_samples(st);
- if (samples < 0)
- goto err;
-
- if (adxl345_fifo_push(indio_dev, samples) < 0)
- goto err;
- }
+ if (adxl345_push_event(indio_dev, int_stat))
+ goto err;
if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat))
goto err;
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function
2025-03-13 16:50 ` [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function Lothar Rubusch
@ 2025-03-16 11:14 ` Jonathan Cameron
2025-03-16 19:58 ` Andy Shevchenko
1 sibling, 0 replies; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-16 11:14 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 13 Mar 2025 16:50:39 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Move the fifo handling into a separate function. This is a preparation
> for a generic handling of the interrupt status register results.
>
> The interrupt status register is read into a variable int_stat. It carries
> status for various sensor events, handling of which is added in follow up
> patches. Evaluation of the int_stat variable is common for sensor events,
> such as tap detection, freefall, activity,... and for fifo events, such as
> data ready, overrun, watermark,... Also, dealing with in case error
> returns shall be common to all events. Thus migrate fifo read-out and push
> fifo content to iio channels into this function to be built up with
> additional event handling.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Applied
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function
2025-03-13 16:50 ` [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function Lothar Rubusch
2025-03-16 11:14 ` Jonathan Cameron
@ 2025-03-16 19:58 ` Andy Shevchenko
2025-03-17 10:55 ` Jonathan Cameron
1 sibling, 1 reply; 33+ messages in thread
From: Andy Shevchenko @ 2025-03-16 19:58 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, jic23, linux-iio, linux-kernel,
eraretuya
Thu, Mar 13, 2025 at 04:50:39PM +0000, Lothar Rubusch kirjoitti:
> Move the fifo handling into a separate function. This is a preparation
> for a generic handling of the interrupt status register results.
>
> The interrupt status register is read into a variable int_stat. It carries
> status for various sensor events, handling of which is added in follow up
> patches. Evaluation of the int_stat variable is common for sensor events,
> such as tap detection, freefall, activity,... and for fifo events, such as
> data ready, overrun, watermark,... Also, dealing with in case error
> returns shall be common to all events. Thus migrate fifo read-out and push
> fifo content to iio channels into this function to be built up with
> additional event handling.
...
> +static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
> +{
> + struct adxl345_state *st = iio_priv(indio_dev);
> + int samples;
> + int ret = -ENOENT;
> +
> + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
> + samples = adxl345_get_samples(st);
> + if (samples < 0)
> + return -EINVAL;
In the original code it makes no difference, but if you are going to share
this, I would expect to see
return samples;
here. Why the error code is shadowed? If it's trully needed, it has to be
explained in the comment.
> + if (adxl345_fifo_push(indio_dev, samples) < 0)
> + return -EINVAL;
> + }
> +
> + return ret;
> +}
...
Jonathan, I saw that you had applied it, but I guess the above needs
a clarification.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function
2025-03-16 19:58 ` Andy Shevchenko
@ 2025-03-17 10:55 ` Jonathan Cameron
2025-03-17 15:51 ` Andy Shevchenko
0 siblings, 1 reply; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-17 10:55 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lothar Rubusch, lars, Michael.Hennerich, linux-iio, linux-kernel,
eraretuya
On Sun, 16 Mar 2025 21:58:00 +0200
Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> Thu, Mar 13, 2025 at 04:50:39PM +0000, Lothar Rubusch kirjoitti:
> > Move the fifo handling into a separate function. This is a preparation
> > for a generic handling of the interrupt status register results.
> >
> > The interrupt status register is read into a variable int_stat. It carries
> > status for various sensor events, handling of which is added in follow up
> > patches. Evaluation of the int_stat variable is common for sensor events,
> > such as tap detection, freefall, activity,... and for fifo events, such as
> > data ready, overrun, watermark,... Also, dealing with in case error
> > returns shall be common to all events. Thus migrate fifo read-out and push
> > fifo content to iio channels into this function to be built up with
> > additional event handling.
>
> ...
>
> > +static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
> > +{
> > + struct adxl345_state *st = iio_priv(indio_dev);
> > + int samples;
> > + int ret = -ENOENT;
> > +
> > + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
> > + samples = adxl345_get_samples(st);
> > + if (samples < 0)
>
> > + return -EINVAL;
>
> In the original code it makes no difference, but if you are going to share
> this, I would expect to see
>
> return samples;
>
> here. Why the error code is shadowed? If it's trully needed, it has to be
> explained in the comment.
>
>
> > + if (adxl345_fifo_push(indio_dev, samples) < 0)
> > + return -EINVAL;
> > + }
> > +
> > + return ret;
> > +}
>
> ...
>
> Jonathan, I saw that you had applied it, but I guess the above needs
> a clarification.
Was right at the top of a tree I don't mind rebasing. So dropped
this patch (kept 1-3)
>
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function
2025-03-17 10:55 ` Jonathan Cameron
@ 2025-03-17 15:51 ` Andy Shevchenko
2025-03-18 9:44 ` Lothar Rubusch
0 siblings, 1 reply; 33+ messages in thread
From: Andy Shevchenko @ 2025-03-17 15:51 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Lothar Rubusch, lars, Michael.Hennerich, linux-iio, linux-kernel,
eraretuya
On Mon, Mar 17, 2025 at 12:56 PM Jonathan Cameron <jic23@kernel.org> wrote:
> On Sun, 16 Mar 2025 21:58:00 +0200
> Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > Thu, Mar 13, 2025 at 04:50:39PM +0000, Lothar Rubusch kirjoitti:
...
> > > +static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
> > > +{
> > > + struct adxl345_state *st = iio_priv(indio_dev);
> > > + int samples;
> > > + int ret = -ENOENT;
Also note, this variable is redundant as far as I can see, just return
the error code directly.
> > > +
> > > + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
> > > + samples = adxl345_get_samples(st);
> > > + if (samples < 0)
> >
> > > + return -EINVAL;
> >
> > In the original code it makes no difference, but if you are going to share
> > this, I would expect to see
> >
> > return samples;
> >
> > here. Why the error code is shadowed? If it's trully needed, it has to be
> > explained in the comment.
> >
> >
> > > + if (adxl345_fifo_push(indio_dev, samples) < 0)
> > > + return -EINVAL;
> > > + }
> > > +
> > > + return ret;
> > > +}
...
> > Jonathan, I saw that you had applied it, but I guess the above needs
> > a clarification.
> Was right at the top of a tree I don't mind rebasing. So dropped
> this patch (kept 1-3)
Thank you!
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function
2025-03-17 15:51 ` Andy Shevchenko
@ 2025-03-18 9:44 ` Lothar Rubusch
2025-03-26 9:32 ` Andy Shevchenko
0 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-18 9:44 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, lars, Michael.Hennerich, linux-iio,
linux-kernel, eraretuya
Hi Andy, Jonathan and IIO ML!
Pls, can you help me clarify a bit what to do best here. Questions
inlined down below.
On Mon, Mar 17, 2025 at 4:52 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
>
> On Mon, Mar 17, 2025 at 12:56 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > On Sun, 16 Mar 2025 21:58:00 +0200
> > Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > > Thu, Mar 13, 2025 at 04:50:39PM +0000, Lothar Rubusch kirjoitti:
>
> ...
>
> > > > +static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat)
> > > > +{
> > > > + struct adxl345_state *st = iio_priv(indio_dev);
> > > > + int samples;
>
> > > > + int ret = -ENOENT;
>
> Also note, this variable is redundant as far as I can see, just return
> the error code directly.
The pre-initialization of ret is actually needed in the follow up
patches. Anyway, I can return -ENOENT directly here.
Evaluation of the sensor events in follow up patches then uses the
ret. It is also possible that reading sensor events fails, then the
error is returned. It is possible, that no sensor event happened, then
it will fallback to -ENOENT. And, of course, if sensor event happened
and could be handled - no error is returned.
Is this approach acceptable? Say, if I'd describe it better in the
commit comment? Could you think of a better approach here? I think
returning 'samples' here does not make fully sense, though. First,
'samples' is not be used outside the called function. Second, I have
to distinguish a case "no event handled" - This covers then all
remaining events like e.g. OVERRUN, DATA READY,... which still need to
have status registers reset, but won't be pushed - currently this is
coveredy by the 'return -ENOENT;' fallback. Third, I need to be able
to return error codes.
>
> > > > +
> > > > + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
> > > > + samples = adxl345_get_samples(st);
> > > > + if (samples < 0)
> > >
> > > > + return -EINVAL;
> > >
> > > In the original code it makes no difference, but if you are going to share
> > > this, I would expect to see
> > >
> > > return samples;
> > >
> > > here. Why the error code is shadowed? If it's trully needed, it has to be
> > > explained in the comment.
As said above, 'samples' is just internally used inside this function.
Basic question here also,
since intuitively you'd expect it rather returning a samples number -
should I rename the function
to make it clearer?
Best,
L
> > >
> > >
> > > > + if (adxl345_fifo_push(indio_dev, samples) < 0)
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + return ret;
> > > > +}
>
> ...
>
> > > Jonathan, I saw that you had applied it, but I guess the above needs
> > > a clarification.
> > Was right at the top of a tree I don't mind rebasing. So dropped
> > this patch (kept 1-3)
>
> Thank you!
>
> --
> With Best Regards,
> Andy Shevchenko
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function
2025-03-18 9:44 ` Lothar Rubusch
@ 2025-03-26 9:32 ` Andy Shevchenko
2025-03-28 15:30 ` Lothar Rubusch
0 siblings, 1 reply; 33+ messages in thread
From: Andy Shevchenko @ 2025-03-26 9:32 UTC (permalink / raw)
To: Lothar Rubusch
Cc: Jonathan Cameron, lars, Michael.Hennerich, linux-iio,
linux-kernel, eraretuya
On Tue, Mar 18, 2025 at 11:45 AM Lothar Rubusch <l.rubusch@gmail.com> wrote:
> On Mon, Mar 17, 2025 at 4:52 PM Andy Shevchenko
> <andy.shevchenko@gmail.com> wrote:
> > On Mon, Mar 17, 2025 at 12:56 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > On Sun, 16 Mar 2025 21:58:00 +0200
> > > Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > > > Thu, Mar 13, 2025 at 04:50:39PM +0000, Lothar Rubusch kirjoitti:
...
> > > > > + int ret = -ENOENT;
> >
> > Also note, this variable is redundant as far as I can see, just return
> > the error code directly.
>
> The pre-initialization of ret is actually needed in the follow up
> patches. Anyway, I can return -ENOENT directly here.
Just do it when it's needed, I mean that this patch can survive
without ret variable.
> Evaluation of the sensor events in follow up patches then uses the
> ret. It is also possible that reading sensor events fails, then the
> error is returned. It is possible, that no sensor event happened, then
> it will fallback to -ENOENT. And, of course, if sensor event happened
> and could be handled - no error is returned.
>
> Is this approach acceptable? Say, if I'd describe it better in the
> commit comment? Could you think of a better approach here? I think
> returning 'samples' here does not make fully sense, though. First,
> 'samples' is not be used outside the called function. Second, I have
> to distinguish a case "no event handled" - This covers then all
> remaining events like e.g. OVERRUN, DATA READY,... which still need to
> have status registers reset, but won't be pushed - currently this is
> coveredy by the 'return -ENOENT;' fallback. Third, I need to be able
> to return error codes.
But does the 'samples' contain an error code? Perhaps you should just
make it do so...
> > + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
> > > > > + samples = adxl345_get_samples(st);
> > > > > + if (samples < 0)
> > > >
> > > > > + return -EINVAL;
> > > >
> > > > In the original code it makes no difference, but if you are going to share
> > > > this, I would expect to see
> > > >
> > > > return samples;
> > > >
> > > > here. Why the error code is shadowed? If it's trully needed, it has to be
> > > > explained in the comment.
>
> As said above, 'samples' is just internally used inside this function.
> Basic question here also,
> since intuitively you'd expect it rather returning a samples number -
> should I rename the function
> to make it clearer?
Perhaps renaming helps, but still, I don't see how a negative return
value can fit here. I would expect a negative to be a meaningful Linux
error code.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function
2025-03-26 9:32 ` Andy Shevchenko
@ 2025-03-28 15:30 ` Lothar Rubusch
0 siblings, 0 replies; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-28 15:30 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, lars, Michael.Hennerich, linux-iio,
linux-kernel, eraretuya
Hello Andy & IIO ML
On Wed, Mar 26, 2025 at 10:33 AM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
>
> On Tue, Mar 18, 2025 at 11:45 AM Lothar Rubusch <l.rubusch@gmail.com> wrote:
> > On Mon, Mar 17, 2025 at 4:52 PM Andy Shevchenko
> > <andy.shevchenko@gmail.com> wrote:
> > > On Mon, Mar 17, 2025 at 12:56 PM Jonathan Cameron <jic23@kernel.org> wrote:
> > > > On Sun, 16 Mar 2025 21:58:00 +0200
> > > > Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > > > > Thu, Mar 13, 2025 at 04:50:39PM +0000, Lothar Rubusch kirjoitti:
>
> ...
>
> > > > > > + int ret = -ENOENT;
> > >
> > > Also note, this variable is redundant as far as I can see, just return
> > > the error code directly.
> >
> > The pre-initialization of ret is actually needed in the follow up
> > patches. Anyway, I can return -ENOENT directly here.
>
> Just do it when it's needed, I mean that this patch can survive
> without ret variable.
>
> > Evaluation of the sensor events in follow up patches then uses the
> > ret. It is also possible that reading sensor events fails, then the
> > error is returned. It is possible, that no sensor event happened, then
> > it will fallback to -ENOENT. And, of course, if sensor event happened
> > and could be handled - no error is returned.
> >
> > Is this approach acceptable? Say, if I'd describe it better in the
> > commit comment? Could you think of a better approach here? I think
> > returning 'samples' here does not make fully sense, though. First,
> > 'samples' is not be used outside the called function. Second, I have
> > to distinguish a case "no event handled" - This covers then all
> > remaining events like e.g. OVERRUN, DATA READY,... which still need to
> > have status registers reset, but won't be pushed - currently this is
> > coveredy by the 'return -ENOENT;' fallback. Third, I need to be able
> > to return error codes.
>
> But does the 'samples' contain an error code? Perhaps you should just
> make it do so...
I'd like to direct you to v5 of the patches. I'm returning 0 now for handled
interrupt and/or read FIFO elements, or a negative error code in case of error.
I guess somehow I could not see the problem initially. Nevertheless, when
preparing v5 it became clear. I did a small adjustement and hope v5 addresses
the issue in a better way.
Thank you, Andy, for pointing this out to me. So, no direct answers here,
hope it's ok.
Best,
L
>
> > > + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
> > > > > > + samples = adxl345_get_samples(st);
> > > > > > + if (samples < 0)
> > > > >
> > > > > > + return -EINVAL;
> > > > >
> > > > > In the original code it makes no difference, but if you are going to share
> > > > > this, I would expect to see
> > > > >
> > > > > return samples;
> > > > >
> > > > > here. Why the error code is shadowed? If it's trully needed, it has to be
> > > > > explained in the comment.
> >
> > As said above, 'samples' is just internally used inside this function.
> > Basic question here also,
> > since intuitively you'd expect it rather returning a samples number -
> > should I rename the function
> > to make it clearer?
>
> Perhaps renaming helps, but still, I don't see how a negative return
> value can fit here. I would expect a negative to be a meaningful Linux
> error code.
>
> --
> With Best Regards,
> Andy Shevchenko
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v4 05/14] iio: accel: adxl345: add single tap feature
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (3 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 04/14] iio: accel: adxl345: introduce adxl345_push_event function Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:22 ` Jonathan Cameron
2025-03-13 16:50 ` [PATCH v4 06/14] iio: accel: adxl345: add double " Lothar Rubusch
` (8 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
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>
---
drivers/iio/accel/adxl345_core.c | 365 ++++++++++++++++++++++++++++++-
1 file changed, 363 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index d4c1a6f1559f..cfdbc73c96b4 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -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,33 @@
#define ADXL345_INT1 0
#define ADXL345_INT2 1
+#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
+
+enum adxl345_axis {
+ ADXL345_Z_EN = BIT(0),
+ ADXL345_Y_EN = BIT(1),
+ ADXL345_X_EN = BIT(2),
+ /* Suppress double tap detection if value > tap threshold */
+ ADXL345_TAP_SUPPRESS = BIT(3),
+};
+
+/* 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 +67,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 +100,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 +158,150 @@ 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 tap_axis_ctrl;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &tap_axis_ctrl);
+ if (ret)
+ return ret;
+
+ axis_valid = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, tap_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;
+ bool axis_en;
+ u32 axis_ctrl;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ switch (axis) {
+ case IIO_MOD_X:
+ axis_en = FIELD_GET(ADXL345_X_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Y:
+ axis_en = FIELD_GET(ADXL345_Y_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Z:
+ axis_en = FIELD_GET(ADXL345_Z_EN, axis_ctrl);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val);
+ if (ret)
+ return ret;
+
+ *en = (adxl345_tap_int_reg[type] & regval) > 0;
+
+ return 0;
+}
+
+static int adxl345_set_singletap_en(struct adxl345_state *st,
+ enum iio_modifier axis, bool en)
+{
+ int ret;
+ enum adxl345_axis axis_ctrl;
+
+ switch (axis) {
+ case IIO_MOD_X:
+ axis_ctrl = ADXL345_X_EN;
+ break;
+ case IIO_MOD_Y:
+ axis_ctrl = ADXL345_Y_EN;
+ break;
+ case IIO_MOD_Z:
+ axis_ctrl = ADXL345_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 +387,132 @@ 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 = -EFAULT;
+
+ 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,
+ int 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));
+ break;
+ case IIO_EV_INFO_TIMEOUT:
+ ret = adxl345_set_tap_duration(st, val, val2);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ return ret; /* measurement stays off */
+
+ 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)
{
@@ -420,12 +735,24 @@ 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);
if (samples < 0)
@@ -449,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_Z_EN, regval))
+ tap_dir = IIO_MOD_Z;
+ else if (FIELD_GET(ADXL345_Y_EN, regval))
+ tap_dir = IIO_MOD_Y;
+ else if (FIELD_GET(ADXL345_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))
@@ -473,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,
};
@@ -506,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));
@@ -519,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;
@@ -591,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)
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH v4 05/14] iio: accel: adxl345: add single tap feature
2025-03-13 16:50 ` [PATCH v4 05/14] iio: accel: adxl345: add single tap feature Lothar Rubusch
@ 2025-03-16 11:22 ` Jonathan Cameron
2025-03-18 10:32 ` Lothar Rubusch
2025-03-18 23:08 ` Lothar Rubusch
0 siblings, 2 replies; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-16 11:22 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 13 Mar 2025 16:50:40 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> 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>
Hi Lothar,
A few things in here are from the discussion that was continuing
on v3 so I may have said more replying to that.
Anyhow, for now I'll hold off on applying from this point on as
a few more things to respond to inline.
Jonathan
>
> #include "adxl345.h"
> @@ -31,6 +33,33 @@
> #define ADXL345_INT1 0
> #define ADXL345_INT2 1
>
> +#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> +
> +enum adxl345_axis {
> + ADXL345_Z_EN = BIT(0),
> + ADXL345_Y_EN = BIT(1),
> + ADXL345_X_EN = BIT(2),
> + /* Suppress double tap detection if value > tap threshold */
> + ADXL345_TAP_SUPPRESS = BIT(3),
> +};
As per feedback (after you sent this!) on v3, I'd drop
the last value out of the enum, or just use defines and a u8 for
the one place this is used for local variable storage.
> @@ -198,6 +387,132 @@ 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 = -EFAULT;
Not used?
> +
> + 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_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;
> +
So in my brief reply to the v3 discussion I suggested perhaps
factoring out everything from here...
> + 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));
> + break;
> + case IIO_EV_INFO_TIMEOUT:
> + ret = adxl345_set_tap_duration(st, val, val2);
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
to here, so as to allow simple direct returns.
I think that will make the code more readable given the need to reenable
measurements and that you want to leave it off on error.
> +
> + if (ret)
> + return ret; /* measurement stays off */
> +
> + return adxl345_set_measure_en(st, true);
> +}
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH v4 05/14] iio: accel: adxl345: add single tap feature
2025-03-16 11:22 ` Jonathan Cameron
@ 2025-03-18 10:32 ` Lothar Rubusch
2025-03-18 23:08 ` Lothar Rubusch
1 sibling, 0 replies; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-18 10:32 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Sun, Mar 16, 2025 at 12:22 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Thu, 13 Mar 2025 16:50:40 +0000
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > 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>
>
> Hi Lothar,
>
> A few things in here are from the discussion that was continuing
> on v3 so I may have said more replying to that.
>
> Anyhow, for now I'll hold off on applying from this point on as
> a few more things to respond to inline.
>
> Jonathan
>
> >
> > #include "adxl345.h"
> > @@ -31,6 +33,33 @@
> > #define ADXL345_INT1 0
> > #define ADXL345_INT2 1
> >
> > +#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > +
> > +enum adxl345_axis {
> > + ADXL345_Z_EN = BIT(0),
> > + ADXL345_Y_EN = BIT(1),
> > + ADXL345_X_EN = BIT(2),
> > + /* Suppress double tap detection if value > tap threshold */
> > + ADXL345_TAP_SUPPRESS = BIT(3),
> > +};
> As per feedback (after you sent this!) on v3, I'd drop
> the last value out of the enum, or just use defines and a u8 for
> the one place this is used for local variable storage.
>
>
> > @@ -198,6 +387,132 @@ 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 = -EFAULT;
> Not used?
>
> > +
> > + 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_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;
> > +
> So in my brief reply to the v3 discussion I suggested perhaps
> factoring out everything from here...
> > + 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));
> > + break;
> > + case IIO_EV_INFO_TIMEOUT:
> > + ret = adxl345_set_tap_duration(st, val, val2);
> > + break;
> > + default:
> > + ret = -EINVAL;
> > + break;
> > + }
> > + break;
> > + default:
> > + ret = -EINVAL;
> > + break;
> > + }
> to here, so as to allow simple direct returns.
>
> I think that will make the code more readable given the need to reenable
> measurements and that you want to leave it off on error.
>
> > +
> > + if (ret)
> > + return ret; /* measurement stays off */
> > +
> > + return adxl345_set_measure_en(st, true);
> > +}
>
Hi Jonathan, my reason to leave this part unchanged was the following:
As you can see above, 'return adxl345_set_measure_en(st, true);' - I'm
actually need to turn off measurement before changing values, then to
turn it on again after. In case of error, doing a simple "return ret"
would keep measurement generally off. I think I should generally try
to turn it on again after.
Alternatively I could do something with goto/label to turn measurement
on, but then return ret. My choice here was to use this ret approach
together with break, at cost of readability. Any advice?
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH v4 05/14] iio: accel: adxl345: add single tap feature
2025-03-16 11:22 ` Jonathan Cameron
2025-03-18 10:32 ` Lothar Rubusch
@ 2025-03-18 23:08 ` Lothar Rubusch
2025-03-31 12:36 ` Jonathan Cameron
1 sibling, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-18 23:08 UTC (permalink / raw)
To: Jonathan Cameron
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Sun, Mar 16, 2025 at 12:22 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Thu, 13 Mar 2025 16:50:40 +0000
> Lothar Rubusch <l.rubusch@gmail.com> wrote:
>
> > 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>
>
> Hi Lothar,
>
> A few things in here are from the discussion that was continuing
> on v3 so I may have said more replying to that.
>
> Anyhow, for now I'll hold off on applying from this point on as
> a few more things to respond to inline.
>
> Jonathan
>
> >
> > #include "adxl345.h"
> > @@ -31,6 +33,33 @@
> > #define ADXL345_INT1 0
> > #define ADXL345_INT2 1
> >
> > +#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > +
> > +enum adxl345_axis {
> > + ADXL345_Z_EN = BIT(0),
> > + ADXL345_Y_EN = BIT(1),
> > + ADXL345_X_EN = BIT(2),
> > + /* Suppress double tap detection if value > tap threshold */
> > + ADXL345_TAP_SUPPRESS = BIT(3),
> > +};
> As per feedback (after you sent this!) on v3, I'd drop
> the last value out of the enum, or just use defines and a u8 for
> the one place this is used for local variable storage.
>
>
> > @@ -198,6 +387,132 @@ 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 = -EFAULT;
> Not used?
>
> > +
> > + 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_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;
> > +
> So in my brief reply to the v3 discussion I suggested perhaps
> factoring out everything from here...
> > + 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));
> > + break;
> > + case IIO_EV_INFO_TIMEOUT:
> > + ret = adxl345_set_tap_duration(st, val, val2);
> > + break;
> > + default:
> > + ret = -EINVAL;
> > + break;
> > + }
> > + break;
> > + default:
> > + ret = -EINVAL;
> > + break;
> > + }
> to here, so as to allow simple direct returns.
>
> I think that will make the code more readable given the need to reenable
> measurements and that you want to leave it off on error.
>
Sorry for replying again on this topic. Pls, find my solution in v5.
After some thinking, I implemented it now using returns directly leaving the
measurement on/off as is. I'm unsure if it actually makes sense, after an error
here to turn measurement on again? I can imagine a situation where a wrong
input might result in an error. Nothing is changed, and measurement
could/should continue. Now, it will probably stop, in case of wrong
input. But is
wrong input actually an issue here?
As other alternative, I can think of is to shift measurement on/off
into the called
functions directly. I think, this approach was used also in the
ADXL380 and seems
to be common. Let me know what you think.
> > +
> > + if (ret)
> > + return ret; /* measurement stays off */
> > +
> > + return adxl345_set_measure_en(st, true);
> > +}
>
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH v4 05/14] iio: accel: adxl345: add single tap feature
2025-03-18 23:08 ` Lothar Rubusch
@ 2025-03-31 12:36 ` Jonathan Cameron
0 siblings, 0 replies; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-31 12:36 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Wed, 19 Mar 2025 00:08:24 +0100
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> On Sun, Mar 16, 2025 at 12:22 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Thu, 13 Mar 2025 16:50:40 +0000
> > Lothar Rubusch <l.rubusch@gmail.com> wrote:
> >
> > > 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>
> >
> > Hi Lothar,
> >
> > A few things in here are from the discussion that was continuing
> > on v3 so I may have said more replying to that.
> >
> > Anyhow, for now I'll hold off on applying from this point on as
> > a few more things to respond to inline.
> >
> > Jonathan
> >
> > >
> > > #include "adxl345.h"
> > > @@ -31,6 +33,33 @@
> > > #define ADXL345_INT1 0
> > > #define ADXL345_INT2 1
> > >
> > > +#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
> > > +
> > > +enum adxl345_axis {
> > > + ADXL345_Z_EN = BIT(0),
> > > + ADXL345_Y_EN = BIT(1),
> > > + ADXL345_X_EN = BIT(2),
> > > + /* Suppress double tap detection if value > tap threshold */
> > > + ADXL345_TAP_SUPPRESS = BIT(3),
> > > +};
> > As per feedback (after you sent this!) on v3, I'd drop
> > the last value out of the enum, or just use defines and a u8 for
> > the one place this is used for local variable storage.
> >
> >
> > > @@ -198,6 +387,132 @@ 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 = -EFAULT;
> > Not used?
> >
> > > +
> > > + 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_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;
> > > +
> > So in my brief reply to the v3 discussion I suggested perhaps
> > factoring out everything from here...
> > > + 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));
> > > + break;
> > > + case IIO_EV_INFO_TIMEOUT:
> > > + ret = adxl345_set_tap_duration(st, val, val2);
> > > + break;
> > > + default:
> > > + ret = -EINVAL;
> > > + break;
> > > + }
> > > + break;
> > > + default:
> > > + ret = -EINVAL;
> > > + break;
> > > + }
> > to here, so as to allow simple direct returns.
> >
> > I think that will make the code more readable given the need to reenable
> > measurements and that you want to leave it off on error.
> >
>
> Sorry for replying again on this topic. Pls, find my solution in v5.
>
> After some thinking, I implemented it now using returns directly leaving the
> measurement on/off as is. I'm unsure if it actually makes sense, after an error
> here to turn measurement on again? I can imagine a situation where a wrong
> input might result in an error. Nothing is changed, and measurement
> could/should continue. Now, it will probably stop, in case of wrong
> input. But is
> wrong input actually an issue here?
If a userspace control input is out of range etc, then returning an error
but leaving things on makes sense. If what we see is a comms error
it gets less clear on what we should do.
>
> As other alternative, I can think of is to shift measurement on/off
> into the called
> functions directly. I think, this approach was used also in the
> ADXL380 and seems
> to be common. Let me know what you think.
>
Moving it up or down a layer can work by allowing direct returns and always
trying to reenable if that makes sense.
Sadly in error handling there is often not a right answer on what to do!
Jonathan
> > > +
> > > + if (ret)
> > > + return ret; /* measurement stays off */
> > > +
> > > + return adxl345_set_measure_en(st, true);
> > > +}
> >
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v4 06/14] iio: accel: adxl345: add double tap feature
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (4 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 05/14] iio: accel: adxl345: add single tap feature Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 07/14] iio: accel: adxl345: set the tap suppress bit permanently Lothar Rubusch
` (7 subsequent siblings)
13 siblings, 0 replies; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add the double tap feature of the sensor. The interrupt handler needs
to catch and forward the event to the IIO channel. The single tap
implementation now is extended to deal with double tap as well.
Doubletap introduces window and latency times, both in us. Since both
times are scaled, the 8-bit register value is stored in hardware,
where the scaled value in [us] is stored as member variable.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 100 ++++++++++++++++++++++++++++++-
1 file changed, 99 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index cfdbc73c96b4..eae419cf8d0b 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -46,17 +46,23 @@ enum adxl345_axis {
/* single/double tap */
enum adxl345_tap_type {
ADXL345_SINGLE_TAP,
+ ADXL345_DOUBLE_TAP,
};
static const unsigned int adxl345_tap_int_reg[] = {
[ADXL345_SINGLE_TAP] = ADXL345_INT_SINGLE_TAP,
+ [ADXL345_DOUBLE_TAP] = ADXL345_INT_DOUBLE_TAP,
};
enum adxl345_tap_time_type {
+ ADXL345_TAP_TIME_LATENT,
+ ADXL345_TAP_TIME_WINDOW,
ADXL345_TAP_TIME_DUR,
};
static const unsigned int adxl345_tap_time_reg[] = {
+ [ADXL345_TAP_TIME_LATENT] = ADXL345_REG_LATENT,
+ [ADXL345_TAP_TIME_WINDOW] = ADXL345_REG_WINDOW,
[ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
};
@@ -69,6 +75,8 @@ struct adxl345_state {
u8 fifo_mode;
u32 tap_duration_us;
+ u32 tap_latent_us;
+ u32 tap_window_us;
__le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
};
@@ -82,6 +90,14 @@ static struct iio_event_spec adxl345_events[] = {
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_TIMEOUT),
},
+ {
+ /* double tap */
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_DOUBLETAP,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT) |
+ BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
+ },
};
#define ADXL345_CHANNEL(index, reg, axis) { \
@@ -167,6 +183,7 @@ static int _adxl345_set_tap_int(struct adxl345_state *st,
unsigned int tap_threshold;
bool axis_valid;
bool singletap_args_valid = false;
+ bool doubletap_args_valid = false;
bool en = false;
u32 tap_axis_ctrl;
int ret;
@@ -187,8 +204,16 @@ static int _adxl345_set_tap_int(struct adxl345_state *st,
*/
singletap_args_valid = tap_threshold > 0 && st->tap_duration_us > 0;
- if (type == ADXL345_SINGLE_TAP)
+ if (type == ADXL345_SINGLE_TAP) {
en = axis_valid && singletap_args_valid;
+ } else {
+ /* doubletap: Window must be equal or greater than latent! */
+ doubletap_args_valid = st->tap_latent_us > 0 &&
+ st->tap_window_us > 0 &&
+ st->tap_window_us >= st->tap_latent_us;
+
+ en = axis_valid && singletap_args_valid && doubletap_args_valid;
+ }
if (state && en)
int_map |= adxl345_tap_int_reg[type];
@@ -265,12 +290,23 @@ static int adxl345_set_singletap_en(struct adxl345_state *st,
return _adxl345_set_tap_int(st, ADXL345_SINGLE_TAP, en);
}
+static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en)
+{
+ return _adxl345_set_tap_int(st, ADXL345_DOUBLE_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_WINDOW:
+ st->tap_window_us = val_us;
+ break;
+ case ADXL345_TAP_TIME_LATENT:
+ st->tap_latent_us = val_us;
+ break;
case ADXL345_TAP_TIME_DUR:
st->tap_duration_us = val_us;
break;
@@ -302,6 +338,34 @@ static int adxl345_set_tap_duration(struct adxl345_state *st, u32 val_int,
return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_DUR, val_fract_us);
}
+static int adxl345_set_tap_window(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ /*
+ * Max value is 255 * 1250 us = 0.318750 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (val_int || val_fract_us > 318750)
+ return -EINVAL;
+
+ return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_WINDOW, val_fract_us);
+}
+
+static int adxl345_set_tap_latent(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ /*
+ * Max value is 255 * 1250 us = 0.318750 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (val_int || val_fract_us > 318750)
+ return -EINVAL;
+
+ return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_LATENT, 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)
@@ -405,6 +469,12 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
if (ret)
return ret;
return int_en;
+ case IIO_EV_DIR_DOUBLETAP:
+ ret = adxl345_is_tap_en(st, chan->channel2,
+ ADXL345_DOUBLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
default:
return -EINVAL;
}
@@ -426,6 +496,8 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
return adxl345_set_singletap_en(st, chan->channel2, state);
+ case IIO_EV_DIR_DOUBLETAP:
+ return adxl345_set_doubletap_en(st, state);
default:
return -EINVAL;
}
@@ -465,6 +537,14 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
*val = st->tap_duration_us;
*val2 = 1000000;
return IIO_VAL_FRACTIONAL;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ *val = st->tap_window_us;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ *val = st->tap_latent_us;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
@@ -497,6 +577,12 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
case IIO_EV_INFO_TIMEOUT:
ret = adxl345_set_tap_duration(st, val, val2);
break;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ ret = adxl345_set_tap_window(st, val, val2);
+ break;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ ret = adxl345_set_tap_latent(st, val, val2);
+ break;
default:
ret = -EINVAL;
break;
@@ -753,6 +839,16 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
return ret;
}
+ if (FIELD_GET(ADXL345_INT_DOUBLE_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_DOUBLETAP),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
samples = adxl345_get_samples(st);
if (samples < 0)
@@ -875,6 +971,8 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
/* Init with reasonable values */
tap_threshold = 48; /* 48 [0x30] -> ~3g */
st->tap_duration_us = 16; /* 16 [0x10] -> .010 */
+ st->tap_window_us = 64; /* 64 [0x40] -> .080 */
+ st->tap_latent_us = 16; /* 16 [0x10] -> .020 */
indio_dev->name = st->info->name;
indio_dev->info = &adxl345_info;
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH v4 07/14] iio: accel: adxl345: set the tap suppress bit permanently
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (5 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 06/14] iio: accel: adxl345: add double " Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 08/14] iio: accel: adxl345: add freefall feature Lothar Rubusch
` (6 subsequent siblings)
13 siblings, 0 replies; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Set the suppress bit feature to the double tap detection, whenever
double tap is enabled. This impedes the suppress bit dangling in any
state, and thus varying in sensitivity for double tap detection.
Any tap event is defined by a rising signal edge above threshold, i.e.
duration time starts counting; and the falling edge under threshold
within duration time, i.e. then the tap event is issued. This means
duration is used individually for each tap event.
For double tap detection after a single tap, a latency time needs to be
specified. Usually tap events, i.e. spikes above and returning below
threshold will be ignored within latency. After latency, the window
time starts counting for a second tap detection which has to happen
within a duration time.
If the suppress bit is not set, spikes within latency time are ignored.
Setting the suppress bit will invalidate the double tap function. The
sensor will thus be able to save the window time for double tap
detection, and follow a more strict definition of what signal qualifies
for a double tap.
In a summary having the suppress bit set, fewer signal spikes will be
considered as double taps. This is an optional add on to double tap,
thus a separate patch.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index eae419cf8d0b..9e33236c786a 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -34,6 +34,7 @@
#define ADXL345_INT2 1
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
+#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
enum adxl345_axis {
ADXL345_Z_EN = BIT(0),
@@ -292,6 +293,18 @@ static int adxl345_set_singletap_en(struct adxl345_state *st,
static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en)
{
+ int ret;
+
+ /*
+ * Generally suppress detection of spikes during the latency period as
+ * double taps here, this is fully optional for double tap detection
+ */
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+ ADXL345_REG_TAP_SUPPRESS_MSK,
+ en ? ADXL345_TAP_SUPPRESS : 0x00);
+ if (ret)
+ return ret;
+
return _adxl345_set_tap_int(st, ADXL345_DOUBLE_TAP, en);
}
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH v4 08/14] iio: accel: adxl345: add freefall feature
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (6 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 07/14] iio: accel: adxl345: set the tap suppress bit permanently Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 09/14] iio: accel: adxl345: extend sample frequency adjustments Lothar Rubusch
` (5 subsequent siblings)
13 siblings, 0 replies; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add the freefall detection of the sensor together with a threshold and
time parameter. A freefall event is detected if the measuring signal
falls below the threshold.
Introduce a freefall threshold stored in regmap cache, and a freefall
time, having the scaled time value stored as a member variable in the
state instance.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 122 +++++++++++++++++++++++++++++++
1 file changed, 122 insertions(+)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 9e33236c786a..1a2bf0c630c9 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -78,6 +78,7 @@ struct adxl345_state {
u32 tap_duration_us;
u32 tap_latent_us;
u32 tap_window_us;
+ u32 ff_time_ms;
__le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
};
@@ -99,6 +100,14 @@ static struct iio_event_spec adxl345_events[] = {
BIT(IIO_EV_INFO_RESET_TIMEOUT) |
BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
},
+ {
+ /* free fall */
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
};
#define ADXL345_CHANNEL(index, reg, axis) { \
@@ -379,6 +388,64 @@ static int adxl345_set_tap_latent(struct adxl345_state *st, u32 val_int,
return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_LATENT, val_fract_us);
}
+/* freefall */
+
+static int adxl345_is_ff_en(struct adxl345_state *st, bool *en)
+{
+ int ret;
+ unsigned int regval;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val);
+ if (ret)
+ return ret;
+
+ *en = FIELD_GET(ADXL345_INT_FREE_FALL, regval) > 0;
+
+ return 0;
+}
+
+static int adxl345_set_ff_en(struct adxl345_state *st, bool cmd_en)
+{
+ unsigned int regval, ff_threshold;
+ const unsigned int freefall_mask = 0x02;
+ bool en;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF, &ff_threshold);
+ if (ret)
+ return ret;
+
+ en = cmd_en && ff_threshold > 0 && st->ff_time_ms > 0;
+
+ regval = en ? ADXL345_INT_FREE_FALL : 0x00;
+
+ return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ freefall_mask, regval);
+}
+
+static int adxl345_set_ff_time(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ unsigned int regval;
+ int val_ms;
+
+ /*
+ * max value is 255 * 5000 us = 1.275000 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (1000000 * val_int + val_fract_us > 1275000)
+ return -EINVAL;
+
+ val_ms = val_int * 1000 + DIV_ROUND_UP(val_fract_us, 1000);
+ st->ff_time_ms = val_ms;
+
+ regval = DIV_ROUND_CLOSEST(val_ms, 5);
+
+ /* Values between 100ms and 350ms (0x14 to 0x46) are recommended. */
+ return regmap_write(st->regmap, ADXL345_REG_TIME_FF, min(regval, 0xff));
+}
+
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -491,6 +558,11 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG:
+ ret = adxl345_is_ff_en(st, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
default:
return -EINVAL;
}
@@ -514,6 +586,8 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG:
+ return adxl345_set_ff_en(st, state);
default:
return -EINVAL;
}
@@ -528,6 +602,7 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
{
struct adxl345_state *st = iio_priv(indio_dev);
unsigned int tap_threshold;
+ unsigned int ff_threshold;
int ret;
switch (type) {
@@ -561,6 +636,22 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_EV_TYPE_MAG:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_FF,
+ &ff_threshold);
+ if (ret)
+ return ret;
+ *val = ff_threshold;
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_PERIOD:
+ *val = st->ff_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -601,6 +692,18 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
break;
}
break;
+ case IIO_EV_TYPE_MAG:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, val);
+ break;
+ case IIO_EV_INFO_PERIOD:
+ ret = adxl345_set_ff_time(st, val, val2);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ break;
default:
ret = -EINVAL;
break;
@@ -862,6 +965,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_OR_Y_OR_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)
@@ -968,6 +1082,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
ADXL345_DATA_FORMAT_FULL_RES |
ADXL345_DATA_FORMAT_SELF_TEST);
unsigned int tap_threshold;
+ unsigned int ff_threshold;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
@@ -987,6 +1102,9 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
st->tap_window_us = 64; /* 64 [0x40] -> .080 */
st->tap_latent_us = 16; /* 16 [0x10] -> .020 */
+ ff_threshold = 8; /* 8 [0x08] */
+ st->ff_time_ms = 32; /* 32 [0x20] -> 0.16 */
+
indio_dev->name = st->info->name;
indio_dev->info = &adxl345_info;
indio_dev->modes = INDIO_DIRECT_MODE;
@@ -1063,6 +1181,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_FF, ff_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)
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH v4 09/14] iio: accel: adxl345: extend sample frequency adjustments
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (7 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 08/14] iio: accel: adxl345: add freefall feature Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:26 ` Jonathan Cameron
2025-03-13 16:50 ` [PATCH v4 10/14] iio: accel: adxl345: add g-range configuration Lothar Rubusch
` (4 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Introduce enums and functions to work with the sample frequency
adjustments. Let the sample frequency adjust via IIO and configure
a reasonable default.
Replace the old static sample frequency handling. The patch is in
preparation for activity/inactivity handling. During adjustment of
bw registers, measuring is disabled and afterwards enabled again.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345.h | 2 +-
drivers/iio/accel/adxl345_core.c | 156 ++++++++++++++++++++++++-------
2 files changed, 124 insertions(+), 34 deletions(-)
diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h
index 7d482dd595fa..6c1f96406136 100644
--- a/drivers/iio/accel/adxl345.h
+++ b/drivers/iio/accel/adxl345.h
@@ -69,7 +69,7 @@
* BW_RATE bits - Bandwidth and output data rate. The default value is
* 0x0A, which translates to a 100 Hz output data rate
*/
-#define ADXL345_BW_RATE GENMASK(3, 0)
+#define ADXL345_BW_RATE_MSK GENMASK(3, 0)
#define ADXL345_BW_LOW_POWER BIT(4)
#define ADXL345_BASE_RATE_NANO_HZ 97656250LL
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 1a2bf0c630c9..4c5084497def 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -67,6 +67,45 @@ static const unsigned int adxl345_tap_time_reg[] = {
[ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
};
+enum adxl345_odr {
+ ADXL345_ODR_0P10HZ = 0,
+ ADXL345_ODR_0P20HZ,
+ ADXL345_ODR_0P39HZ,
+ ADXL345_ODR_0P78HZ,
+ ADXL345_ODR_1P56HZ,
+ ADXL345_ODR_3P13HZ,
+ ADXL345_ODR_6P25HZ,
+ ADXL345_ODR_12P50HZ,
+ ADXL345_ODR_25HZ,
+ ADXL345_ODR_50HZ,
+ ADXL345_ODR_100HZ,
+ ADXL345_ODR_200HZ,
+ ADXL345_ODR_400HZ,
+ ADXL345_ODR_800HZ,
+ ADXL345_ODR_1600HZ,
+ ADXL345_ODR_3200HZ,
+};
+
+/* Certain features recommend 12.5 Hz - 400 Hz ODR */
+static const int adxl345_odr_tbl[][2] = {
+ [ADXL345_ODR_0P10HZ] = { 0, 97000 },
+ [ADXL345_ODR_0P20HZ] = { 0, 195000 },
+ [ADXL345_ODR_0P39HZ] = { 0, 390000 },
+ [ADXL345_ODR_0P78HZ] = { 0, 781000 },
+ [ADXL345_ODR_1P56HZ] = { 1, 562000 },
+ [ADXL345_ODR_3P13HZ] = { 3, 125000 },
+ [ADXL345_ODR_6P25HZ] = { 6, 250000 },
+ [ADXL345_ODR_12P50HZ] = { 12, 500000 },
+ [ADXL345_ODR_25HZ] = { 25, 0 },
+ [ADXL345_ODR_50HZ] = { 50, 0 },
+ [ADXL345_ODR_100HZ] = { 100, 0 },
+ [ADXL345_ODR_200HZ] = { 200, 0 },
+ [ADXL345_ODR_400HZ] = { 400, 0 },
+ [ADXL345_ODR_800HZ] = { 800, 0 },
+ [ADXL345_ODR_1600HZ] = { 1600, 0 },
+ [ADXL345_ODR_3200HZ] = { 3200, 0 },
+};
+
struct adxl345_state {
const struct adxl345_chip_info *info;
struct regmap *regmap;
@@ -119,6 +158,7 @@ static struct iio_event_spec adxl345_events[] = {
BIT(IIO_CHAN_INFO_CALIBBIAS), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.scan_index = (index), \
.scan_type = { \
.sign = 's', \
@@ -446,14 +486,59 @@ static int adxl345_set_ff_time(struct adxl345_state *st, u32 val_int,
return regmap_write(st->regmap, ADXL345_REG_TIME_FF, min(regval, 0xff));
}
+static int adxl345_find_odr(struct adxl345_state *st, int val,
+ int val2, enum adxl345_odr *odr)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(adxl345_odr_tbl); i++) {
+ if (val == adxl345_odr_tbl[i][0] &&
+ val2 == adxl345_odr_tbl[i][1]) {
+ *odr = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
+{
+ 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;
+
+ return 0;
+}
+
+static int adxl345_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = (int *)adxl345_odr_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(adxl345_odr_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ }
+
+ return -EINVAL;
+}
+
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct adxl345_state *st = iio_priv(indio_dev);
__le16 accel;
- long long samp_freq_nhz;
unsigned int regval;
+ enum adxl345_odr odr;
int ret;
switch (mask) {
@@ -491,12 +576,10 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val);
if (ret)
return ret;
-
- samp_freq_nhz = ADXL345_BASE_RATE_NANO_HZ <<
- (regval & ADXL345_BW_RATE);
- *val = div_s64_rem(samp_freq_nhz, NANOHZ_PER_HZ, val2);
-
- return IIO_VAL_INT_PLUS_NANO;
+ odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval);
+ *val = adxl345_odr_tbl[odr][0];
+ *val2 = adxl345_odr_tbl[odr][1];
+ return IIO_VAL_INT_PLUS_MICRO;
}
return -EINVAL;
@@ -507,7 +590,12 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
int val, int val2, long mask)
{
struct adxl345_state *st = iio_priv(indio_dev);
- s64 n;
+ enum adxl345_odr odr;
+ int ret;
+
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
switch (mask) {
case IIO_CHAN_INFO_CALIBBIAS:
@@ -515,20 +603,26 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
* 8-bit resolution at +/- 2g, that is 4x accel data scale
* factor
*/
- return regmap_write(st->regmap,
- ADXL345_REG_OFS_AXIS(chan->address),
- val / 4);
+ ret = regmap_write(st->regmap,
+ ADXL345_REG_OFS_AXIS(chan->address),
+ val / 4);
+ if (ret)
+ return ret;
+ break;
case IIO_CHAN_INFO_SAMP_FREQ:
- n = div_s64(val * NANOHZ_PER_HZ + val2,
- ADXL345_BASE_RATE_NANO_HZ);
+ ret = adxl345_find_odr(st, val, val2, &odr);
+ if (ret)
+ return ret;
- return regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE,
- ADXL345_BW_RATE,
- clamp_val(ilog2(n), 0,
- ADXL345_BW_RATE));
+ ret = adxl345_set_odr(st, odr);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
}
- return -EINVAL;
+ return adxl345_set_measure_en(st, true);
}
static int adxl345_read_event_config(struct iio_dev *indio_dev,
@@ -754,7 +848,7 @@ static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_CALIBBIAS:
return IIO_VAL_INT;
case IIO_CHAN_INFO_SAMP_FREQ:
- return IIO_VAL_INT_PLUS_NANO;
+ return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
@@ -767,19 +861,6 @@ static void adxl345_powerdown(void *ptr)
adxl345_set_measure_en(st, false);
}
-static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
-"0.09765625 0.1953125 0.390625 0.78125 1.5625 3.125 6.25 12.5 25 50 100 200 400 800 1600 3200"
-);
-
-static struct attribute *adxl345_attrs[] = {
- &iio_const_attr_sampling_frequency_available.dev_attr.attr,
- NULL
-};
-
-static const struct attribute_group adxl345_attrs_group = {
- .attrs = adxl345_attrs,
-};
-
static int adxl345_set_fifo(struct adxl345_state *st)
{
unsigned int intio;
@@ -1040,9 +1121,9 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
}
static const struct iio_info adxl345_info = {
- .attrs = &adxl345_attrs_group,
.read_raw = adxl345_read_raw,
.write_raw = adxl345_write_raw,
+ .read_avail = adxl345_read_avail,
.write_raw_get_fmt = adxl345_write_raw_get_fmt,
.read_event_config = adxl345_read_event_config,
.write_event_config = adxl345_write_event_config,
@@ -1112,6 +1193,15 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
indio_dev->num_channels = ARRAY_SIZE(adxl345_channels);
indio_dev->available_scan_masks = adxl345_scan_masks;
+ /*
+ * Using I2C at 100kHz would limit the maximum ODR to 200Hz, operation
+ * at an output rate above the recommended maximum may result in
+ * undesired behavior.
+ */
+ ret = adxl345_set_odr(st, ADXL345_ODR_200HZ);
+ if (ret)
+ return ret;
+
/* Reset interrupts at start up */
ret = regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
if (ret)
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH v4 09/14] iio: accel: adxl345: extend sample frequency adjustments
2025-03-13 16:50 ` [PATCH v4 09/14] iio: accel: adxl345: extend sample frequency adjustments Lothar Rubusch
@ 2025-03-16 11:26 ` Jonathan Cameron
0 siblings, 0 replies; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-16 11:26 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 13 Mar 2025 16:50:44 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Introduce enums and functions to work with the sample frequency
> adjustments. Let the sample frequency adjust via IIO and configure
> a reasonable default.
>
> Replace the old static sample frequency handling. The patch is in
> preparation for activity/inactivity handling. During adjustment of
> bw registers, measuring is disabled and afterwards enabled again.
It's a good feature to have on it's own. I'd drop the reference
to it being in preparation for the activity stuff. Maybe I'm missing
some reason it doesn't make sense without that?
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v4 10/14] iio: accel: adxl345: add g-range configuration
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (8 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 09/14] iio: accel: adxl345: extend sample frequency adjustments Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:28 ` Jonathan Cameron
2025-03-13 16:50 ` [PATCH v4 11/14] iio: accel: adxl345: add activity event feature Lothar Rubusch
` (3 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Introduce means to configure and work with the available g-ranges
keeping the precision of 13 digits.
This is in preparation for the activity/inactivity feature.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 96 +++++++++++++++++++++++++++++++-
1 file changed, 93 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 4c5084497def..de9202126f70 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -86,6 +86,13 @@ enum adxl345_odr {
ADXL345_ODR_3200HZ,
};
+enum adxl345_range {
+ ADXL345_2G_RANGE = 0,
+ ADXL345_4G_RANGE,
+ ADXL345_8G_RANGE,
+ ADXL345_16G_RANGE,
+};
+
/* Certain features recommend 12.5 Hz - 400 Hz ODR */
static const int adxl345_odr_tbl[][2] = {
[ADXL345_ODR_0P10HZ] = { 0, 97000 },
@@ -106,6 +113,33 @@ static const int adxl345_odr_tbl[][2] = {
[ADXL345_ODR_3200HZ] = { 3200, 0 },
};
+/*
+ * Full resolution frequency table:
+ * (g * 2 * 9.80665) / (2^(resolution) - 1)
+ *
+ * resolution := 13 (full)
+ * g := 2|4|8|16
+ *
+ * 2g at 13bit: 0.004789
+ * 4g at 13bit: 0.009578
+ * 8g at 13bit: 0.019156
+ * 16g at 16bit: 0.038312
+ */
+static const int adxl345_fullres_range_tbl[][2] = {
+ [ADXL345_2G_RANGE] = { 0, 4789 },
+ [ADXL345_4G_RANGE] = { 0, 9578 },
+ [ADXL345_8G_RANGE] = { 0, 19156 },
+ [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;
@@ -158,7 +192,8 @@ static struct iio_event_spec adxl345_events[] = {
BIT(IIO_CHAN_INFO_CALIBBIAS), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
- .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.scan_index = (index), \
.scan_type = { \
.sign = 's', \
@@ -515,12 +550,46 @@ static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
return 0;
}
+static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
+ enum adxl345_range *range)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(adxl345_fullres_range_tbl); i++) {
+ if (val == adxl345_fullres_range_tbl[i][0] &&
+ val2 == adxl345_fullres_range_tbl[i][1]) {
+ *range = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
+{
+ int 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;
+
+ return 0;
+}
+
static int adxl345_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type,
int *length, long mask)
{
switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (int *)adxl345_fullres_range_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(adxl345_fullres_range_tbl) * 2;
+ return IIO_AVAIL_LIST;
case IIO_CHAN_INFO_SAMP_FREQ:
*vals = (int *)adxl345_odr_tbl;
*type = IIO_VAL_INT_PLUS_MICRO;
@@ -539,6 +608,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
__le16 accel;
unsigned int regval;
enum adxl345_odr odr;
+ enum adxl345_range range;
int ret;
switch (mask) {
@@ -557,8 +627,12 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
*val = sign_extend32(le16_to_cpu(accel), 12);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- *val = 0;
- *val2 = st->info->uscale;
+ ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val);
+ if (ret)
+ return ret;
+ range = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
+ *val = adxl345_fullres_range_tbl[range][0];
+ *val2 = adxl345_fullres_range_tbl[range][1];
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_CALIBBIAS:
ret = regmap_read(st->regmap,
@@ -590,6 +664,7 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
int val, int val2, long mask)
{
struct adxl345_state *st = iio_priv(indio_dev);
+ enum adxl345_range range;
enum adxl345_odr odr;
int ret;
@@ -618,6 +693,15 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
if (ret)
return ret;
break;
+ case IIO_CHAN_INFO_SCALE:
+ ret = adxl345_find_range(st, val, val2, &range);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_range(st, range);
+ if (ret)
+ return ret;
+ break;
default:
return -EINVAL;
}
@@ -847,6 +931,8 @@ static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_CALIBBIAS:
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_SAMP_FREQ:
return IIO_VAL_INT_PLUS_MICRO;
default:
@@ -1202,6 +1288,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ ret = adxl345_set_range(st, ADXL345_16G_RANGE);
+ if (ret)
+ return ret;
+
/* Reset interrupts at start up */
ret = regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
if (ret)
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH v4 10/14] iio: accel: adxl345: add g-range configuration
2025-03-13 16:50 ` [PATCH v4 10/14] iio: accel: adxl345: add g-range configuration Lothar Rubusch
@ 2025-03-16 11:28 ` Jonathan Cameron
0 siblings, 0 replies; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-16 11:28 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 13 Mar 2025 16:50:45 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Introduce means to configure and work with the available g-ranges
> keeping the precision of 13 digits.
>
> This is in preparation for the activity/inactivity feature.
As with previous. This seems to be a separate feature to me and
that comment raises questions that we don't need to raise so
just drop it.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
> +
> +static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
> +{
> + int 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;
return regmap_update_bits() unless there is more coming here in later patches.
> +
> + return 0;
> +}
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v4 11/14] iio: accel: adxl345: add activity event feature
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (9 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 10/14] iio: accel: adxl345: add g-range configuration Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:32 ` Jonathan Cameron
2025-03-13 16:50 ` [PATCH v4 12/14] iio: accel: adxl345: add inactivity feature Lothar Rubusch
` (2 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Make the sensor detect and issue interrupts at activity. Activity
events are configured by a threshold stored in regmap cache. Initialize
the activity threshold register to a reasonable default value in probe.
The value is taken from the older ADXL345 input driver, to provide a
similar behavior. Reset the activity/inactivity direction enabling
register in probe. Reset and initialization shall bring the sensor in a
defined initial state to prevent dangling settings when warm restarting
the sensor.
Activity, ODR configuration together with the range setting prepare the
activity/inactivity hystersesis setup, implemented in a follow up patch.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 222 ++++++++++++++++++++++++++++++-
1 file changed, 219 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index de9202126f70..98a781987b63 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -35,6 +35,7 @@
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
+#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
enum adxl345_axis {
ADXL345_Z_EN = BIT(0),
@@ -67,6 +68,23 @@ 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,
+};
+
+static const unsigned int adxl345_act_axis_msk[] = {
+ [ADXL345_ACTIVITY] = ADXL345_REG_ACT_AXIS_MSK,
+};
+
enum adxl345_odr {
ADXL345_ODR_0P10HZ = 0,
ADXL345_ODR_0P20HZ,
@@ -157,6 +175,13 @@ struct adxl345_state {
};
static struct iio_event_spec adxl345_events[] = {
+ {
+ /* activity */
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+ },
{
/* single tap */
.type = IIO_EV_TYPE_GESTURE,
@@ -259,6 +284,108 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
return regmap_write(st->regmap, ADXL345_REG_POWER_CTL, val);
}
+/* act/inact */
+
+static int adxl345_is_act_inact_en(struct adxl345_state *st,
+ enum iio_modifier axis,
+ enum adxl345_activity_type type, bool *en)
+{
+ unsigned int regval;
+ bool axis_en;
+ u32 axis_ctrl;
+ int act_shift;
+ int ret;
+
+ act_shift = (type == ADXL345_ACTIVITY) ? 4 : 0;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ axis_ctrl = axis_ctrl >> act_shift;
+
+ switch (axis) {
+ case IIO_MOD_X:
+ axis_en = FIELD_GET(ADXL345_X_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Y:
+ axis_en = FIELD_GET(ADXL345_Y_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Z:
+ axis_en = FIELD_GET(ADXL345_Z_EN, axis_ctrl);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val);
+ if (ret)
+ return ret;
+
+ *en = (adxl345_act_int_reg[type] & regval) > 0;
+
+ return 0;
+}
+
+static int adxl345_set_act_inact_en(struct adxl345_state *st,
+ enum iio_modifier axis,
+ enum adxl345_activity_type type,
+ bool cmd_en)
+{
+ bool axis_en, en;
+ unsigned int threshold;
+ u32 axis_ctrl;
+ /*
+ * Activity and inactivity share the same register for enabling
+ * directions. Activity uses the four upper bits, where inactivity
+ * detection uses the lower bits. In order to keep generic x, y
+ * and z-axis type, it is shifted for initialization of activity.
+ * NB: Activity and inactivitty axis enable bits are regmap cached.
+ */
+ int act_shift = (type == ADXL345_ACTIVITY) ? 4 : 0;
+ int ret;
+
+ switch (axis) {
+ case IIO_MOD_X:
+ axis_ctrl = ADXL345_X_EN;
+ break;
+ case IIO_MOD_Y:
+ axis_ctrl = ADXL345_Y_EN;
+ break;
+ case IIO_MOD_Z:
+ axis_ctrl = ADXL345_Z_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ axis_ctrl = axis_ctrl << act_shift;
+
+ if (cmd_en)
+ ret = regmap_set_bits(st->regmap,
+ ADXL345_REG_ACT_INACT_CTRL, axis_ctrl);
+ else
+ ret = regmap_clear_bits(st->regmap,
+ ADXL345_REG_ACT_INACT_CTRL, axis_ctrl);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, adxl345_act_thresh_reg[type], &threshold);
+ if (ret)
+ return ret;
+
+ en = false;
+
+ if (type == ADXL345_ACTIVITY) {
+ axis_en = FIELD_GET(ADXL345_REG_ACT_AXIS_MSK, axis_ctrl) > 0;
+ en = axis_en && threshold > 0;
+ }
+
+ return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_act_int_reg[type],
+ en ? adxl345_act_int_reg[type] : 0);
+}
+
/* tap */
static int _adxl345_set_tap_int(struct adxl345_state *st,
@@ -719,6 +846,18 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
int ret = -EFAULT;
switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = adxl345_is_act_inact_en(st, chan->channel2,
+ ADXL345_ACTIVITY,
+ &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ default:
+ return -EINVAL;
+ }
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -755,6 +894,14 @@ 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_THRESH:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl345_set_act_inact_en(st, chan->channel2,
+ ADXL345_ACTIVITY, state);
+ default:
+ return -EINVAL;
+ }
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -779,11 +926,31 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
int *val, int *val2)
{
struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int act_threshold;
unsigned int tap_threshold;
unsigned int ff_threshold;
int ret;
switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ &act_threshold);
+ if (ret)
+ return ret;
+
+ *val = act_threshold;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -850,6 +1017,23 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
return ret;
switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ val);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ break;
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -1105,7 +1289,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);
@@ -1132,6 +1317,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_THRESH,
+ IIO_EV_DIR_RISING),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) {
ret = iio_push_event(indio_dev,
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
@@ -1164,10 +1359,12 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
*/
static irqreturn_t adxl345_irq_handler(int irq, void *p)
{
+ const int act_shift = 4;
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;
+ enum iio_modifier act_dir = IIO_NO_MOD;
u32 axis_ctrl;
int int_stat;
int ret;
@@ -1176,7 +1373,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;
@@ -1187,12 +1385,22 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
tap_dir = IIO_MOD_Y;
else if (FIELD_GET(ADXL345_X_EN, regval))
tap_dir = IIO_MOD_X;
+
+ /* Activity direction is stored in the upper four bits */
+ regval >>= act_shift;
+
+ if (FIELD_GET(ADXL345_Z_EN, regval))
+ act_dir = IIO_MOD_Z;
+ else if (FIELD_GET(ADXL345_Y_EN, regval))
+ act_dir = IIO_MOD_Y;
+ else if (FIELD_GET(ADXL345_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))
@@ -1357,6 +1565,14 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ 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] 33+ messages in thread* Re: [PATCH v4 11/14] iio: accel: adxl345: add activity event feature
2025-03-13 16:50 ` [PATCH v4 11/14] iio: accel: adxl345: add activity event feature Lothar Rubusch
@ 2025-03-16 11:32 ` Jonathan Cameron
0 siblings, 0 replies; 33+ messages in thread
From: Jonathan Cameron @ 2025-03-16 11:32 UTC (permalink / raw)
To: Lothar Rubusch
Cc: lars, Michael.Hennerich, linux-iio, linux-kernel, eraretuya
On Thu, 13 Mar 2025 16:50:46 +0000
Lothar Rubusch <l.rubusch@gmail.com> wrote:
> Make the sensor detect and issue interrupts at activity. Activity
> events are configured by a threshold stored in regmap cache. Initialize
> the activity threshold register to a reasonable default value in probe.
> The value is taken from the older ADXL345 input driver, to provide a
> similar behavior. Reset the activity/inactivity direction enabling
> register in probe. Reset and initialization shall bring the sensor in a
> defined initial state to prevent dangling settings when warm restarting
> the sensor.
>
> Activity, ODR configuration together with the range setting prepare the
> activity/inactivity hystersesis setup, implemented in a follow up patch.
>
> Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
A few things inline.
Jonathan
> @@ -1176,7 +1373,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;
> @@ -1187,12 +1385,22 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
> tap_dir = IIO_MOD_Y;
> else if (FIELD_GET(ADXL345_X_EN, regval))
> tap_dir = IIO_MOD_X;
> +
> + /* Activity direction is stored in the upper four bits */
> + regval >>= act_shift;
As per feedback on v3 (late feedback that is), just add more defines
for these bits as the reuse adds connections that are kind of
coincidence rather than fundamental (bits 'could' have been defined
in any order or non contiguous) and lead to less readable code.
> +
> + if (FIELD_GET(ADXL345_Z_EN, regval))
> + act_dir = IIO_MOD_Z;
> + else if (FIELD_GET(ADXL345_Y_EN, regval))
> + act_dir = IIO_MOD_Y;
> + else if (FIELD_GET(ADXL345_X_EN, regval))
> + act_dir = IIO_MOD_X;
> }
>
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v4 12/14] iio: accel: adxl345: add inactivity feature
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (10 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 11/14] iio: accel: adxl345: add activity event feature Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 13/14] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 14/14] docs: iio: add documentation for adxl345 driver Lothar Rubusch
13 siblings, 0 replies; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add the inactivity feature of the sensor. When activity and inactivity
are enabled, a link bit will be set linking activity and inactivity
handling. Additionally, the auto-sleep mode will be enabled. Due to the
link bit the sensor is going to auto-sleep when inactivity was
detected.
Inactivity detection needs a threshold to be configured, and a time
after which it will go into inactivity state if measurements under
threshold.
When a ODR is configured this time for inactivity is adjusted with a
corresponding reasonable default value, in order to have higher
frequencies and lower inactivity times, and lower sample frequency but
give more time until inactivity. Both with reasonable upper and lower
boundaries, since many of the sensor's features (e.g. auto-sleep) will
need to operate beween 12.5 Hz and 400 Hz. This is a default setting
when actively changing sample frequency, explicitly setting the time
until inactivity will overwrite the default.
Similarly, setting the g-range will provide a default value for the
activity and inactivity thresholds. Both are implicit defaults, but
equally can be overwritten to be explicitly configured.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 132 ++++++++++++++++++++++++++++++-
1 file changed, 128 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 98a781987b63..c718d529d897 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -36,6 +36,8 @@
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
#define ADXL345_REG_TAP_SUPPRESS_MSK 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)
enum adxl345_axis {
ADXL345_Z_EN = BIT(0),
@@ -71,18 +73,22 @@ 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,
};
static const unsigned int adxl345_act_axis_msk[] = {
[ADXL345_ACTIVITY] = ADXL345_REG_ACT_AXIS_MSK,
+ [ADXL345_INACTIVITY] = ADXL345_REG_INACT_AXIS_MSK,
};
enum adxl345_odr {
@@ -182,6 +188,14 @@ static struct iio_event_spec adxl345_events[] = {
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
},
+ {
+ /* inactivity */
+ .type = IIO_EV_TYPE_THRESH,
+ .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),
+ },
{
/* single tap */
.type = IIO_EV_TYPE_GESTURE,
@@ -333,6 +347,7 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
bool cmd_en)
{
bool axis_en, en;
+ unsigned int inact_time_s;
unsigned int threshold;
u32 axis_ctrl;
/*
@@ -379,11 +394,67 @@ static int adxl345_set_act_inact_en(struct adxl345_state *st,
if (type == ADXL345_ACTIVITY) {
axis_en = FIELD_GET(ADXL345_REG_ACT_AXIS_MSK, axis_ctrl) > 0;
en = axis_en && threshold > 0;
+ } else {
+ ret = regmap_read(st->regmap, ADXL345_REG_TIME_INACT, &inact_time_s);
+ if (ret)
+ return ret;
+
+ axis_en = FIELD_GET(ADXL345_REG_INACT_AXIS_MSK, axis_ctrl) > 0;
+ en = axis_en && threshold > 0 && inact_time_s > 0;
}
- return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
- adxl345_act_int_reg[type],
- en ? adxl345_act_int_reg[type] : 0);
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_act_int_reg[type],
+ en ? adxl345_act_int_reg[type] : 0);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(st->regmap, ADXL345_REG_POWER_CTL,
+ ADXL345_POWER_CTL_INACT_MSK,
+ en ? (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
+ : 0);
+}
+
+/**
+ * adxl345_set_inact_time_s - Configure inactivity time explicitly or by ODR.
+ * @st: The sensor state instance.
+ * @val_s: A desired time value, between 0 and 255.
+ *
+ * If val_s is 0, a default inactivity time will be computed. It should take
+ * power consumption into consideration. Thus it shall be shorter for higher
+ * frequencies and longer for lower frequencies. Hence, frequencies above 255 Hz
+ * shall default to 10 s and frequencies below 10 Hz shall result in 255 s to
+ * detect inactivity.
+ *
+ * The approach simply subtracts the pre-decimal figure of the configured
+ * sample frequency from 255 s to compute inactivity time [s]. Sub-Hz are thus
+ * ignored in this estimation. The recommended ODRs for various features
+ * (activity/inactivity, sleep modes, free fall, etc.) lie between 12.5 Hz and
+ * 400 Hz, thus higher or lower frequencies will result in the boundary
+ * defaults or need to be explicitly specified via val_s.
+ *
+ * Return: 0 or error value.
+ */
+static int adxl345_set_inact_time_s(struct adxl345_state *st, u32 val_s)
+{
+ unsigned int max_boundary = 255;
+ unsigned int min_boundary = 10;
+ unsigned int val = min(val_s, max_boundary);
+ 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 = (adxl345_odr_tbl[odr][0] > max_boundary)
+ ? min_boundary : max_boundary - adxl345_odr_tbl[odr][0];
+ }
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val);
}
/* tap */
@@ -855,6 +926,13 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
if (ret)
return ret;
return int_en;
+ case IIO_EV_DIR_FALLING:
+ ret = adxl345_is_act_inact_en(st, chan->channel2,
+ ADXL345_INACTIVITY,
+ &int_en);
+ if (ret)
+ return ret;
+ return int_en;
default:
return -EINVAL;
}
@@ -899,6 +977,9 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
case IIO_EV_DIR_RISING:
return adxl345_set_act_inact_en(st, chan->channel2,
ADXL345_ACTIVITY, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_act_inact_en(st, chan->channel2,
+ ADXL345_INACTIVITY, state);
default:
return -EINVAL;
}
@@ -926,7 +1007,8 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
int *val, int *val2)
{
struct adxl345_state *st = iio_priv(indio_dev);
- unsigned int act_threshold;
+ unsigned int act_threshold, inact_threshold;
+ unsigned int inact_time_s;
unsigned int tap_threshold;
unsigned int ff_threshold;
int ret;
@@ -945,9 +1027,24 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
*val = act_threshold;
return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ &inact_threshold);
+ if (ret)
+ return ret;
+
+ *val = inact_threshold;
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
+ case IIO_EV_INFO_PERIOD:
+ ret = regmap_read(st->regmap, ADXL345_REG_TIME_INACT, &inact_time_s);
+ if (ret)
+ return ret;
+ *val = inact_time_s;
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
@@ -1026,10 +1123,18 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
adxl345_act_thresh_reg[ADXL345_ACTIVITY],
val);
break;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ val);
+ break;
default:
ret = -EINVAL;
}
break;
+ case IIO_EV_INFO_PERIOD:
+ ret = adxl345_set_inact_time_s(st, val);
+ break;
default:
ret = -EINVAL;
}
@@ -1327,6 +1432,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_OR_Y_OR_Z,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_FALLING),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) {
ret = iio_push_event(indio_dev,
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
@@ -1569,10 +1685,18 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ ret = regmap_write(st->regmap, ADXL345_REG_TIME_INACT, 3);
+ 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] 33+ messages in thread* [PATCH v4 13/14] iio: accel: adxl345: add coupling detection for activity/inactivity
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (11 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 12/14] iio: accel: adxl345: add inactivity feature Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-13 16:50 ` [PATCH v4 14/14] docs: iio: add documentation for adxl345 driver Lothar Rubusch
13 siblings, 0 replies; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, eraretuya, l.rubusch
Add coupling activity/inactivity detection by the AC/DC bit. This is an
addititional enhancement for the detection of activity states and
completes the activity / inactivity feature of the ADXL345.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/iio/accel/adxl345_core.c | 150 ++++++++++++++++++++++++++++++-
1 file changed, 148 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index c718d529d897..1cbe431cda71 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -36,7 +36,9 @@
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
#define ADXL345_REG_ACT_AXIS_MSK GENMASK(6, 4)
+#define ADXL345_REG_ACT_ACDC_MSK BIT(7)
#define ADXL345_REG_INACT_AXIS_MSK GENMASK(2, 0)
+#define ADXL345_REG_INACT_ACDC_MSK BIT(3)
#define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
enum adxl345_axis {
@@ -86,6 +88,11 @@ static const unsigned int adxl345_act_thresh_reg[] = {
[ADXL345_INACTIVITY] = 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,
+};
+
static const unsigned int adxl345_act_axis_msk[] = {
[ADXL345_ACTIVITY] = ADXL345_REG_ACT_AXIS_MSK,
[ADXL345_INACTIVITY] = ADXL345_REG_INACT_AXIS_MSK,
@@ -220,6 +227,18 @@ static struct iio_event_spec adxl345_events[] = {
BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_PERIOD),
},
+ {
+ /* activity, activity - ac bit */
+ .type = IIO_EV_TYPE_MAG_REFERENCED,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ /* activity, inactivity - ac bit */
+ .type = IIO_EV_TYPE_MAG_REFERENCED,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
+ },
};
#define ADXL345_CHANNEL(index, reg, axis) { \
@@ -300,6 +319,69 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
/* act/inact */
+static int adxl345_is_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type, bool *ac)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val);
+ if (ret)
+ return ret;
+
+ if (type == ADXL345_ACTIVITY)
+ *ac = (FIELD_GET(ADXL345_REG_ACT_ACDC_MSK, regval) > 0);
+ else
+ *ac = (FIELD_GET(ADXL345_REG_INACT_ACDC_MSK, regval) > 0);
+
+ return 0;
+}
+
+static int adxl345_set_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type, bool ac)
+{
+ unsigned int act_inact_ac = ac ? 0xff : 0x00;
+
+ /*
+ * 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_update_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 iio_modifier axis,
enum adxl345_activity_type type, bool *en)
@@ -745,7 +827,8 @@ static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
if (ret)
return ret;
- return 0;
+ /* update inactivity time by ODR */
+ return adxl345_set_inact_time_s(st, 0);
}
static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
@@ -766,15 +849,51 @@ 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)
{
+ 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;
- return 0;
+ act_threshold = act_threshold
+ * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ act_threshold = min(255, max(1, inact_threshold));
+
+ inact_threshold = inact_threshold
+ * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ inact_threshold = min(255, 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,
@@ -914,6 +1033,8 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
{
struct adxl345_state *st = iio_priv(indio_dev);
bool int_en;
+ bool act_ac;
+ bool inact_ac;
int ret = -EFAULT;
switch (type) {
@@ -958,6 +1079,21 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
if (ret)
return ret;
return int_en;
+ case IIO_EV_TYPE_MAG_REFERENCED:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = adxl345_is_act_inact_ac(st, ADXL345_ACTIVITY, &act_ac);
+ if (ret)
+ return ret;
+ return act_ac;
+ case IIO_EV_DIR_FALLING:
+ ret = adxl345_is_act_inact_ac(st, ADXL345_INACTIVITY, &inact_ac);
+ if (ret)
+ return ret;
+ return inact_ac;
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -994,6 +1130,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
}
case IIO_EV_TYPE_MAG:
return adxl345_set_ff_en(st, state);
+ case IIO_EV_TYPE_MAG_REFERENCED:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl345_set_act_inact_ac(st, ADXL345_ACTIVITY, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_act_inact_ac(st, ADXL345_INACTIVITY, state);
+ default:
+ return -EINVAL;
+ }
+
default:
return -EINVAL;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH v4 14/14] docs: iio: add documentation for adxl345 driver
2025-03-13 16:50 [PATCH v4 00/14] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
` (12 preceding siblings ...)
2025-03-13 16:50 ` [PATCH v4 13/14] iio: accel: adxl345: add coupling detection for activity/inactivity Lothar Rubusch
@ 2025-03-13 16:50 ` Lothar Rubusch
2025-03-16 11:37 ` Jonathan Cameron
13 siblings, 1 reply; 33+ messages in thread
From: Lothar Rubusch @ 2025-03-13 16:50 UTC (permalink / raw)
To: lars, Michael.Hennerich, jic23
Cc: linux-iio, linux-kernel, 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 | 416 ++++++++++++++++++++++++++++++++++
1 file changed, 416 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..683121c1a435
--- /dev/null
+++ b/Documentation/iio/adxl345.rst
@@ -0,0 +1,416 @@
+.. 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
+-------------
+
+Particular IIO events will be triggered by the corresponding interrupts. The
+sensor driver supports no or one active INT line, where the sensor has two
+possible INT IOs. Configure the used INT line in the devicetree. If no INT line
+is configured, the sensor falls back to FIFO bypass mode and no events are
+possible, only X, Y and Z axis measurements are possible.
+
+The following table shows the ADXL345 related device files, found in the
+specific device folder path ``/sys/bus/iio/devices/iio:deviceX/events``.
+
++---------------------------------------------+-----------------------------------------+
+| 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_en | Enable free fall detection |
++---------------------------------------------+-----------------------------------------+
+| in_accel_mag_falling_period | Free fall time in [us] |
++---------------------------------------------+-----------------------------------------+
+| in_accel_mag_falling_value | Free fall threshold value in 62.5/LSB |
++---------------------------------------------+-----------------------------------------+
+| in_accel_mag_referenced_falling_en | Set 1 to AC-coupled inactivity, 0 for DC|
++---------------------------------------------+-----------------------------------------+
+| in_accel_mag_referenced_rising_en | Set 1 to AC-coupled activity, 0 for DC |
++---------------------------------------------+-----------------------------------------+
+| in_accel_x_thresh_falling_en | Enable inactivity detection on X axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_y_thresh_falling_en | Enable inactivity detection on Y axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_z_thresh_falling_en | Enable inactivity detection on Z axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_thresh_falling_period | Inactivity time in seconds |
++---------------------------------------------+-----------------------------------------+
+| in_accel_thresh_falling_value | Inactivity threshold value in 62.5/LSB |
++---------------------------------------------+-----------------------------------------+
+| in_accel_x_thresh_rising_en | Enable activity detection on X axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_y_thresh_rising_en | Enable activity detection on Y axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_z_thresh_rising_en | Enable activity detection on Z axis |
++---------------------------------------------+-----------------------------------------+
+| in_accel_thresh_rising_value | Activity threshold value in 62.5/LSB |
++---------------------------------------------+-----------------------------------------+
+| in_accel_x_gesture_singletap_en | Enable single tap detection on X 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 |
++---------------------------------------------+-----------------------------------------+
+
+Find a detailed description of a particular functionality in the sensor
+datasheet.
+
+Setting the ODR explicitly will result in estimated adjusted default values
+for the inactivity time detection, where higher frequencies shall default to
+longer wait periods, and vice versa. It is also possible to explicetly
+configure inactivity wait times, if the defaulting approach does not match
+application requirements. Setting 0 here, will fall back to default setting.
+
+The g range configuration also tries to estimate activity and inactivity
+thresholds when switching to another g range. The default range will be
+factorized by the relation of old range divided by new range. The value never
+becomes 0 and will be at least 1 and at most 255 i.e. 62.5g/LSB according to
+the datasheet. Nevertheless activity and inactivity thresholds can be
+overwritten by explicit values.
+
+When activity and inactivity events are both enabled, the driver automatically
+will implement its hysteresis solution by setting link bit and autosleep bit.
+The link bit serially links the activity and inactivity functions. On the other
+side, the autosleep function switches the sensor to sleep mode if the
+inactivity function is enabled. This will reduce current consumption to the
+sub-12.5Hz rate.
+
+In dc-coupled operation, the current acceleration magnitude is compared
+directly with THRESH_ACT and THRESH_INACT registers to determine whether
+activity or inactivity was 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 are then compared to this reference value.
+Note, ac-coupling and dc-coupling are individually set for activity and/or
+inactivity detection. Activity and inactivity detection are dependent on the
+direction, i.e. the x/y/z axis where this driver generally enables all
+directions. Also, the direction settings are particular to activity and
+inactivity detection, respectively.
+
+Single tap detection can be configured according to the datasheet by specifying
+threshold and duration. If only the single tap is in use, the single tap
+interrupt is triggered when the acceleration goes above threshold (i.e. DUR
+start) and below the threshold, as long as duration is not exceeded. If single
+tap and double tap are in use, the single tap is triggered when the doulbe tap
+event has been either validated or invalidated.
+
+For double tap configure additionally window and latency in [us]. Latency
+starts counting when the single tap goes below threshold and is a waiting
+period, any spikes here are ignored for double tap detection. After latency,
+the window starts. Any rise above threshold, with a consequent fall below
+threshold within window time, rises a double tap signal when going below
+threshold.
+
+Double tap event detection is best described in the datasheet. After a
+single tap event was detected, a double tap event can be detected. Therefore the
+signal must match several criteria, and detection can also considered invalid
+for three reasons:
+* If the suppress bit is set and when still in the tap latency period, any
+measurement of acceleration spike above the tap threshold invalidates double tap
+detection immediately, i.e. during latency must not occur spikes for double tap
+detection when the suppress bit is set.
+* A double tap event is considered invalid, if acceleration lies above the
+threshold at the start of the window time for double tap.
+* Additionally, double tap detection can be considered invalid, if an
+acceleration exceeds the time limit for taps, set by duration register.
+Note, since for double tap the same duration counts, i.e. when rising above
+threshold, a consequent falling below threshold has to be within duration time.
+Also note, the suppress bit is generally set when double tap is enabled.
+
+A free fall event will be detected if the signal goes below the configured
+threshold, for the configured time [us].
+
+Note, that activity/inactivy, as also freefall is recommended for 12.5 Hz ODR
+up to 400 Hz.
+
+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 forumla:
+
+.. 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
+
+ ## activity, threshold [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 6 > ./events/in_accel_thresh_rising_value
+
+ ## inactivity, threshold, [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 4 > ./events/in_accel_thresh_falling_value
+
+ ## inactivity, time [s]
+ root:/sys/bus/iio/devices/iio:device0> echo 3 > ./events/in_accel_thresh_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
+
+ ## freefall, threshold [62.5/LSB]
+ root:/sys/bus/iio/devices/iio:device0> echo 8 > ./events/in_accel_mag_falling_value
+
+ ## freefall, time [ms]
+ root:/sys/bus/iio/devices/iio:device0> echo 1.25 > ./events/in_accel_mag_falling_period
+
+ ## activity, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_thresh_rising_en
+
+ ## inactivity, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_thresh_falling_en
+
+ ## freefall, enable
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_mag_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: thresh, direction: rising
+ Event: time: 1739063415963770218, type: accel(z), channel: 0, evtype: thresh, 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: thresh, direction: falling
+ Event: time: 1739063436539080713, type: accel(x|y|z), channel: 0, evtype: thresh, direction: falling
+ Event: time: 1739063438357970381, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446726161586, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446727892670, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446743019768, type: accel(z), channel: 0, evtype: thresh, direction: rising
+ Event: time: 1739063446744650696, type: accel(z), channel: 0, evtype: thresh, 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: thresh, 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.
--
2.39.5
^ permalink raw reply related [flat|nested] 33+ messages in thread