public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Lothar Rubusch <l.rubusch@gmail.com>
To: lars@metafoo.de, Michael.Hennerich@analog.com, jic23@kernel.org
Cc: linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org,
	eraretuya@gmail.com, l.rubusch@gmail.com
Subject: [PATCH v1 06/12] iio: accel: adxl345: add single tap feature
Date: Tue, 28 Jan 2025 12:00:54 +0000	[thread overview]
Message-ID: <20250128120100.205523-7-l.rubusch@gmail.com> (raw)
In-Reply-To: <20250128120100.205523-1-l.rubusch@gmail.com>

Add the single tap feature with a threshold in 62.5mg/LSB points and a
scaled duration in us. Keep singletap threshold by means of IIO but add
sysfs entry for the duration. Using a sysfs entry allow for a clearer
naming of the handle to improve usage. Extend the channels for single
enable x/y/z axis of the feature but also check if threshold (a.k.a
"value") and duration have reasonable content. When an interrupt is
caught it will be pushed to the according IIO channel.

The function call structure is in preparation to be extended for an
upcoming doubletap feature in the follow up patches.

Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
 drivers/iio/accel/adxl345_core.c | 367 +++++++++++++++++++++++++++++++
 1 file changed, 367 insertions(+)

diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index b55f6774b1e9..0d991f3ec10c 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -15,6 +15,7 @@
 #include <linux/units.h>
 
 #include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/kfifo_buf.h>
 #include <linux/iio/sysfs.h>
@@ -118,6 +119,16 @@
 #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),
+};
+
 struct adxl345_state {
 	const struct adxl345_chip_info *info;
 	struct regmap *regmap;
@@ -127,9 +138,24 @@ struct adxl345_state {
 	u8 int_map;
 	u8 watermark;
 	u8 fifo_mode;
+
+	u32 tap_axis_ctrl;
+	u8 tap_threshold;
+	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),
+	},
+};
+
 #define ADXL345_CHANNEL(index, reg, axis) {					\
 	.type = IIO_ACCEL,						\
 	.modified = 1,							\
@@ -146,6 +172,8 @@ struct adxl345_state {
 		.storagebits = 16,			\
 		.endianness = IIO_LE,			\
 	},						\
+	.event_spec = adxl345_events,				\
+	.num_event_specs = ARRAY_SIZE(adxl345_events),	\
 }
 
 enum adxl345_chans {
@@ -190,11 +218,121 @@ static void adxl345_powerdown(void *ptr)
 	adxl345_set_measure_en(st, false);
 }
 
+static inline void adxl345_intmap_switch_bit(struct adxl345_state *st,
+					     bool condition, u8 bit)
+{
+	st->int_map = condition ? st->int_map | bit : st->int_map & ~bit;
+}
+
+static inline int adxl345_read_interrupts(struct adxl345_state *st,
+					  unsigned int *interrupts)
+{
+	return regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, interrupts);
+}
+
 static inline int adxl345_write_interrupts(struct adxl345_state *st)
 {
 	return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, st->int_map);
 }
 
+/* tap */
+
+static int adxl345_write_tap_axis(struct adxl345_state *st,
+				  enum adxl345_axis axis, bool en)
+{
+	st->tap_axis_ctrl = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK,
+				      en ? st->tap_axis_ctrl | axis
+				      : st->tap_axis_ctrl & ~axis);
+
+	return regmap_update_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+				  ADXL345_REG_TAP_AXIS_MSK,
+				  FIELD_PREP(ADXL345_REG_TAP_AXIS_MSK,
+					     st->tap_axis_ctrl));
+}
+
+static int _adxl345_set_tap_int(struct adxl345_state *st, bool state)
+{
+	bool axis_valid;
+	bool singletap_args_valid = false;
+	bool en = false;
+
+	axis_valid = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, st->tap_axis_ctrl) > 0;
+
+	/*
+	 * 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 = st->tap_threshold > 0 && st->tap_duration_us > 0;
+
+	en = axis_valid && singletap_args_valid;
+
+	adxl345_intmap_switch_bit(st, state && en, ADXL345_INT_SINGLE_TAP);
+
+	return adxl345_write_interrupts(st);
+}
+
+static int adxl345_is_tap_en(struct adxl345_state *st, bool *en)
+{
+	int ret;
+	unsigned int regval;
+
+	ret = adxl345_read_interrupts(st, &regval);
+	if (ret)
+		return ret;
+
+	*en = FIELD_GET(ADXL345_INT_SINGLE_TAP, regval) > 0;
+
+	return 0;
+}
+
+static int adxl345_set_singletap_en(struct adxl345_state *st,
+				    enum adxl345_axis axis, bool en)
+{
+	int ret;
+
+	ret = adxl345_write_tap_axis(st, axis, en);
+	if (ret)
+		return ret;
+
+	return _adxl345_set_tap_int(st, en);
+}
+
+static int adxl345_set_tap_value(struct adxl345_state *st, u8 val)
+{
+	st->tap_threshold = val;
+
+	return regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, min(val, 0xFF));
+}
+
+static int _adxl345_set_tap_time(struct adxl345_state *st, u32 val_us)
+{
+	unsigned int regval;
+
+	st->tap_duration_us = val_us;
+
+	/*
+	 * The scale factor is 1250us / LSB for tap_window_us and tap_latent_us.
+	 * For tap_duration_us the scale factor is 625us / LSB.
+	 */
+	regval = DIV_ROUND_CLOSEST(val_us, 625);
+
+	return regmap_write(st->regmap, ADXL345_REG_DUR, 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, 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)
@@ -275,6 +413,141 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
 					  ADXL345_BW_RATE,
 					  clamp_val(ilog2(n), 0,
 						    ADXL345_BW_RATE));
+	default:
+		return -EINVAL;
+	}
+
+	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;
+	bool axis_en;
+	int ret = -EFAULT;
+
+	switch (type) {
+	case IIO_EV_TYPE_GESTURE:
+		switch (dir) {
+		case IIO_EV_DIR_SINGLETAP:
+			switch (chan->channel2) {
+			case IIO_MOD_X:
+				axis_en = FIELD_GET(ADXL345_X_EN, st->tap_axis_ctrl);
+				break;
+			case IIO_MOD_Y:
+				axis_en = FIELD_GET(ADXL345_Y_EN, st->tap_axis_ctrl);
+				break;
+			case IIO_MOD_Z:
+				axis_en = FIELD_GET(ADXL345_Z_EN, st->tap_axis_ctrl);
+				break;
+			default:
+				return -EINVAL;
+			}
+
+			ret = adxl345_is_tap_en(st, &int_en);
+			if (ret)
+				return ret;
+			return int_en && axis_en;
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+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);
+	enum adxl345_axis axis;
+
+	if (type != IIO_EV_TYPE_GESTURE)
+		return -EINVAL;
+
+	switch (dir) {
+	case IIO_EV_DIR_SINGLETAP:
+		switch (chan->channel2) {
+		case IIO_MOD_X:
+			axis = ADXL345_X_EN;
+			break;
+		case IIO_MOD_Y:
+			axis = ADXL345_Y_EN;
+			break;
+		case IIO_MOD_Z:
+			axis = ADXL345_Z_EN;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		return adxl345_set_singletap_en(st, axis, state);
+	default:
+		return -EINVAL;
+	}
+
+	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);
+
+	if (type != IIO_EV_TYPE_GESTURE)
+		return -EINVAL;
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		/*
+		 * The scale factor is 62.5mg/LSB (i.e. 0xFF = 16g) but
+		 * not applied here.
+		 */
+		*val = sign_extend32(st->tap_threshold, 7);
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+
+	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;
+
+	if (type != IIO_EV_TYPE_GESTURE)
+		return -EINVAL;
+
+	if (info == IIO_EV_INFO_VALUE) {
+		if (val < 0 || val > 255)
+			return -EINVAL;
+
+		ret = adxl345_set_measure_en(st, false);
+		if (ret)
+			return ret;
+
+		ret = adxl345_set_tap_value(st, val);
+		if (ret)
+			return ret;
+
+		return adxl345_set_measure_en(st, true);
 	}
 
 	return -EINVAL;
@@ -322,6 +595,58 @@ static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev,
 	}
 }
 
+#define ADXL345_generate_iio_dev_attr_FRACTIONAL(A, B, C, D, E)		\
+	static ssize_t in_accel_##A##_##C##_##E##_show(struct device *dev, \
+						       struct device_attribute *attr, \
+						       char *buf)	\
+	{								\
+		struct iio_dev *indio_dev = dev_to_iio_dev(dev);	\
+		struct adxl345_state *st = iio_priv(indio_dev);		\
+		int vals[2];						\
+									\
+		vals[0] = st->B##_##C##_##E;				\
+		vals[1] = D;						\
+									\
+		return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals); \
+	}								\
+									\
+	static ssize_t in_accel_##A##_##C##_##E##_store(struct device *dev, \
+							struct device_attribute *attr, \
+							const char *buf, size_t len) \
+	{								\
+		struct iio_dev *indio_dev = dev_to_iio_dev(dev);	\
+		struct adxl345_state *st = iio_priv(indio_dev);		\
+		int val_int, val_fract_us, ret;				\
+									\
+		ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us); \
+		if (ret)						\
+			return ret;					\
+									\
+		ret = adxl345_set_measure_en(st, false);		\
+		if (ret)						\
+			return ret;					\
+									\
+		adxl345_set_##B##_##C(st, val_int, val_fract_us);	\
+									\
+		ret = adxl345_set_measure_en(st, true);			\
+		if (ret)						\
+			return ret;					\
+									\
+		return len;						\
+	}								\
+	static IIO_DEVICE_ATTR_RW(in_accel_##A##_##C##_##E, 0)
+
+ADXL345_generate_iio_dev_attr_FRACTIONAL(gesture_singletap, tap, duration, MICRO, us);
+
+static struct attribute *adxl345_event_attrs[] = {
+	&iio_dev_attr_in_accel_gesture_singletap_duration_us.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group adxl345_event_attrs_group = {
+	.attrs = adxl345_event_attrs,
+};
+
 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"
 );
@@ -477,6 +802,17 @@ static const struct iio_buffer_setup_ops adxl345_buffer_ops = {
 
 static int adxl345_get_status(struct adxl345_state *st, unsigned int *int_stat)
 {
+	unsigned int regval;
+	bool check_tap_stat;
+
+	check_tap_stat = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, st->tap_axis_ctrl) > 0;
+
+	if (check_tap_stat) {
+		/* ACT_TAP_STATUS should be read before clearing the interrupt */
+		if (regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, &regval))
+			return -EINVAL;
+	}
+
 	return regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, int_stat);
 }
 
@@ -499,6 +835,25 @@ 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)
+{
+	s64 ts = iio_get_time_ns(indio_dev);
+	int ret;
+
+	if (FIELD_GET(ADXL345_INT_SINGLE_TAP, 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_GESTURE,
+							IIO_EV_DIR_SINGLETAP),
+				     ts);
+		if (ret)
+			return ret;
+	}
+
+	return -ENOENT;
+}
+
 /**
  * adxl345_irq_handler() - Handle irqs of the ADXL345.
  * @irq: The irq being handled.
@@ -516,6 +871,9 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
 	if (adxl345_get_status(st, &int_stat))
 		return IRQ_NONE;
 
+	if (adxl345_push_event(indio_dev, int_stat) == 0)
+		return IRQ_HANDLED;
+
 	if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
 		samples = adxl345_get_samples(st);
 		if (samples < 0)
@@ -538,9 +896,14 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
 
 static const struct iio_info adxl345_info = {
 	.attrs		= &adxl345_attrs_group,
+	.event_attrs	= &adxl345_event_attrs_group,
 	.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,
 };
@@ -588,6 +951,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
 
 	st->int_map = 0x00;			/* reset interrupts */
 
+	/* Init with reasonable values */
+	st->tap_threshold = 35;			/*   35 [0x23]            */
+	st->tap_duration_us = 3;		/*    3 [0x03] -> .001875 */
+
 	indio_dev->name = st->info->name;
 	indio_dev->info = &adxl345_info;
 	indio_dev->modes = INDIO_DIRECT_MODE;
-- 
2.39.5


  parent reply	other threads:[~2025-01-28 12:01 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-01-28 12:00 [PATCH v1 00/12] iio: accel: adxl345: add interrupt based sensor events Lothar Rubusch
2025-01-28 12:00 ` [PATCH v1 01/12] iio: accel: adxl345: migrate constants to core Lothar Rubusch
2025-02-01 16:35   ` Jonathan Cameron
2025-02-04 14:13     ` Lothar Rubusch
2025-02-04 14:46       ` Jonathan Cameron
2025-01-28 12:00 ` [PATCH v1 02/12] iio: accel: adxl345: reorganize measurement enable Lothar Rubusch
2025-02-01 16:37   ` Jonathan Cameron
2025-01-28 12:00 ` [PATCH v1 03/12] iio: accel: adxl345: add debug register access Lothar Rubusch
2025-01-28 12:00 ` [PATCH v1 04/12] iio: accel: adxl345: reorganize irq handler Lothar Rubusch
2025-02-01 16:43   ` Jonathan Cameron
2025-01-28 12:00 ` [PATCH v1 05/12] iio: accel: adxl345: improve access to the interrupt enable register Lothar Rubusch
2025-02-01 16:49   ` Jonathan Cameron
2025-01-28 12:00 ` Lothar Rubusch [this message]
2025-02-01 17:02   ` [PATCH v1 06/12] iio: accel: adxl345: add single tap feature Jonathan Cameron
2025-01-28 12:00 ` [PATCH v1 07/12] iio: accel: adxl345: show tap status and direction Lothar Rubusch
2025-02-01 17:09   ` Jonathan Cameron
2025-01-28 12:00 ` [PATCH v1 08/12] iio: accel: adxl345: add double tap feature Lothar Rubusch
2025-02-01 17:15   ` Jonathan Cameron
2025-01-28 12:00 ` [PATCH v1 09/12] iio: accel: adxl345: add double tap suppress bit Lothar Rubusch
2025-02-01 17:17   ` Jonathan Cameron
2025-01-28 12:00 ` [PATCH v1 10/12] iio: accel: adxl345: add freefall feature Lothar Rubusch
2025-02-01 17:22   ` Jonathan Cameron
2025-01-28 12:00 ` [PATCH v1 11/12] iio: accel: adxl345: add activity feature Lothar Rubusch
2025-02-01 17:27   ` Jonathan Cameron
2025-02-04 13:48     ` Lothar Rubusch
2025-01-28 12:01 ` [PATCH v1 12/12] iio: accel: adxl345: add inactivity feature Lothar Rubusch
2025-02-01 17:41   ` Jonathan Cameron
2025-02-01 17:48 ` [PATCH v1 00/12] iio: accel: adxl345: add interrupt based sensor events Jonathan Cameron
2025-02-04 13:40   ` Lothar Rubusch
2025-02-08 12:57     ` Jonathan Cameron

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250128120100.205523-7-l.rubusch@gmail.com \
    --to=l.rubusch@gmail.com \
    --cc=Michael.Hennerich@analog.com \
    --cc=eraretuya@gmail.com \
    --cc=jic23@kernel.org \
    --cc=lars@metafoo.de \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox