* [PATCH v4 1/3] dt-bindings: iio: accel: add ADXL380
@ 2024-07-01 8:30 Antoniu Miclaus
2024-07-01 8:30 ` [PATCH v4 2/3] iio: accel: add ADXL380 driver Antoniu Miclaus
2024-07-01 8:30 ` [PATCH v4 3/3] docs: iio: add documentation for adxl380 driver Antoniu Miclaus
0 siblings, 2 replies; 7+ messages in thread
From: Antoniu Miclaus @ 2024-07-01 8:30 UTC (permalink / raw)
To: Ramona Gradinariu, Antoniu Miclaus, Lars-Peter Clausen,
Michael Hennerich, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Jun Yan,
Matti Vaittinen, Mehdi Djait, Mario Limonciello, linux-iio,
devicetree, linux-kernel, linux-doc
Cc: Conor Dooley
Add dt-bindings for ADXL380/ADLX382 low noise density, low
power, 3-axis accelerometer with selectable measurement ranges.
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Ramona Gradinariu <ramona.gradinariu@analog.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
changes in v4:
- drop marketing from description.
.../bindings/iio/accel/adi,adxl380.yaml | 92 +++++++++++++++++++
MAINTAINERS | 7 ++
2 files changed, 99 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml
diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml
new file mode 100644
index 000000000000..f1ff5ff4f478
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/accel/adi,adxl380.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADXL380/382 3-Axis Digital Accelerometer
+
+maintainers:
+ - Ramona Gradinariu <ramona.gradinariu@analog.com>
+ - Antoniu Miclaus <antoniu.miclaus@analog.com>
+
+description: |
+ The ADXL380/ADXL382 is a low noise density, low power, 3-axis
+ accelerometer with selectable measurement ranges. The ADXL380
+ supports the ±4 g, ±8 g, and ±16 g ranges, and the ADXL382 supports
+ ±15 g, ±30 g, and ±60 g ranges.
+
+ https://www.analog.com/en/products/adxl380.html
+
+properties:
+ compatible:
+ enum:
+ - adi,adxl380
+ - adi,adxl382
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ minItems: 1
+ maxItems: 2
+
+ interrupt-names:
+ minItems: 1
+ items:
+ - enum: [INT0, INT1]
+ - const: INT1
+
+ vddio-supply: true
+
+ vsupply-supply: true
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - vddio-supply
+ - vsupply-supply
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ accelerometer@54 {
+ compatible = "adi,adxl380";
+ reg = <0x54>;
+ vddio-supply = <&vddio>;
+ vsupply-supply = <&vsupply>;
+ interrupt-parent = <&gpio>;
+ interrupts = <25 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "INT0";
+ };
+ };
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ accelerometer@0 {
+ compatible = "adi,adxl380";
+ reg = <0>;
+ spi-max-frequency = <8000000>;
+ vddio-supply = <&vddio>;
+ vsupply-supply = <&vsupply>;
+ interrupt-parent = <&gpio>;
+ interrupts = <25 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "INT0";
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index be590c462d91..1425182c85e2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -618,6 +618,13 @@ F: drivers/iio/accel/adxl372.c
F: drivers/iio/accel/adxl372_i2c.c
F: drivers/iio/accel/adxl372_spi.c
+ADXL380 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
+M: Ramona Gradinariu <ramona.gradinariu@analog.com>
+M: Antoniu Miclaus <antoniu.miclaus@analog.com>
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml
+
AF8133J THREE-AXIS MAGNETOMETER DRIVER
M: Ondřej Jirman <megi@xff.cz>
S: Maintained
--
2.45.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 2/3] iio: accel: add ADXL380 driver
2024-07-01 8:30 [PATCH v4 1/3] dt-bindings: iio: accel: add ADXL380 Antoniu Miclaus
@ 2024-07-01 8:30 ` Antoniu Miclaus
2024-07-03 7:06 ` Alexandru Ardelean
2024-07-01 8:30 ` [PATCH v4 3/3] docs: iio: add documentation for adxl380 driver Antoniu Miclaus
1 sibling, 1 reply; 7+ messages in thread
From: Antoniu Miclaus @ 2024-07-01 8:30 UTC (permalink / raw)
To: Ramona Gradinariu, Antoniu Miclaus, Lars-Peter Clausen,
Michael Hennerich, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Jun Yan,
Matti Vaittinen, Mario Limonciello, Mehdi Djait, linux-iio,
devicetree, linux-kernel, linux-doc
The ADXL380/ADXL382 is a low noise density, low power, 3-axis
accelerometer with selectable measurement ranges. The ADXL380 supports
the +/-4 g, +/-8 g, and +/-16 g ranges, and the ADXL382 supports
+/-15 g, +/-30 g and +/-60 g ranges.
The ADXL380/ADXL382 offers industry leading noise, enabling precision
applications with minimal calibration. The low noise, and low power
ADXL380/ADXL382 enables accurate measurement in an environment with
high vibration, heart sounds and audio.
In addition to its low power consumption, the ADXL380/ADXL382 has many
features to enable true system level performance. These include a
built-in micropower temperature sensor, single / double / triple tap
detection and a state machine to prevent a false triggering. In
addition, the ADXL380/ADXL382 has provisions for external control of
the sampling time and/or an external clock.
Signed-off-by: Ramona Gradinariu <ramona.gradinariu@analog.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
changes in v4:
- drop __aligned(IIO_DMA_MINALIGN); from tables and use it on fifo_buf
- drop brackets around odr >> 1 and odr & 1
- wrap long line
- drop comma on null terminator
- fix odd indentation
- drop extra space before >
- add space before } on arrays where missing
MAINTAINERS | 4 +
drivers/iio/accel/Kconfig | 27 +
drivers/iio/accel/Makefile | 3 +
drivers/iio/accel/adxl380.c | 1908 +++++++++++++++++++++++++++++++
drivers/iio/accel/adxl380.h | 26 +
drivers/iio/accel/adxl380_i2c.c | 64 ++
drivers/iio/accel/adxl380_spi.c | 66 ++
7 files changed, 2098 insertions(+)
create mode 100644 drivers/iio/accel/adxl380.c
create mode 100644 drivers/iio/accel/adxl380.h
create mode 100644 drivers/iio/accel/adxl380_i2c.c
create mode 100644 drivers/iio/accel/adxl380_spi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 1425182c85e2..67583f13da51 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -624,6 +624,10 @@ M: Antoniu Miclaus <antoniu.miclaus@analog.com>
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml
+F: drivers/iio/accel/adxl380.c
+F: drivers/iio/accel/adxl380.h
+F: drivers/iio/accel/adxl380_i2c.c
+F: drivers/iio/accel/adxl380_spi.c
AF8133J THREE-AXIS MAGNETOMETER DRIVER
M: Ondřej Jirman <megi@xff.cz>
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
index c2da5066e9a7..6572ab447e14 100644
--- a/drivers/iio/accel/Kconfig
+++ b/drivers/iio/accel/Kconfig
@@ -177,6 +177,33 @@ config ADXL372_I2C
To compile this driver as a module, choose M here: the
module will be called adxl372_i2c.
+config ADXL380
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config ADXL380_SPI
+ tristate "Analog Devices ADXL380 3-Axis Accelerometer SPI Driver"
+ depends on SPI
+ select ADXL380
+ select REGMAP_SPI
+ help
+ Say yes here to add support for the Analog Devices ADXL380 triaxial
+ acceleration sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called adxl380_spi.
+
+config ADXL380_I2C
+ tristate "Analog Devices ADXL380 3-Axis Accelerometer I2C Driver"
+ depends on I2C
+ select ADXL380
+ select REGMAP_I2C
+ help
+ Say yes here to add support for the Analog Devices ADXL380 triaxial
+ acceleration sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called adxl380_i2c.
+
config BMA180
tristate "Bosch BMA023/BMA1x0/BMA250 3-Axis Accelerometer Driver"
depends on I2C && INPUT_BMA150=n
diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile
index db90532ba24a..ca8569e25aba 100644
--- a/drivers/iio/accel/Makefile
+++ b/drivers/iio/accel/Makefile
@@ -21,6 +21,9 @@ obj-$(CONFIG_ADXL367_SPI) += adxl367_spi.o
obj-$(CONFIG_ADXL372) += adxl372.o
obj-$(CONFIG_ADXL372_I2C) += adxl372_i2c.o
obj-$(CONFIG_ADXL372_SPI) += adxl372_spi.o
+obj-$(CONFIG_ADXL380) += adxl380.o
+obj-$(CONFIG_ADXL380_I2C) += adxl380_i2c.o
+obj-$(CONFIG_ADXL380_SPI) += adxl380_spi.o
obj-$(CONFIG_BMA180) += bma180.o
obj-$(CONFIG_BMA220) += bma220_spi.o
obj-$(CONFIG_BMA400) += bma400_core.o
diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c
new file mode 100644
index 000000000000..d37f4d85cf84
--- /dev/null
+++ b/drivers/iio/accel/adxl380.c
@@ -0,0 +1,1908 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL380 3-Axis Digital Accelerometer core driver
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <asm/unaligned.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>
+
+#include <linux/regulator/consumer.h>
+
+#include "adxl380.h"
+
+#define ADXL380_ID_VAL 380
+#define ADXL382_ID_VAL 382
+
+#define ADXL380_DEVID_AD_REG 0x00
+#define ADLX380_PART_ID_REG 0x02
+
+#define ADXL380_X_DATA_H_REG 0x15
+#define ADXL380_Y_DATA_H_REG 0x17
+#define ADXL380_Z_DATA_H_REG 0x19
+#define ADXL380_T_DATA_H_REG 0x1B
+
+#define ADXL380_MISC_0_REG 0x20
+#define ADXL380_XL382_MSK BIT(7)
+
+#define ADXL380_MISC_1_REG 0x21
+
+#define ADXL380_X_DSM_OFFSET_REG 0x4D
+
+#define ADXL380_ACT_INACT_CTL_REG 0x37
+#define ADXL380_INACT_EN_MSK BIT(2)
+#define ADXL380_ACT_EN_MSK BIT(0)
+
+#define ADXL380_SNSR_AXIS_EN_REG 0x38
+#define ADXL380_ACT_INACT_AXIS_EN_MSK GENMASK(2, 0)
+
+#define ADXL380_THRESH_ACT_H_REG 0x39
+#define ADXL380_TIME_ACT_H_REG 0x3B
+#define ADXL380_THRESH_INACT_H_REG 0x3E
+#define ADXL380_TIME_INACT_H_REG 0x40
+#define ADXL380_THRESH_MAX GENMASK(12, 0)
+#define ADXL380_TIME_MAX GENMASK(24, 0)
+
+#define ADXL380_FIFO_CONFIG_0_REG 0x30
+#define ADXL380_FIFO_SAMPLES_8_MSK BIT(0)
+#define ADXL380_FIFO_MODE_MSK GENMASK(5, 4)
+
+#define ADXL380_FIFO_DISABLED 0
+#define ADXL380_FIFO_NORMAL 1
+#define ADXL380_FIFO_STREAMED 2
+#define ADXL380_FIFO_TRIGGERED 3
+
+#define ADXL380_FIFO_CONFIG_1_REG 0x31
+#define ADXL380_FIFO_STATUS_0_REG 0x1E
+
+#define ADXL380_TAP_THRESH_REG 0x43
+#define ADXL380_TAP_DUR_REG 0x44
+#define ADXL380_TAP_LATENT_REG 0x45
+#define ADXL380_TAP_WINDOW_REG 0x46
+#define ADXL380_TAP_TIME_MAX GENMASK(7, 0)
+
+#define ADXL380_TAP_CFG_REG 0x47
+#define ADXL380_TAP_AXIS_MSK GENMASK(1, 0)
+
+#define ADXL380_TRIG_CFG_REG 0x49
+#define ADXL380_TRIG_CFG_DEC_2X_MSK BIT(7)
+#define ADXL380_TRIG_CFG_SINC_RATE_MSK BIT(6)
+
+#define ADXL380_FILTER_REG 0x50
+#define ADXL380_FILTER_EQ_FILT_MSK BIT(6)
+#define ADXL380_FILTER_LPF_MODE_MSK GENMASK(5, 4)
+#define ADXL380_FILTER_HPF_PATH_MSK BIT(3)
+#define ADXL380_FILTER_HPF_CORNER_MSK GENMASK(2, 0)
+
+#define ADXL380_OP_MODE_REG 0x26
+#define ADXL380_OP_MODE_RANGE_MSK GENMASK(7, 6)
+#define ADXL380_OP_MODE_MSK GENMASK(3, 0)
+#define ADXL380_OP_MODE_STANDBY 0
+#define ADXL380_OP_MODE_HEART_SOUND 1
+#define ADXL380_OP_MODE_ULP 2
+#define ADXL380_OP_MODE_VLP 3
+#define ADXL380_OP_MODE_LP 4
+#define ADXL380_OP_MODE_LP_ULP 6
+#define ADXL380_OP_MODE_LP_VLP 7
+#define ADXL380_OP_MODE_RBW 8
+#define ADXL380_OP_MODE_RBW_ULP 10
+#define ADXL380_OP_MODE_RBW_VLP 11
+#define ADXL380_OP_MODE_HP 12
+#define ADXL380_OP_MODE_HP_ULP 14
+#define ADXL380_OP_MODE_HP_VLP 15
+
+#define ADXL380_OP_MODE_4G_RANGE 0
+#define ADXL382_OP_MODE_15G_RANGE 0
+#define ADXL380_OP_MODE_8G_RANGE 1
+#define ADXL382_OP_MODE_30G_RANGE 1
+#define ADXL380_OP_MODE_16G_RANGE 2
+#define ADXL382_OP_MODE_60G_RANGE 2
+
+#define ADXL380_DIG_EN_REG 0x27
+#define ADXL380_CHAN_EN_MSK(chan) BIT(4 + (chan))
+#define ADXL380_FIFO_EN_MSK BIT(3)
+
+#define ADXL380_INT0_MAP0_REG 0x2B
+#define ADXL380_INT1_MAP0_REG 0x2D
+#define ADXL380_INT_MAP0_INACT_INT0_MSK BIT(6)
+#define ADXL380_INT_MAP0_ACT_INT0_MSK BIT(5)
+#define ADXL380_INT_MAP0_FIFO_WM_INT0_MSK BIT(3)
+
+#define ADXL380_INT0_MAP1_REG 0x2C
+#define ADXL380_INT1_MAP1_REG 0x2E
+#define ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK BIT(1)
+#define ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK BIT(0)
+
+#define ADXL380_INT0_REG 0x5D
+#define ADXL380_INT0_POL_MSK BIT(7)
+
+#define ADXL380_RESET_REG 0x2A
+#define ADXL380_FIFO_DATA 0x1D
+
+#define ADXL380_DEVID_AD_VAL 0xAD
+#define ADXL380_RESET_CODE 0x52
+
+#define ADXL380_STATUS_0_REG 0x11
+#define ADXL380_STATUS_0_FIFO_FULL_MSK BIT(1)
+#define ADXL380_STATUS_0_FIFO_WM_MSK BIT(3)
+
+#define ADXL380_STATUS_1_INACT_MSK BIT(6)
+#define ADXL380_STATUS_1_ACT_MSK BIT(5)
+#define ADXL380_STATUS_1_DOUBLE_TAP_MSK BIT(1)
+#define ADXL380_STATUS_1_SINGLE_TAP_MSK BIT(0)
+
+#define ADXL380_FIFO_SAMPLES 315UL
+
+enum adxl380_channels {
+ ADXL380_ACCEL_X,
+ ADXL380_ACCEL_Y,
+ ADXL380_ACCEL_Z,
+ ADXL380_TEMP,
+ ADXL380_CH_NUM,
+};
+
+enum adxl380_axis {
+ ADXL380_X_AXIS,
+ ADXL380_Y_AXIS,
+ ADXL380_Z_AXIS,
+};
+
+enum adxl380_activity_type {
+ ADXL380_ACTIVITY,
+ ADXL380_INACTIVITY,
+};
+
+enum adxl380_tap_type {
+ ADXL380_SINGLE_TAP,
+ ADXL380_DOUBLE_TAP,
+};
+
+enum adxl380_tap_time_type {
+ ADXL380_TAP_TIME_LATENT,
+ ADXL380_TAP_TIME_WINDOW,
+};
+
+static const int adxl380_range_scale_factor_tbl[] = { 1, 2, 4 };
+
+const struct adxl380_chip_info adxl380_chip_info = {
+ .name = "adxl380",
+ .chip_id = ADXL380_ID_VAL,
+ .scale_tbl = {
+ [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 },
+ [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 },
+ [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 },
+ },
+ .samp_freq_tbl = { 8000, 16000, 32000 },
+ /*
+ * The datasheet defines an intercept of 470 LSB at 25 degC
+ * and a sensitivity of 10.2 LSB/C.
+ */
+ .temp_offset = 25 * 102 / 10 - 470,
+
+};
+EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, IIO_ADXL380);
+
+const struct adxl380_chip_info adxl382_chip_info = {
+ .name = "adxl382",
+ .chip_id = ADXL382_ID_VAL,
+ .scale_tbl = {
+ [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 },
+ [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 },
+ [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 },
+ },
+ .samp_freq_tbl = { 16000, 32000, 64000 },
+ /*
+ * The datasheet defines an intercept of 570 LSB at 25 degC
+ * and a sensitivity of 10.2 LSB/C.
+ */
+ .temp_offset = 25 * 102 / 10 - 570,
+};
+EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, IIO_ADXL380);
+
+static const unsigned int adxl380_th_reg_high_addr[2] = {
+ [ADXL380_ACTIVITY] = ADXL380_THRESH_ACT_H_REG,
+ [ADXL380_INACTIVITY] = ADXL380_THRESH_INACT_H_REG,
+};
+
+static const unsigned int adxl380_time_reg_high_addr[2] = {
+ [ADXL380_ACTIVITY] = ADXL380_TIME_ACT_H_REG,
+ [ADXL380_INACTIVITY] = ADXL380_TIME_INACT_H_REG,
+};
+
+static const unsigned int adxl380_tap_time_reg[2] = {
+ [ADXL380_TAP_TIME_LATENT] = ADXL380_TAP_LATENT_REG,
+ [ADXL380_TAP_TIME_WINDOW] = ADXL380_TAP_WINDOW_REG,
+};
+
+struct adxl380_state {
+ struct regmap *regmap;
+ struct device *dev;
+ const struct adxl380_chip_info *chip_info;
+ /*
+ * Synchronize access to members of driver state, and ensure atomicity
+ * of consecutive regmap operations.
+ */
+ struct mutex lock;
+ enum adxl380_axis tap_axis_en;
+ u8 range;
+ u8 odr;
+ u8 fifo_set_size;
+ u8 transf_buf[3];
+ u16 watermark;
+ u32 act_time_ms;
+ u32 act_threshold;
+ u32 inact_time_ms;
+ u32 inact_threshold;
+ u32 tap_latent_us;
+ u32 tap_window_us;
+ u32 tap_duration_us;
+ u32 tap_threshold;
+ int irq;
+ int int_map[2];
+ int lpf_tbl[4];
+ int hpf_tbl[7][2];
+
+ __be16 fifo_buf[ADXL380_FIFO_SAMPLES] __aligned(IIO_DMA_MINALIGN);
+};
+
+bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg)
+{
+ return reg == ADXL380_FIFO_DATA;
+}
+EXPORT_SYMBOL_NS_GPL(adxl380_readable_noinc_reg, IIO_ADXL380);
+
+static int adxl380_set_measure_en(struct adxl380_state *st, bool en)
+{
+ int ret;
+ unsigned int act_inact_ctl;
+ u8 op_mode = ADXL380_OP_MODE_STANDBY;
+
+ if (en) {
+ ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG, &act_inact_ctl);
+ if (ret)
+ return ret;
+
+ /* Activity/ Inactivity detection available only in VLP/ULP mode */
+ if (FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) ||
+ FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl))
+ op_mode = ADXL380_OP_MODE_VLP;
+ else
+ op_mode = ADXL380_OP_MODE_HP;
+ }
+
+ return regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG,
+ ADXL380_OP_MODE_MSK,
+ FIELD_PREP(ADXL380_OP_MODE_MSK, op_mode));
+}
+
+static void adxl380_scale_act_inact_thresholds(struct adxl380_state *st,
+ u8 old_range,
+ u8 new_range)
+{
+ st->act_threshold = mult_frac(st->act_threshold,
+ adxl380_range_scale_factor_tbl[old_range],
+ adxl380_range_scale_factor_tbl[new_range]);
+ st->inact_threshold = mult_frac(st->inact_threshold,
+ adxl380_range_scale_factor_tbl[old_range],
+ adxl380_range_scale_factor_tbl[new_range]);
+}
+
+static int adxl380_write_act_inact_threshold(struct adxl380_state *st,
+ enum adxl380_activity_type act,
+ unsigned int th)
+{
+ int ret;
+ u8 reg = adxl380_th_reg_high_addr[act];
+
+ if (th > ADXL380_THRESH_MAX)
+ return -EINVAL;
+
+ ret = regmap_write(st->regmap, reg + 1, th & GENMASK(7, 0));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, reg, GENMASK(2, 0), th >> 8);
+ if (ret)
+ return ret;
+
+ if (act == ADXL380_ACTIVITY)
+ st->act_threshold = th;
+ else
+ st->inact_threshold = th;
+
+ return 0;
+}
+
+static int adxl380_set_act_inact_threshold(struct iio_dev *indio_dev,
+ enum adxl380_activity_type act,
+ u16 th)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_act_inact_threshold(st, act, th);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_set_tap_threshold_value(struct iio_dev *indio_dev, u8 th)
+{
+ int ret;
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL380_TAP_THRESH_REG, th);
+ if (ret)
+ return ret;
+
+ st->tap_threshold = th;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int _adxl380_write_tap_time_us(struct adxl380_state *st,
+ enum adxl380_tap_time_type tap_time_type,
+ u32 us)
+{
+ u8 reg = adxl380_tap_time_reg[tap_time_type];
+ unsigned int reg_val;
+ int ret;
+
+ /* scale factor for tap window is 1250us / LSB */
+ reg_val = DIV_ROUND_CLOSEST(us, 1250);
+ if (reg_val > ADXL380_TAP_TIME_MAX)
+ reg_val = ADXL380_TAP_TIME_MAX;
+
+ ret = regmap_write(st->regmap, reg, reg_val);
+ if (ret)
+ return ret;
+
+ if (tap_time_type == ADXL380_TAP_TIME_WINDOW)
+ st->tap_window_us = us;
+ else
+ st->tap_latent_us = us;
+
+ return 0;
+}
+
+static int adxl380_write_tap_time_us(struct adxl380_state *st,
+ enum adxl380_tap_time_type tap_time_type, u32 us)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = _adxl380_write_tap_time_us(st, tap_time_type, us);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_write_tap_dur_us(struct iio_dev *indio_dev, u32 us)
+{
+ int ret;
+ unsigned int reg_val;
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ /* 625us per code is the scale factor of TAP_DUR register */
+ reg_val = DIV_ROUND_CLOSEST(us, 625);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL380_TAP_DUR_REG, reg_val);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_read_chn(struct adxl380_state *st, u8 addr)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_bulk_read(st->regmap, addr, &st->transf_buf, 2);
+ if (ret)
+ return ret;
+
+ return get_unaligned_be16(st->transf_buf);
+}
+
+static int adxl380_get_odr(struct adxl380_state *st, int *odr)
+{
+ int ret;
+ unsigned int trig_cfg, odr_idx;
+
+ ret = regmap_read(st->regmap, ADXL380_TRIG_CFG_REG, &trig_cfg);
+ if (ret)
+ return ret;
+
+ odr_idx = (FIELD_GET(ADXL380_TRIG_CFG_SINC_RATE_MSK, trig_cfg) << 1) |
+ (FIELD_GET(ADXL380_TRIG_CFG_DEC_2X_MSK, trig_cfg) & 1);
+
+ *odr = st->chip_info->samp_freq_tbl[odr_idx];
+
+ return 0;
+}
+
+static const int adxl380_lpf_div[] = {
+ 1, 4, 8, 16,
+};
+
+static int adxl380_fill_lpf_tbl(struct adxl380_state *st)
+{
+ int ret, i;
+ int odr;
+
+ ret = adxl380_get_odr(st, &odr);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(st->lpf_tbl); i++)
+ st->lpf_tbl[i] = DIV_ROUND_CLOSEST(odr, adxl380_lpf_div[i]);
+
+ return 0;
+}
+
+static const int adxl380_hpf_mul[] = {
+ 0, 247000, 62084, 15545, 3862, 954, 238,
+};
+
+static int adxl380_fill_hpf_tbl(struct adxl380_state *st)
+{
+ int i, ret, odr_hz;
+ u32 multiplier;
+ u64 div, rem, odr;
+
+ ret = adxl380_get_odr(st, &odr_hz);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(adxl380_hpf_mul); i++) {
+ odr = mul_u64_u32_shr(odr_hz, MEGA, 0);
+ multiplier = adxl380_hpf_mul[i];
+ div = div64_u64_rem(mul_u64_u32_shr(odr, multiplier, 0),
+ TERA * 100, &rem);
+
+ st->hpf_tbl[i][0] = div;
+ st->hpf_tbl[i][1] = div_u64(rem, MEGA * 100);
+ }
+
+ return 0;
+}
+
+static int adxl380_set_odr(struct adxl380_state *st, u8 odr)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG,
+ ADXL380_TRIG_CFG_DEC_2X_MSK,
+ FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, odr & 1));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG,
+ ADXL380_TRIG_CFG_SINC_RATE_MSK,
+ FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, odr >> 1));
+ if (ret)
+ return ret;
+
+ ret = adxl380_set_measure_en(st, true);
+ if (ret)
+ return ret;
+
+ ret = adxl380_fill_lpf_tbl(st);
+ if (ret)
+ return ret;
+
+ return adxl380_fill_hpf_tbl(st);
+}
+
+static int adxl380_find_match_1d_tbl(const int *array, unsigned int size,
+ int val)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (val == array[i])
+ return i;
+ }
+
+ return size - 1;
+}
+
+static int adxl380_find_match_2d_tbl(const int (*freq_tbl)[2], int n, int val, int val2)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int adxl380_get_lpf(struct adxl380_state *st, int *lpf)
+{
+ int ret;
+ unsigned int trig_cfg, lpf_idx;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg);
+ if (ret)
+ return ret;
+
+ lpf_idx = FIELD_GET(ADXL380_FILTER_LPF_MODE_MSK, trig_cfg);
+
+ *lpf = st->lpf_tbl[lpf_idx];
+
+ return 0;
+}
+
+static int adxl380_set_lpf(struct adxl380_state *st, u8 lpf)
+{
+ int ret;
+ u8 eq_bypass = 0;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ if (lpf)
+ eq_bypass = 1;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
+ ADXL380_FILTER_EQ_FILT_MSK,
+ FIELD_PREP(ADXL380_FILTER_EQ_FILT_MSK, eq_bypass));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
+ ADXL380_FILTER_LPF_MODE_MSK,
+ FIELD_PREP(ADXL380_FILTER_LPF_MODE_MSK, lpf));
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_get_hpf(struct adxl380_state *st, int *hpf_int, int *hpf_frac)
+{
+ int ret;
+ unsigned int trig_cfg, hpf_idx;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg);
+ if (ret)
+ return ret;
+
+ hpf_idx = FIELD_GET(ADXL380_FILTER_HPF_CORNER_MSK, trig_cfg);
+
+ *hpf_int = st->hpf_tbl[hpf_idx][0];
+ *hpf_frac = st->hpf_tbl[hpf_idx][1];
+
+ return 0;
+}
+
+static int adxl380_set_hpf(struct adxl380_state *st, u8 hpf)
+{
+ int ret;
+ u8 hpf_path = 0;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ if (hpf)
+ hpf_path = 1;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
+ ADXL380_FILTER_HPF_PATH_MSK,
+ FIELD_PREP(ADXL380_FILTER_HPF_PATH_MSK, hpf_path));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
+ ADXL380_FILTER_HPF_CORNER_MSK,
+ FIELD_PREP(ADXL380_FILTER_HPF_CORNER_MSK, hpf));
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int _adxl380_set_act_inact_time_ms(struct adxl380_state *st,
+ enum adxl380_activity_type act,
+ u32 ms)
+{
+ u8 reg = adxl380_time_reg_high_addr[act];
+ unsigned int reg_val;
+ int ret;
+
+ /* 500us per code is the scale factor of TIME_ACT / TIME_INACT registers */
+ reg_val = min(DIV_ROUND_CLOSEST(ms * 1000, 500), ADXL380_TIME_MAX);
+
+ put_unaligned_be24(reg_val, &st->transf_buf[0]);
+
+ ret = regmap_bulk_write(st->regmap, reg, st->transf_buf, sizeof(st->transf_buf));
+ if (ret)
+ return ret;
+
+ if (act == ADXL380_ACTIVITY)
+ st->act_time_ms = ms;
+ else
+ st->inact_time_ms = ms;
+
+ return 0;
+}
+
+static int adxl380_set_act_inact_time_ms(struct adxl380_state *st,
+ enum adxl380_activity_type act,
+ u32 ms)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = _adxl380_set_act_inact_time_ms(st, act, ms);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_set_range(struct adxl380_state *st, u8 range)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG,
+ ADXL380_OP_MODE_RANGE_MSK,
+ FIELD_PREP(ADXL380_OP_MODE_RANGE_MSK, range));
+
+ if (ret)
+ return ret;
+
+ adxl380_scale_act_inact_thresholds(st, st->range, range);
+
+ /* Activity thresholds depend on range */
+ ret = adxl380_write_act_inact_threshold(st, ADXL380_ACTIVITY,
+ st->act_threshold);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_act_inact_threshold(st, ADXL380_INACTIVITY,
+ st->inact_threshold);
+ if (ret)
+ return ret;
+
+ st->range = range;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_write_act_inact_en(struct adxl380_state *st,
+ enum adxl380_activity_type type,
+ bool en)
+{
+ if (type == ADXL380_ACTIVITY)
+ return regmap_update_bits(st->regmap, ADXL380_ACT_INACT_CTL_REG,
+ ADXL380_ACT_EN_MSK,
+ FIELD_PREP(ADXL380_ACT_EN_MSK, en));
+
+ return regmap_update_bits(st->regmap, ADXL380_ACT_INACT_CTL_REG,
+ ADXL380_INACT_EN_MSK,
+ FIELD_PREP(ADXL380_INACT_EN_MSK, en));
+}
+
+static int adxl380_read_act_inact_int(struct adxl380_state *st,
+ enum adxl380_activity_type type,
+ bool *en)
+{
+ int ret;
+ unsigned int reg_val;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_read(st->regmap, st->int_map[0], ®_val);
+ if (ret)
+ return ret;
+
+ if (type == ADXL380_ACTIVITY)
+ *en = FIELD_GET(ADXL380_INT_MAP0_ACT_INT0_MSK, reg_val);
+ else
+ *en = FIELD_GET(ADXL380_INT_MAP0_INACT_INT0_MSK, reg_val);
+
+ return 0;
+}
+
+static int adxl380_write_act_inact_int(struct adxl380_state *st,
+ enum adxl380_activity_type act,
+ bool en)
+{
+ if (act == ADXL380_ACTIVITY)
+ return regmap_update_bits(st->regmap, st->int_map[0],
+ ADXL380_INT_MAP0_ACT_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP0_ACT_INT0_MSK, en));
+
+ return regmap_update_bits(st->regmap, st->int_map[0],
+ ADXL380_INT_MAP0_INACT_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP0_INACT_INT0_MSK, en));
+}
+
+static int adxl380_act_inact_config(struct adxl380_state *st,
+ enum adxl380_activity_type type,
+ bool en)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_act_inact_en(st, type, en);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_act_inact_int(st, type, en);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_write_tap_axis(struct adxl380_state *st,
+ enum adxl380_axis axis)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_TAP_CFG_REG,
+ ADXL380_TAP_AXIS_MSK,
+ FIELD_PREP(ADXL380_TAP_AXIS_MSK, axis));
+
+ if (ret)
+ return ret;
+
+ st->tap_axis_en = axis;
+
+ return 0;
+}
+
+static int adxl380_read_tap_int(struct adxl380_state *st, enum adxl380_tap_type type, bool *en)
+{
+ int ret;
+ unsigned int reg_val;
+
+ ret = regmap_read(st->regmap, st->int_map[1], ®_val);
+ if (ret)
+ return ret;
+
+ if (type == ADXL380_SINGLE_TAP)
+ *en = FIELD_GET(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, reg_val);
+ else
+ *en = FIELD_GET(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, reg_val);
+
+ return 0;
+}
+
+static int adxl380_write_tap_int(struct adxl380_state *st, enum adxl380_tap_type type, bool en)
+{
+ if (type == ADXL380_SINGLE_TAP)
+ return regmap_update_bits(st->regmap, st->int_map[1],
+ ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, en));
+
+ return regmap_update_bits(st->regmap, st->int_map[1],
+ ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, en));
+}
+
+static int adxl380_tap_config(struct adxl380_state *st,
+ enum adxl380_axis axis,
+ enum adxl380_tap_type type,
+ bool en)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_tap_axis(st, axis);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_tap_int(st, type, en);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_set_fifo_samples(struct adxl380_state *st)
+{
+ int ret;
+ u16 fifo_samples = st->watermark * st->fifo_set_size;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG,
+ ADXL380_FIFO_SAMPLES_8_MSK,
+ FIELD_PREP(ADXL380_FIFO_SAMPLES_8_MSK,
+ (fifo_samples & BIT(8))));
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, ADXL380_FIFO_CONFIG_1_REG,
+ fifo_samples & 0xFF);
+}
+
+static int adxl380_get_status(struct adxl380_state *st, u8 *status0, u8 *status1)
+{
+ int ret;
+
+ /* STATUS0, STATUS1 are adjacent regs */
+ ret = regmap_bulk_read(st->regmap, ADXL380_STATUS_0_REG,
+ &st->transf_buf, 2);
+ if (ret)
+ return ret;
+
+ *status0 = st->transf_buf[0];
+ *status1 = st->transf_buf[1];
+
+ return 0;
+}
+
+static int adxl380_get_fifo_entries(struct adxl380_state *st, u16 *fifo_entries)
+{
+ int ret;
+
+ ret = regmap_bulk_read(st->regmap, ADXL380_FIFO_STATUS_0_REG,
+ &st->transf_buf, 2);
+ if (ret)
+ return ret;
+
+ *fifo_entries = st->transf_buf[0] | ((BIT(0) & st->transf_buf[1]) << 8);
+
+ return 0;
+}
+
+static void adxl380_push_event(struct iio_dev *indio_dev, s64 timestamp,
+ u8 status1)
+{
+ if (FIELD_GET(ADXL380_STATUS_1_ACT_MSK, status1))
+ 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_RISING),
+ timestamp);
+
+ if (FIELD_GET(ADXL380_STATUS_1_INACT_MSK, status1))
+ 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),
+ timestamp);
+ if (FIELD_GET(ADXL380_STATUS_1_SINGLE_TAP_MSK, status1))
+ 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),
+ timestamp);
+
+ if (FIELD_GET(ADXL380_STATUS_1_DOUBLE_TAP_MSK, status1))
+ 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_DOUBLETAP),
+ timestamp);
+}
+
+static irqreturn_t adxl380_irq_handler(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct adxl380_state *st = iio_priv(indio_dev);
+ u8 status0, status1;
+ u16 fifo_entries;
+ int i;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_get_status(st, &status0, &status1);
+ if (ret)
+ return IRQ_HANDLED;
+
+ adxl380_push_event(indio_dev, iio_get_time_ns(indio_dev), status1);
+
+ if (!FIELD_GET(ADXL380_STATUS_0_FIFO_WM_MSK, status0))
+ return IRQ_HANDLED;
+
+ ret = adxl380_get_fifo_entries(st, &fifo_entries);
+ if (ret)
+ return IRQ_HANDLED;
+
+ for (i = 0; i < fifo_entries; i += st->fifo_set_size) {
+ ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA,
+ &st->fifo_buf[i],
+ 2 * st->fifo_set_size);
+ if (ret)
+ return IRQ_HANDLED;
+ iio_push_to_buffers(indio_dev, &st->fifo_buf[i]);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int adxl380_write_calibbias_value(struct adxl380_state *st,
+ unsigned long chan_addr,
+ s8 calibbias)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL380_X_DSM_OFFSET_REG + chan_addr, calibbias);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_read_calibbias_value(struct adxl380_state *st,
+ unsigned long chan_addr,
+ int *calibbias)
+{
+ int ret;
+ unsigned int reg_val;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_read(st->regmap, ADXL380_X_DSM_OFFSET_REG + chan_addr, ®_val);
+ if (ret)
+ return ret;
+
+ *calibbias = sign_extend32(reg_val, 7);
+
+ return 0;
+}
+
+static ssize_t hwfifo_watermark_min_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "1\n");
+}
+
+static ssize_t hwfifo_watermark_max_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%lu\n", ADXL380_FIFO_SAMPLES);
+}
+
+static ssize_t adxl380_get_fifo_watermark(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%d\n", st->watermark);
+}
+
+static ssize_t adxl380_get_fifo_enabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+ unsigned int reg_val;
+
+ ret = regmap_read(st->regmap, ADXL380_DIG_EN_REG, ®_val);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu\n",
+ FIELD_GET(ADXL380_FIFO_EN_MSK, reg_val));
+}
+
+static IIO_DEVICE_ATTR_RO(hwfifo_watermark_min, 0);
+static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0);
+static IIO_DEVICE_ATTR(hwfifo_watermark, 0444,
+ adxl380_get_fifo_watermark, NULL, 0);
+static IIO_DEVICE_ATTR(hwfifo_enabled, 0444,
+ adxl380_get_fifo_enabled, NULL, 0);
+
+static const struct iio_dev_attr *adxl380_fifo_attributes[] = {
+ &iio_dev_attr_hwfifo_watermark_min,
+ &iio_dev_attr_hwfifo_watermark_max,
+ &iio_dev_attr_hwfifo_watermark,
+ &iio_dev_attr_hwfifo_enabled,
+ NULL
+};
+
+static int adxl380_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int i;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap,
+ st->int_map[0],
+ ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 1));
+ if (ret)
+ return ret;
+
+ for_each_clear_bit(i, indio_dev->active_scan_mask, ADXL380_CH_NUM) {
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
+ ADXL380_CHAN_EN_MSK(i),
+ 0 << (4 + i));
+ if (ret)
+ return ret;
+ }
+
+ st->fifo_set_size = bitmap_weight(indio_dev->active_scan_mask,
+ indio_dev->masklength);
+
+ if ((st->watermark * st->fifo_set_size) > ADXL380_FIFO_SAMPLES)
+ st->watermark = (ADXL380_FIFO_SAMPLES / st->fifo_set_size);
+
+ ret = adxl380_set_fifo_samples(st);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, ADXL380_FIFO_EN_MSK,
+ FIELD_PREP(ADXL380_FIFO_EN_MSK, 1));
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret, i;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap,
+ st->int_map[0],
+ ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 0));
+ if (ret)
+ return ret;
+
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
+ ADXL380_CHAN_EN_MSK(i),
+ 1 << (4 + i));
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, ADXL380_FIFO_EN_MSK,
+ FIELD_PREP(ADXL380_FIFO_EN_MSK, 0));
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static const struct iio_buffer_setup_ops adxl380_buffer_ops = {
+ .postenable = adxl380_buffer_postenable,
+ .predisable = adxl380_buffer_predisable,
+};
+
+static int adxl380_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = adxl380_read_chn(st, chan->address);
+ if (ret)
+ return ret;
+
+ iio_device_release_direct_mode(indio_dev);
+
+ *val = sign_extend32(ret >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ scoped_guard(mutex, &st->lock) {
+ *val = st->chip_info->scale_tbl[st->range][0];
+ *val2 = st->chip_info->scale_tbl[st->range][1];
+ }
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_TEMP:
+ /* 10.2 LSB / Degree Celsius */
+ *val = 10000;
+ *val2 = 102;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_TEMP:
+ *val = st->chip_info->temp_offset;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_CALIBBIAS:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ ret = adxl380_read_calibbias_value(st, chan->scan_index, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = adxl380_get_odr(st, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ ret = adxl380_get_lpf(st, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+ ret = adxl380_get_hpf(st, val, val2);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ return -EINVAL;
+}
+
+static int adxl380_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (const int *)st->chip_info->scale_tbl;
+ *type = IIO_VAL_INT_PLUS_NANO;
+ *length = ARRAY_SIZE(st->chip_info->scale_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = (const int *)st->chip_info->samp_freq_tbl;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(st->chip_info->samp_freq_tbl);
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ *vals = (const int *)st->lpf_tbl;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(st->lpf_tbl);
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+ *vals = (const int *)st->hpf_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ /* Values are stored in a 2D matrix */
+ *length = ARRAY_SIZE(st->hpf_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int odr_index, lpf_index, hpf_index, range_index;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ odr_index = adxl380_find_match_1d_tbl(st->chip_info->samp_freq_tbl,
+ ARRAY_SIZE(st->chip_info->samp_freq_tbl),
+ val);
+ return adxl380_set_odr(st, odr_index);
+ case IIO_CHAN_INFO_CALIBBIAS:
+ return adxl380_write_calibbias_value(st, chan->scan_index, val);
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ lpf_index = adxl380_find_match_1d_tbl(st->lpf_tbl,
+ ARRAY_SIZE(st->lpf_tbl),
+ val);
+ if (lpf_index < 0)
+ return lpf_index;
+ return adxl380_set_lpf(st, lpf_index);
+ case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+ hpf_index = adxl380_find_match_2d_tbl(st->hpf_tbl,
+ ARRAY_SIZE(st->hpf_tbl),
+ val, val2);
+ if (hpf_index < 0)
+ return hpf_index;
+ return adxl380_set_hpf(st, hpf_index);
+ case IIO_CHAN_INFO_SCALE:
+ range_index = adxl380_find_match_2d_tbl(st->chip_info->scale_tbl,
+ ARRAY_SIZE(st->chip_info->scale_tbl),
+ val, val2);
+ if (range_index < 0)
+ return range_index;
+ return adxl380_set_range(st, range_index);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
+static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+ bool int_en;
+ bool tap_axis_en = false;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ tap_axis_en = st->tap_axis_en == ADXL380_X_AXIS;
+ break;
+ case IIO_MOD_Y:
+ tap_axis_en = st->tap_axis_en == ADXL380_Y_AXIS;
+ break;
+ case IIO_MOD_Z:
+ tap_axis_en = st->tap_axis_en == ADXL380_Z_AXIS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = adxl380_read_act_inact_int(st, ADXL380_ACTIVITY, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ case IIO_EV_DIR_FALLING:
+ ret = adxl380_read_act_inact_int(st, ADXL380_INACTIVITY, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ case IIO_EV_DIR_SINGLETAP:
+ ret = adxl380_read_tap_int(st, ADXL380_SINGLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en && tap_axis_en;
+ case IIO_EV_DIR_DOUBLETAP:
+ ret = adxl380_read_tap_int(st, ADXL380_DOUBLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en && tap_axis_en;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
+ enum adxl380_axis axis;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ axis = ADXL380_X_AXIS;
+ break;
+ case IIO_MOD_Y:
+ axis = ADXL380_Y_AXIS;
+ break;
+ case IIO_MOD_Z:
+ axis = ADXL380_Z_AXIS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl380_act_inact_config(st, ADXL380_ACTIVITY, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl380_act_inact_config(st, ADXL380_INACTIVITY, state);
+ case IIO_EV_DIR_SINGLETAP:
+ return adxl380_tap_config(st, axis, ADXL380_SINGLE_TAP, state);
+ case IIO_EV_DIR_DOUBLETAP:
+ return adxl380_tap_config(st, axis, ADXL380_DOUBLE_TAP, state);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (info) {
+ case IIO_EV_INFO_VALUE: {
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ *val = st->act_threshold;
+ return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ *val = st->inact_threshold;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ }
+ case IIO_EV_INFO_PERIOD:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ *val = st->act_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ *val = st->inact_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ *val = st->tap_threshold;
+ return IIO_VAL_INT;
+ 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;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
+ u32 val_ms, val_us;
+
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl380_set_act_inact_threshold(indio_dev,
+ ADXL380_ACTIVITY, val);
+ case IIO_EV_DIR_FALLING:
+ return adxl380_set_act_inact_threshold(indio_dev,
+ ADXL380_INACTIVITY, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ val_ms = val * 1000 + DIV_ROUND_UP(val2, 1000);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl380_set_act_inact_time_ms(st,
+ ADXL380_ACTIVITY, val_ms);
+ case IIO_EV_DIR_FALLING:
+ return adxl380_set_act_inact_time_ms(st,
+ ADXL380_INACTIVITY, val_ms);
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ return adxl380_set_tap_threshold_value(indio_dev, val);
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ val_us = val * 1000000 + val2;
+ return adxl380_write_tap_time_us(st,
+ ADXL380_TAP_TIME_WINDOW,
+ val_us);
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ val_us = val * 1000000 + val2;
+ return adxl380_write_tap_time_us(st,
+ ADXL380_TAP_TIME_LATENT,
+ val_us);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int vals[2];
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ vals[0] = st->tap_duration_us;
+ vals[1] = MICRO;
+
+ return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals);
+}
+
+static ssize_t in_accel_gesture_tap_maxtomin_time_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 adxl380_state *st = iio_priv(indio_dev);
+ int ret, val_int, val_fract_us;
+
+ guard(mutex)(&st->lock);
+
+ ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us);
+ if (ret)
+ return ret;
+
+ /* maximum value is 255 * 625 us = 0.159375 seconds */
+ if (val_int || val_fract_us > 159375 || val_fract_us < 0)
+ return -EINVAL;
+
+ ret = adxl380_write_tap_dur_us(indio_dev, val_fract_us);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_maxtomin_time, 0);
+
+static struct attribute *adxl380_event_attributes[] = {
+ &iio_dev_attr_in_accel_gesture_tap_maxtomin_time.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group adxl380_event_attribute_group = {
+ .attrs = adxl380_event_attributes,
+};
+
+static int adxl380_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int writeval,
+ unsigned int *readval)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static int adxl380_set_watermark(struct iio_dev *indio_dev, unsigned int val)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ st->watermark = min(val, ADXL380_FIFO_SAMPLES);
+
+ return 0;
+}
+
+static const struct iio_info adxl380_info = {
+ .read_raw = adxl380_read_raw,
+ .read_avail = &adxl380_read_avail,
+ .write_raw = adxl380_write_raw,
+ .write_raw_get_fmt = adxl380_write_raw_get_fmt,
+ .read_event_config = adxl380_read_event_config,
+ .write_event_config = adxl380_write_event_config,
+ .read_event_value = adxl380_read_event_value,
+ .write_event_value = adxl380_write_event_value,
+ .event_attrs = &adxl380_event_attribute_group,
+ .debugfs_reg_access = &adxl380_reg_access,
+ .hwfifo_set_watermark = adxl380_set_watermark,
+};
+
+static const struct iio_event_spec adxl380_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+ {
+ .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_RESET_TIMEOUT),
+ },
+ {
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_DOUBLETAP,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT) |
+ BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
+ },
+};
+
+#define ADXL380_ACCEL_CHANNEL(index, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_type = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \
+ BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \
+ BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+ .event_spec = adxl380_events, \
+ .num_event_specs = ARRAY_SIZE(adxl380_events) \
+}
+
+static const struct iio_chan_spec adxl380_channels[] = {
+ ADXL380_ACCEL_CHANNEL(0, ADXL380_X_DATA_H_REG, X),
+ ADXL380_ACCEL_CHANNEL(1, ADXL380_Y_DATA_H_REG, Y),
+ ADXL380_ACCEL_CHANNEL(2, ADXL380_Z_DATA_H_REG, Z),
+ {
+ .type = IIO_TEMP,
+ .address = ADXL380_T_DATA_H_REG,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ .scan_index = 3,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 12,
+ .storagebits = 16,
+ .shift = 4,
+ .endianness = IIO_BE,
+ },
+ },
+};
+
+static int adxl380_config_irq(struct iio_dev *indio_dev)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ unsigned long irq_flag;
+ struct irq_data *desc;
+ u32 irq_type;
+ u8 polarity;
+ int ret;
+
+ desc = irq_get_irq_data(st->irq);
+ if (!desc)
+ return dev_err_probe(st->dev, -EINVAL, "Could not find IRQ %d\n", st->irq);
+
+ irq_type = irqd_get_trigger_type(desc);
+ if (irq_type == IRQ_TYPE_LEVEL_HIGH) {
+ polarity = 0;
+ irq_flag = IRQF_TRIGGER_HIGH | IRQF_ONESHOT;
+ } else if (irq_type == IRQ_TYPE_LEVEL_LOW) {
+ polarity = 1;
+ irq_flag = IRQF_TRIGGER_LOW | IRQF_ONESHOT;
+ } else {
+ return dev_err_probe(st->dev, -EINVAL,
+ "Invalid interrupt 0x%x. Only level interrupts supported\n",
+ irq_type);
+ }
+
+ ret = regmap_update_bits(st->regmap, ADXL380_INT0_REG,
+ ADXL380_INT0_POL_MSK,
+ FIELD_PREP(ADXL380_INT0_POL_MSK, polarity));
+ if (ret)
+ return ret;
+
+ return devm_request_threaded_irq(st->dev, st->irq, NULL,
+ adxl380_irq_handler, irq_flag,
+ indio_dev->name, indio_dev);
+}
+
+static int adxl380_setup(struct iio_dev *indio_dev)
+{
+ unsigned int reg_val;
+ u16 part_id, chip_id;
+ int ret, i;
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ ret = regmap_read(st->regmap, ADXL380_DEVID_AD_REG, ®_val);
+ if (ret)
+ return ret;
+
+ if (reg_val != ADXL380_DEVID_AD_VAL)
+ dev_warn(st->dev, "Unknown chip id %x\n", reg_val);
+
+ ret = regmap_bulk_read(st->regmap, ADLX380_PART_ID_REG,
+ &st->transf_buf, 2);
+ if (ret)
+ return ret;
+
+ part_id = get_unaligned_be16(st->transf_buf);
+ part_id >>= 4;
+
+ if (part_id != ADXL380_ID_VAL)
+ dev_warn(st->dev, "Unknown part id %x\n", part_id);
+
+ ret = regmap_read(st->regmap, ADXL380_MISC_0_REG, ®_val);
+ if (ret)
+ return ret;
+
+ /* Bit to differentiate between ADXL380/382. */
+ if (reg_val & ADXL380_XL382_MSK)
+ chip_id = ADXL382_ID_VAL;
+ else
+ chip_id = ADXL380_ID_VAL;
+
+ if (chip_id != st->chip_info->chip_id)
+ dev_warn(st->dev, "Unknown chip id %x\n", chip_id);
+
+ ret = regmap_write(st->regmap, ADXL380_RESET_REG, ADXL380_RESET_CODE);
+ if (ret)
+ return ret;
+
+ /*
+ * A latency of approximately 0.5 ms is required after soft reset.
+ * Stated in the register REG_RESET description.
+ */
+ fsleep(500);
+
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
+ ADXL380_CHAN_EN_MSK(i),
+ 1 << (4 + i));
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG,
+ ADXL380_FIFO_MODE_MSK,
+ FIELD_PREP(ADXL380_FIFO_MODE_MSK, ADXL380_FIFO_STREAMED));
+ if (ret)
+ return ret;
+
+ /* Select all 3 axis for act/inact detection. */
+ ret = regmap_update_bits(st->regmap, ADXL380_SNSR_AXIS_EN_REG,
+ ADXL380_ACT_INACT_AXIS_EN_MSK,
+ FIELD_PREP(ADXL380_ACT_INACT_AXIS_EN_MSK,
+ ADXL380_ACT_INACT_AXIS_EN_MSK));
+ if (ret)
+ return ret;
+
+ ret = adxl380_config_irq(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = adxl380_fill_lpf_tbl(st);
+ if (ret)
+ return ret;
+
+ ret = adxl380_fill_hpf_tbl(st);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+int adxl380_probe(struct device *dev, struct regmap *regmap,
+ const struct adxl380_chip_info *chip_info)
+{
+ struct iio_dev *indio_dev;
+ struct adxl380_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->dev = dev;
+ st->regmap = regmap;
+ st->chip_info = chip_info;
+
+ mutex_init(&st->lock);
+
+ indio_dev->channels = adxl380_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adxl380_channels);
+ indio_dev->name = chip_info->name;
+ indio_dev->info = &adxl380_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = devm_regulator_get_enable(dev, "vddio");
+ if (ret)
+ return dev_err_probe(st->dev, ret,
+ "Failed to get vddio regulator\n");
+
+ ret = devm_regulator_get_enable(st->dev, "vsupply");
+ if (ret)
+ return dev_err_probe(st->dev, ret,
+ "Failed to get vsupply regulator\n");
+
+ st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT0");
+ if (st->irq > 0) {
+ st->int_map[0] = ADXL380_INT0_MAP0_REG;
+ st->int_map[1] = ADXL380_INT0_MAP1_REG;
+ } else {
+ st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
+ if (st->irq > 0)
+ return dev_err_probe(dev, -ENODEV,
+ "no interrupt name specified");
+ st->int_map[0] = ADXL380_INT1_MAP0_REG;
+ st->int_map[1] = ADXL380_INT1_MAP1_REG;
+ }
+
+ ret = adxl380_setup(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev,
+ &adxl380_buffer_ops,
+ adxl380_fifo_attributes);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(adxl380_probe, IIO_ADXL380);
+
+MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/adxl380.h b/drivers/iio/accel/adxl380.h
new file mode 100644
index 000000000000..a683625d897a
--- /dev/null
+++ b/drivers/iio/accel/adxl380.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * ADXL380 3-Axis Digital Accelerometer
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#ifndef _ADXL380_H_
+#define _ADXL380_H_
+
+struct adxl380_chip_info {
+ const char *name;
+ const int scale_tbl[3][2];
+ const int samp_freq_tbl[3];
+ const int temp_offset;
+ const u16 chip_id;
+};
+
+extern const struct adxl380_chip_info adxl380_chip_info;
+extern const struct adxl380_chip_info adxl382_chip_info;
+
+int adxl380_probe(struct device *dev, struct regmap *regmap,
+ const struct adxl380_chip_info *chip_info);
+bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg);
+
+#endif /* _ADXL380_H_ */
diff --git a/drivers/iio/accel/adxl380_i2c.c b/drivers/iio/accel/adxl380_i2c.c
new file mode 100644
index 000000000000..1dc1e77be815
--- /dev/null
+++ b/drivers/iio/accel/adxl380_i2c.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL380 3-Axis Digital Accelerometer I2C driver
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "adxl380.h"
+
+static const struct regmap_config adxl380_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .readable_noinc_reg = adxl380_readable_noinc_reg,
+};
+
+static int adxl380_i2c_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+ const struct adxl380_chip_info *chip_data;
+
+ chip_data = i2c_get_match_data(client);
+
+ regmap = devm_regmap_init_i2c(client, &adxl380_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return adxl380_probe(&client->dev, regmap, chip_data);
+}
+
+static const struct i2c_device_id adxl380_i2c_id[] = {
+ { "adxl380", (kernel_ulong_t)&adxl380_chip_info },
+ { "adxl382", (kernel_ulong_t)&adxl382_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adxl380_i2c_id);
+
+static const struct of_device_id adxl380_of_match[] = {
+ { .compatible = "adi,adxl380", .data = &adxl380_chip_info },
+ { .compatible = "adi,adxl382", .data = &adxl382_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl380_of_match);
+
+static struct i2c_driver adxl380_i2c_driver = {
+ .driver = {
+ .name = "adxl380_i2c",
+ .of_match_table = adxl380_of_match,
+ },
+ .probe = adxl380_i2c_probe,
+ .id_table = adxl380_i2c_id,
+};
+
+module_i2c_driver(adxl380_i2c_driver);
+
+MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer I2C driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_ADXL380);
diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c
new file mode 100644
index 000000000000..e7b5778cb6cf
--- /dev/null
+++ b/drivers/iio/accel/adxl380_spi.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL380 3-Axis Digital Accelerometer SPI driver
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "adxl380.h"
+
+static const struct regmap_config adxl380_spi_regmap_config = {
+ .reg_bits = 7,
+ .pad_bits = 1,
+ .val_bits = 8,
+ .read_flag_mask = BIT(0),
+ .readable_noinc_reg = adxl380_readable_noinc_reg,
+};
+
+static int adxl380_spi_probe(struct spi_device *spi)
+{
+ const struct adxl380_chip_info *chip_data;
+ struct regmap *regmap;
+
+ chip_data = spi_get_device_match_data(spi);
+
+ regmap = devm_regmap_init_spi(spi, &adxl380_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return adxl380_probe(&spi->dev, regmap, chip_data);
+}
+
+static const struct spi_device_id adxl380_spi_id[] = {
+ { "adxl380", (kernel_ulong_t)&adxl380_chip_info },
+ { "adxl382", (kernel_ulong_t)&adxl382_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adxl380_spi_id);
+
+static const struct of_device_id adxl380_of_match[] = {
+ { .compatible = "adi,adxl380", .data = &adxl380_chip_info },
+ { .compatible = "adi,adxl382", .data = &adxl382_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl380_of_match);
+
+static struct spi_driver adxl380_spi_driver = {
+ .driver = {
+ .name = "adxl380_spi",
+ .of_match_table = adxl380_of_match,
+ },
+ .probe = adxl380_spi_probe,
+ .id_table = adxl380_spi_id,
+};
+
+module_spi_driver(adxl380_spi_driver);
+
+MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer SPI driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_ADXL380);
--
2.45.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 3/3] docs: iio: add documentation for adxl380 driver
2024-07-01 8:30 [PATCH v4 1/3] dt-bindings: iio: accel: add ADXL380 Antoniu Miclaus
2024-07-01 8:30 ` [PATCH v4 2/3] iio: accel: add ADXL380 driver Antoniu Miclaus
@ 2024-07-01 8:30 ` Antoniu Miclaus
1 sibling, 0 replies; 7+ messages in thread
From: Antoniu Miclaus @ 2024-07-01 8:30 UTC (permalink / raw)
To: Ramona Gradinariu, Antoniu Miclaus, Lars-Peter Clausen,
Michael Hennerich, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Matti Vaittinen, Jun Yan, Andy Shevchenko, Mehdi Djait,
Mario Limonciello, linux-iio, devicetree, linux-kernel, linux-doc
Add documentation for adxl380 driver which describes the driver
device files and shows how the user may use the ABI for various
scenarios (configuration, measurement, etc.).
Signed-off-by: Ramona Gradinariu <ramona.gradinariu@analog.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
changes in v4:
- drop marketing from description.
Documentation/iio/adxl380.rst | 233 ++++++++++++++++++++++++++++++++++
Documentation/iio/index.rst | 1 +
2 files changed, 234 insertions(+)
create mode 100644 Documentation/iio/adxl380.rst
diff --git a/Documentation/iio/adxl380.rst b/Documentation/iio/adxl380.rst
new file mode 100644
index 000000000000..376dee5fe1dd
--- /dev/null
+++ b/Documentation/iio/adxl380.rst
@@ -0,0 +1,233 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+ADXL380 driver
+===============
+
+This driver supports Analog Device's ADXL380/382 on SPI/I2C bus.
+
+1. Supported devices
+====================
+
+* `ADXL380 <https://www.analog.com/ADXL380>`_
+* `ADXL382 <https://www.analog.com/ADXL382>`_
+
+The ADXL380/ADXL382 is a low noise density, low power, 3-axis accelerometer with
+selectable measurement ranges. The ADXL380 supports the ±4 g, ±8 g, and ±16 g
+ranges, and the ADXL382 supports ±15 g, ±30 g, and ±60 g ranges.
+
+2. Device attributes
+====================
+
+Accelerometer measurements are always provided.
+
+Temperature data are also provided. This data can be used to monitor the
+internal system temperature or to improve the temperature stability of the
+device via calibration.
+
+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 tables show the adxl380 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_scale | Scale for the accelerometer channels. |
++---------------------------------------------------+----------------------------------------------------------+
+| in_accel_filter_high_pass_3db_frequency | Low pass filter bandwidth. |
++---------------------------------------------------+----------------------------------------------------------+
+| in_accel_filter_high_pass_3db_frequency_available | Available low pass filter bandwidth configurations. |
++---------------------------------------------------+----------------------------------------------------------+
+| in_accel_filter_low_pass_3db_frequency | High pass filter bandwidth. |
++---------------------------------------------------+----------------------------------------------------------+
+| in_accel_filter_low_pass_3db_frequency_available | Available high pass filter bandwidth configurations. |
++---------------------------------------------------+----------------------------------------------------------+
+| 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. |
++---------------------------------------------------+----------------------------------------------------------+
+
++----------------------------------+--------------------------------------------+
+| Temperature sensor related files | Description |
++----------------------------------+--------------------------------------------+
+| in_temp_raw | Raw temperature channel value. |
++----------------------------------+--------------------------------------------+
+| in_temp_offset | Offset for the temperature sensor channel. |
++----------------------------------+--------------------------------------------+
+| in_temp_scale | Scale for the temperature sensor channel. |
++----------------------------------+--------------------------------------------+
+
++------------------------------+----------------------------------------------+
+| Miscellaneous device files | Description |
++------------------------------+----------------------------------------------+
+| name | Name of the IIO device. |
++------------------------------+----------------------------------------------+
+| sampling_frequency | Currently selected sample rate. |
++------------------------------+----------------------------------------------+
+| sampling_frequency_available | Available sampling frequency configurations. |
++------------------------------+----------------------------------------------+
+
+Channels 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.
+
+The adis16475 driver offers data for 2 types of channels, the table below shows
+the measurement units for the processed value, which are defined by the IIO
+framework:
+
++-------------------------------------+---------------------------+
+| Channel type | Measurement unit |
++-------------------------------------+---------------------------+
+| Acceleration on X, Y, and Z axis | Meters per Second squared |
++-------------------------------------+---------------------------+
+| Temperature | Millidegrees Celsius |
++-------------------------------------+---------------------------+
+
+Usage examples
+--------------
+
+Show device name:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat name
+ adxl382
+
+Show accelerometer channels value:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_x_raw
+ -1771
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_y_raw
+ 282
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_z_raw
+ -1523
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_scale
+ 0.004903325
+
+- X-axis acceleration = in_accel_x_raw * in_accel_scale = −8.683788575 m/s^2
+- Y-axis acceleration = in_accel_y_raw * in_accel_scale = 1.38273765 m/s^2
+- Z-axis acceleration = in_accel_z_raw * in_accel_scale = -7.467763975 m/s^2
+
+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
+
+Set sampling frequency:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat sampling_frequency
+ 16000
+ root:/sys/bus/iio/devices/iio:device0> cat sampling_frequency_available
+ 16000 32000 64000
+
+ root:/sys/bus/iio/devices/iio:device0> echo 32000 > sampling_frequency
+ root:/sys/bus/iio/devices/iio:device0> cat sampling_frequency
+ 32000
+
+Set low pass filter bandwidth for accelerometer channels:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_filter_low_pass_3db_frequency
+ 32000
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_filter_low_pass_3db_frequency_available
+ 32000 8000 4000 2000
+
+ root:/sys/bus/iio/devices/iio:device0> echo 2000 > in_accel_filter_low_pass_3db_frequency
+ root:/sys/bus/iio/devices/iio:device0> cat in_accel_filter_low_pass_3db_frequency
+ 2000
+
+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
+ root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_temp_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:/sys/bus/iio/devices/iio:device0> hexdump -C /dev/iio\:device0
+ ...
+ 002bc300 f7 e7 00 a8 fb c5 24 80 f7 e7 01 04 fb d6 24 80 |......$.......$.|
+ 002bc310 f7 f9 00 ab fb dc 24 80 f7 c3 00 b8 fb e2 24 80 |......$.......$.|
+ 002bc320 f7 fb 00 bb fb d1 24 80 f7 b1 00 5f fb d1 24 80 |......$...._..$.|
+ 002bc330 f7 c4 00 c6 fb a6 24 80 f7 a6 00 68 fb f1 24 80 |......$....h..$.|
+ 002bc340 f7 b8 00 a3 fb e7 24 80 f7 9a 00 b1 fb af 24 80 |......$.......$.|
+ 002bc350 f7 b1 00 67 fb ee 24 80 f7 96 00 be fb 92 24 80 |...g..$.......$.|
+ 002bc360 f7 ab 00 7a fc 1b 24 80 f7 b6 00 ae fb 76 24 80 |...z..$......v$.|
+ 002bc370 f7 ce 00 a3 fc 02 24 80 f7 c0 00 be fb 8b 24 80 |......$.......$.|
+ 002bc380 f7 c3 00 93 fb d0 24 80 f7 ce 00 d8 fb c8 24 80 |......$.......$.|
+ 002bc390 f7 bd 00 c0 fb 82 24 80 f8 00 00 e8 fb db 24 80 |......$.......$.|
+ 002bc3a0 f7 d8 00 d3 fb b4 24 80 f8 0b 00 e5 fb c3 24 80 |......$.......$.|
+ 002bc3b0 f7 eb 00 c8 fb 92 24 80 f7 e7 00 ea fb cb 24 80 |......$.......$.|
+ 002bc3c0 f7 fd 00 cb fb 94 24 80 f7 e3 00 f2 fb b8 24 80 |......$.......$.|
+ ...
+
+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.
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index 4c13bfa2865c..1ce5b24d40aa 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -20,5 +20,6 @@ Industrial I/O Kernel Drivers
ad7944
adis16475
adis16480
+ adxl380
bno055
ep93xx_adc
--
2.45.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v4 2/3] iio: accel: add ADXL380 driver
2024-07-01 8:30 ` [PATCH v4 2/3] iio: accel: add ADXL380 driver Antoniu Miclaus
@ 2024-07-03 7:06 ` Alexandru Ardelean
2024-07-07 16:31 ` Jonathan Cameron
2024-07-08 10:22 ` Miclaus, Antoniu
0 siblings, 2 replies; 7+ messages in thread
From: Alexandru Ardelean @ 2024-07-03 7:06 UTC (permalink / raw)
To: Antoniu Miclaus
Cc: Ramona Gradinariu, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Jun Yan, Matti Vaittinen, Mario Limonciello,
Mehdi Djait, linux-iio, devicetree, linux-kernel, linux-doc
On Mon, Jul 1, 2024 at 11:47 AM Antoniu Miclaus
<antoniu.miclaus@analog.com> wrote:
>
> The ADXL380/ADXL382 is a low noise density, low power, 3-axis
> accelerometer with selectable measurement ranges. The ADXL380 supports
> the +/-4 g, +/-8 g, and +/-16 g ranges, and the ADXL382 supports
> +/-15 g, +/-30 g and +/-60 g ranges.
> The ADXL380/ADXL382 offers industry leading noise, enabling precision
> applications with minimal calibration. The low noise, and low power
> ADXL380/ADXL382 enables accurate measurement in an environment with
> high vibration, heart sounds and audio.
>
> In addition to its low power consumption, the ADXL380/ADXL382 has many
> features to enable true system level performance. These include a
> built-in micropower temperature sensor, single / double / triple tap
> detection and a state machine to prevent a false triggering. In
> addition, the ADXL380/ADXL382 has provisions for external control of
> the sampling time and/or an external clock.
>
> Signed-off-by: Ramona Gradinariu <ramona.gradinariu@analog.com>
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> ---
> changes in v4:
> - drop __aligned(IIO_DMA_MINALIGN); from tables and use it on fifo_buf
> - drop brackets around odr >> 1 and odr & 1
> - wrap long line
> - drop comma on null terminator
> - fix odd indentation
> - drop extra space before >
> - add space before } on arrays where missing
> MAINTAINERS | 4 +
> drivers/iio/accel/Kconfig | 27 +
> drivers/iio/accel/Makefile | 3 +
> drivers/iio/accel/adxl380.c | 1908 +++++++++++++++++++++++++++++++
> drivers/iio/accel/adxl380.h | 26 +
> drivers/iio/accel/adxl380_i2c.c | 64 ++
> drivers/iio/accel/adxl380_spi.c | 66 ++
> 7 files changed, 2098 insertions(+)
> create mode 100644 drivers/iio/accel/adxl380.c
> create mode 100644 drivers/iio/accel/adxl380.h
> create mode 100644 drivers/iio/accel/adxl380_i2c.c
> create mode 100644 drivers/iio/accel/adxl380_spi.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1425182c85e2..67583f13da51 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -624,6 +624,10 @@ M: Antoniu Miclaus <antoniu.miclaus@analog.com>
> S: Supported
> W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml
> +F: drivers/iio/accel/adxl380.c
> +F: drivers/iio/accel/adxl380.h
> +F: drivers/iio/accel/adxl380_i2c.c
> +F: drivers/iio/accel/adxl380_spi.c
>
> AF8133J THREE-AXIS MAGNETOMETER DRIVER
> M: Ondřej Jirman <megi@xff.cz>
> diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
> index c2da5066e9a7..6572ab447e14 100644
> --- a/drivers/iio/accel/Kconfig
> +++ b/drivers/iio/accel/Kconfig
> @@ -177,6 +177,33 @@ config ADXL372_I2C
> To compile this driver as a module, choose M here: the
> module will be called adxl372_i2c.
>
> +config ADXL380
> + tristate
> + select IIO_BUFFER
> + select IIO_TRIGGERED_BUFFER
> +
> +config ADXL380_SPI
> + tristate "Analog Devices ADXL380 3-Axis Accelerometer SPI Driver"
> + depends on SPI
> + select ADXL380
> + select REGMAP_SPI
> + help
> + Say yes here to add support for the Analog Devices ADXL380 triaxial
> + acceleration sensor.
> + To compile this driver as a module, choose M here: the
> + module will be called adxl380_spi.
> +
> +config ADXL380_I2C
> + tristate "Analog Devices ADXL380 3-Axis Accelerometer I2C Driver"
> + depends on I2C
> + select ADXL380
> + select REGMAP_I2C
> + help
> + Say yes here to add support for the Analog Devices ADXL380 triaxial
> + acceleration sensor.
> + To compile this driver as a module, choose M here: the
> + module will be called adxl380_i2c.
> +
> config BMA180
> tristate "Bosch BMA023/BMA1x0/BMA250 3-Axis Accelerometer Driver"
> depends on I2C && INPUT_BMA150=n
> diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile
> index db90532ba24a..ca8569e25aba 100644
> --- a/drivers/iio/accel/Makefile
> +++ b/drivers/iio/accel/Makefile
> @@ -21,6 +21,9 @@ obj-$(CONFIG_ADXL367_SPI) += adxl367_spi.o
> obj-$(CONFIG_ADXL372) += adxl372.o
> obj-$(CONFIG_ADXL372_I2C) += adxl372_i2c.o
> obj-$(CONFIG_ADXL372_SPI) += adxl372_spi.o
> +obj-$(CONFIG_ADXL380) += adxl380.o
> +obj-$(CONFIG_ADXL380_I2C) += adxl380_i2c.o
> +obj-$(CONFIG_ADXL380_SPI) += adxl380_spi.o
> obj-$(CONFIG_BMA180) += bma180.o
> obj-$(CONFIG_BMA220) += bma220_spi.o
> obj-$(CONFIG_BMA400) += bma400_core.o
> diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c
> new file mode 100644
> index 000000000000..d37f4d85cf84
> --- /dev/null
> +++ b/drivers/iio/accel/adxl380.c
> @@ -0,0 +1,1908 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * ADXL380 3-Axis Digital Accelerometer core driver
> + *
> + * Copyright 2024 Analog Devices Inc.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/units.h>
> +
> +#include <asm/unaligned.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>
> +
> +#include <linux/regulator/consumer.h>
> +
> +#include "adxl380.h"
> +
> +#define ADXL380_ID_VAL 380
> +#define ADXL382_ID_VAL 382
> +
> +#define ADXL380_DEVID_AD_REG 0x00
> +#define ADLX380_PART_ID_REG 0x02
> +
> +#define ADXL380_X_DATA_H_REG 0x15
> +#define ADXL380_Y_DATA_H_REG 0x17
> +#define ADXL380_Z_DATA_H_REG 0x19
> +#define ADXL380_T_DATA_H_REG 0x1B
> +
> +#define ADXL380_MISC_0_REG 0x20
> +#define ADXL380_XL382_MSK BIT(7)
> +
> +#define ADXL380_MISC_1_REG 0x21
> +
> +#define ADXL380_X_DSM_OFFSET_REG 0x4D
> +
> +#define ADXL380_ACT_INACT_CTL_REG 0x37
> +#define ADXL380_INACT_EN_MSK BIT(2)
> +#define ADXL380_ACT_EN_MSK BIT(0)
> +
> +#define ADXL380_SNSR_AXIS_EN_REG 0x38
> +#define ADXL380_ACT_INACT_AXIS_EN_MSK GENMASK(2, 0)
> +
> +#define ADXL380_THRESH_ACT_H_REG 0x39
> +#define ADXL380_TIME_ACT_H_REG 0x3B
> +#define ADXL380_THRESH_INACT_H_REG 0x3E
> +#define ADXL380_TIME_INACT_H_REG 0x40
> +#define ADXL380_THRESH_MAX GENMASK(12, 0)
> +#define ADXL380_TIME_MAX GENMASK(24, 0)
> +
> +#define ADXL380_FIFO_CONFIG_0_REG 0x30
> +#define ADXL380_FIFO_SAMPLES_8_MSK BIT(0)
> +#define ADXL380_FIFO_MODE_MSK GENMASK(5, 4)
> +
> +#define ADXL380_FIFO_DISABLED 0
> +#define ADXL380_FIFO_NORMAL 1
> +#define ADXL380_FIFO_STREAMED 2
> +#define ADXL380_FIFO_TRIGGERED 3
> +
> +#define ADXL380_FIFO_CONFIG_1_REG 0x31
> +#define ADXL380_FIFO_STATUS_0_REG 0x1E
> +
> +#define ADXL380_TAP_THRESH_REG 0x43
> +#define ADXL380_TAP_DUR_REG 0x44
> +#define ADXL380_TAP_LATENT_REG 0x45
> +#define ADXL380_TAP_WINDOW_REG 0x46
> +#define ADXL380_TAP_TIME_MAX GENMASK(7, 0)
> +
> +#define ADXL380_TAP_CFG_REG 0x47
> +#define ADXL380_TAP_AXIS_MSK GENMASK(1, 0)
> +
> +#define ADXL380_TRIG_CFG_REG 0x49
> +#define ADXL380_TRIG_CFG_DEC_2X_MSK BIT(7)
> +#define ADXL380_TRIG_CFG_SINC_RATE_MSK BIT(6)
> +
> +#define ADXL380_FILTER_REG 0x50
> +#define ADXL380_FILTER_EQ_FILT_MSK BIT(6)
> +#define ADXL380_FILTER_LPF_MODE_MSK GENMASK(5, 4)
> +#define ADXL380_FILTER_HPF_PATH_MSK BIT(3)
> +#define ADXL380_FILTER_HPF_CORNER_MSK GENMASK(2, 0)
> +
> +#define ADXL380_OP_MODE_REG 0x26
> +#define ADXL380_OP_MODE_RANGE_MSK GENMASK(7, 6)
> +#define ADXL380_OP_MODE_MSK GENMASK(3, 0)
> +#define ADXL380_OP_MODE_STANDBY 0
> +#define ADXL380_OP_MODE_HEART_SOUND 1
> +#define ADXL380_OP_MODE_ULP 2
> +#define ADXL380_OP_MODE_VLP 3
> +#define ADXL380_OP_MODE_LP 4
> +#define ADXL380_OP_MODE_LP_ULP 6
> +#define ADXL380_OP_MODE_LP_VLP 7
> +#define ADXL380_OP_MODE_RBW 8
> +#define ADXL380_OP_MODE_RBW_ULP 10
> +#define ADXL380_OP_MODE_RBW_VLP 11
> +#define ADXL380_OP_MODE_HP 12
> +#define ADXL380_OP_MODE_HP_ULP 14
> +#define ADXL380_OP_MODE_HP_VLP 15
> +
> +#define ADXL380_OP_MODE_4G_RANGE 0
> +#define ADXL382_OP_MODE_15G_RANGE 0
> +#define ADXL380_OP_MODE_8G_RANGE 1
> +#define ADXL382_OP_MODE_30G_RANGE 1
> +#define ADXL380_OP_MODE_16G_RANGE 2
> +#define ADXL382_OP_MODE_60G_RANGE 2
> +
> +#define ADXL380_DIG_EN_REG 0x27
> +#define ADXL380_CHAN_EN_MSK(chan) BIT(4 + (chan))
> +#define ADXL380_FIFO_EN_MSK BIT(3)
> +
> +#define ADXL380_INT0_MAP0_REG 0x2B
> +#define ADXL380_INT1_MAP0_REG 0x2D
> +#define ADXL380_INT_MAP0_INACT_INT0_MSK BIT(6)
> +#define ADXL380_INT_MAP0_ACT_INT0_MSK BIT(5)
> +#define ADXL380_INT_MAP0_FIFO_WM_INT0_MSK BIT(3)
> +
> +#define ADXL380_INT0_MAP1_REG 0x2C
> +#define ADXL380_INT1_MAP1_REG 0x2E
> +#define ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK BIT(1)
> +#define ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK BIT(0)
> +
> +#define ADXL380_INT0_REG 0x5D
> +#define ADXL380_INT0_POL_MSK BIT(7)
> +
> +#define ADXL380_RESET_REG 0x2A
> +#define ADXL380_FIFO_DATA 0x1D
> +
> +#define ADXL380_DEVID_AD_VAL 0xAD
> +#define ADXL380_RESET_CODE 0x52
> +
> +#define ADXL380_STATUS_0_REG 0x11
> +#define ADXL380_STATUS_0_FIFO_FULL_MSK BIT(1)
> +#define ADXL380_STATUS_0_FIFO_WM_MSK BIT(3)
> +
> +#define ADXL380_STATUS_1_INACT_MSK BIT(6)
> +#define ADXL380_STATUS_1_ACT_MSK BIT(5)
> +#define ADXL380_STATUS_1_DOUBLE_TAP_MSK BIT(1)
> +#define ADXL380_STATUS_1_SINGLE_TAP_MSK BIT(0)
> +
> +#define ADXL380_FIFO_SAMPLES 315UL
> +
> +enum adxl380_channels {
> + ADXL380_ACCEL_X,
> + ADXL380_ACCEL_Y,
> + ADXL380_ACCEL_Z,
> + ADXL380_TEMP,
> + ADXL380_CH_NUM,
nitpick: If ADXL380_CH_NUM is the number of channels, then a trailing
comma is not needed.
Fine to also leave it.
> +};
> +
> +enum adxl380_axis {
> + ADXL380_X_AXIS,
> + ADXL380_Y_AXIS,
> + ADXL380_Z_AXIS,
> +};
> +
> +enum adxl380_activity_type {
> + ADXL380_ACTIVITY,
> + ADXL380_INACTIVITY,
> +};
> +
> +enum adxl380_tap_type {
> + ADXL380_SINGLE_TAP,
> + ADXL380_DOUBLE_TAP,
> +};
> +
> +enum adxl380_tap_time_type {
> + ADXL380_TAP_TIME_LATENT,
> + ADXL380_TAP_TIME_WINDOW,
> +};
> +
> +static const int adxl380_range_scale_factor_tbl[] = { 1, 2, 4 };
> +
> +const struct adxl380_chip_info adxl380_chip_info = {
> + .name = "adxl380",
> + .chip_id = ADXL380_ID_VAL,
> + .scale_tbl = {
> + [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 },
> + [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 },
> + [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 },
> + },
> + .samp_freq_tbl = { 8000, 16000, 32000 },
> + /*
> + * The datasheet defines an intercept of 470 LSB at 25 degC
> + * and a sensitivity of 10.2 LSB/C.
> + */
> + .temp_offset = 25 * 102 / 10 - 470,
> +
> +};
> +EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, IIO_ADXL380);
> +
> +const struct adxl380_chip_info adxl382_chip_info = {
> + .name = "adxl382",
> + .chip_id = ADXL382_ID_VAL,
> + .scale_tbl = {
> + [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 },
> + [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 },
> + [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 },
> + },
> + .samp_freq_tbl = { 16000, 32000, 64000 },
> + /*
> + * The datasheet defines an intercept of 570 LSB at 25 degC
> + * and a sensitivity of 10.2 LSB/C.
> + */
> + .temp_offset = 25 * 102 / 10 - 570,
> +};
> +EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, IIO_ADXL380);
> +
> +static const unsigned int adxl380_th_reg_high_addr[2] = {
> + [ADXL380_ACTIVITY] = ADXL380_THRESH_ACT_H_REG,
> + [ADXL380_INACTIVITY] = ADXL380_THRESH_INACT_H_REG,
> +};
> +
> +static const unsigned int adxl380_time_reg_high_addr[2] = {
> + [ADXL380_ACTIVITY] = ADXL380_TIME_ACT_H_REG,
> + [ADXL380_INACTIVITY] = ADXL380_TIME_INACT_H_REG,
> +};
> +
> +static const unsigned int adxl380_tap_time_reg[2] = {
> + [ADXL380_TAP_TIME_LATENT] = ADXL380_TAP_LATENT_REG,
> + [ADXL380_TAP_TIME_WINDOW] = ADXL380_TAP_WINDOW_REG,
> +};
> +
> +struct adxl380_state {
> + struct regmap *regmap;
> + struct device *dev;
> + const struct adxl380_chip_info *chip_info;
> + /*
> + * Synchronize access to members of driver state, and ensure atomicity
> + * of consecutive regmap operations.
> + */
> + struct mutex lock;
> + enum adxl380_axis tap_axis_en;
> + u8 range;
> + u8 odr;
> + u8 fifo_set_size;
> + u8 transf_buf[3];
> + u16 watermark;
> + u32 act_time_ms;
> + u32 act_threshold;
> + u32 inact_time_ms;
> + u32 inact_threshold;
> + u32 tap_latent_us;
> + u32 tap_window_us;
> + u32 tap_duration_us;
> + u32 tap_threshold;
> + int irq;
> + int int_map[2];
> + int lpf_tbl[4];
> + int hpf_tbl[7][2];
> +
> + __be16 fifo_buf[ADXL380_FIFO_SAMPLES] __aligned(IIO_DMA_MINALIGN);
> +};
> +
> +bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg)
> +{
> + return reg == ADXL380_FIFO_DATA;
> +}
> +EXPORT_SYMBOL_NS_GPL(adxl380_readable_noinc_reg, IIO_ADXL380);
> +
> +static int adxl380_set_measure_en(struct adxl380_state *st, bool en)
> +{
> + int ret;
> + unsigned int act_inact_ctl;
> + u8 op_mode = ADXL380_OP_MODE_STANDBY;
> +
> + if (en) {
> + ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG, &act_inact_ctl);
> + if (ret)
> + return ret;
> +
> + /* Activity/ Inactivity detection available only in VLP/ULP mode */
> + if (FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) ||
> + FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl))
> + op_mode = ADXL380_OP_MODE_VLP;
> + else
> + op_mode = ADXL380_OP_MODE_HP;
> + }
> +
> + return regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG,
> + ADXL380_OP_MODE_MSK,
> + FIELD_PREP(ADXL380_OP_MODE_MSK, op_mode));
> +}
> +
> +static void adxl380_scale_act_inact_thresholds(struct adxl380_state *st,
> + u8 old_range,
> + u8 new_range)
> +{
> + st->act_threshold = mult_frac(st->act_threshold,
> + adxl380_range_scale_factor_tbl[old_range],
> + adxl380_range_scale_factor_tbl[new_range]);
> + st->inact_threshold = mult_frac(st->inact_threshold,
> + adxl380_range_scale_factor_tbl[old_range],
> + adxl380_range_scale_factor_tbl[new_range]);
> +}
> +
> +static int adxl380_write_act_inact_threshold(struct adxl380_state *st,
> + enum adxl380_activity_type act,
> + unsigned int th)
> +{
> + int ret;
> + u8 reg = adxl380_th_reg_high_addr[act];
> +
> + if (th > ADXL380_THRESH_MAX)
> + return -EINVAL;
> +
> + ret = regmap_write(st->regmap, reg + 1, th & GENMASK(7, 0));
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, reg, GENMASK(2, 0), th >> 8);
> + if (ret)
> + return ret;
> +
> + if (act == ADXL380_ACTIVITY)
> + st->act_threshold = th;
> + else
> + st->inact_threshold = th;
> +
> + return 0;
> +}
> +
> +static int adxl380_set_act_inact_threshold(struct iio_dev *indio_dev,
> + enum adxl380_activity_type act,
> + u16 th)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_write_act_inact_threshold(st, act, th);
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_set_tap_threshold_value(struct iio_dev *indio_dev, u8 th)
> +{
> + int ret;
> + struct adxl380_state *st = iio_priv(indio_dev);
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, ADXL380_TAP_THRESH_REG, th);
> + if (ret)
> + return ret;
> +
> + st->tap_threshold = th;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int _adxl380_write_tap_time_us(struct adxl380_state *st,
> + enum adxl380_tap_time_type tap_time_type,
> + u32 us)
> +{
> + u8 reg = adxl380_tap_time_reg[tap_time_type];
> + unsigned int reg_val;
> + int ret;
> +
> + /* scale factor for tap window is 1250us / LSB */
> + reg_val = DIV_ROUND_CLOSEST(us, 1250);
> + if (reg_val > ADXL380_TAP_TIME_MAX)
> + reg_val = ADXL380_TAP_TIME_MAX;
> +
> + ret = regmap_write(st->regmap, reg, reg_val);
> + if (ret)
> + return ret;
> +
> + if (tap_time_type == ADXL380_TAP_TIME_WINDOW)
> + st->tap_window_us = us;
> + else
> + st->tap_latent_us = us;
> +
> + return 0;
> +}
> +
> +static int adxl380_write_tap_time_us(struct adxl380_state *st,
> + enum adxl380_tap_time_type tap_time_type, u32 us)
> +{
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = _adxl380_write_tap_time_us(st, tap_time_type, us);
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_write_tap_dur_us(struct iio_dev *indio_dev, u32 us)
> +{
> + int ret;
> + unsigned int reg_val;
> + struct adxl380_state *st = iio_priv(indio_dev);
> +
> + /* 625us per code is the scale factor of TAP_DUR register */
> + reg_val = DIV_ROUND_CLOSEST(us, 625);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, ADXL380_TAP_DUR_REG, reg_val);
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_read_chn(struct adxl380_state *st, u8 addr)
> +{
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = regmap_bulk_read(st->regmap, addr, &st->transf_buf, 2);
> + if (ret)
> + return ret;
> +
> + return get_unaligned_be16(st->transf_buf);
> +}
> +
> +static int adxl380_get_odr(struct adxl380_state *st, int *odr)
> +{
> + int ret;
> + unsigned int trig_cfg, odr_idx;
> +
> + ret = regmap_read(st->regmap, ADXL380_TRIG_CFG_REG, &trig_cfg);
> + if (ret)
> + return ret;
> +
> + odr_idx = (FIELD_GET(ADXL380_TRIG_CFG_SINC_RATE_MSK, trig_cfg) << 1) |
> + (FIELD_GET(ADXL380_TRIG_CFG_DEC_2X_MSK, trig_cfg) & 1);
> +
> + *odr = st->chip_info->samp_freq_tbl[odr_idx];
> +
> + return 0;
> +}
> +
> +static const int adxl380_lpf_div[] = {
> + 1, 4, 8, 16,
> +};
> +
> +static int adxl380_fill_lpf_tbl(struct adxl380_state *st)
> +{
> + int ret, i;
> + int odr;
> +
> + ret = adxl380_get_odr(st, &odr);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < ARRAY_SIZE(st->lpf_tbl); i++)
> + st->lpf_tbl[i] = DIV_ROUND_CLOSEST(odr, adxl380_lpf_div[i]);
> +
> + return 0;
> +}
> +
> +static const int adxl380_hpf_mul[] = {
> + 0, 247000, 62084, 15545, 3862, 954, 238,
> +};
> +
> +static int adxl380_fill_hpf_tbl(struct adxl380_state *st)
> +{
> + int i, ret, odr_hz;
> + u32 multiplier;
> + u64 div, rem, odr;
> +
> + ret = adxl380_get_odr(st, &odr_hz);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < ARRAY_SIZE(adxl380_hpf_mul); i++) {
> + odr = mul_u64_u32_shr(odr_hz, MEGA, 0);
> + multiplier = adxl380_hpf_mul[i];
> + div = div64_u64_rem(mul_u64_u32_shr(odr, multiplier, 0),
> + TERA * 100, &rem);
> +
> + st->hpf_tbl[i][0] = div;
> + st->hpf_tbl[i][1] = div_u64(rem, MEGA * 100);
> + }
> +
> + return 0;
> +}
> +
> +static int adxl380_set_odr(struct adxl380_state *st, u8 odr)
> +{
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG,
> + ADXL380_TRIG_CFG_DEC_2X_MSK,
> + FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, odr & 1));
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG,
> + ADXL380_TRIG_CFG_SINC_RATE_MSK,
> + FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, odr >> 1));
> + if (ret)
> + return ret;
> +
> + ret = adxl380_set_measure_en(st, true);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_fill_lpf_tbl(st);
> + if (ret)
> + return ret;
> +
> + return adxl380_fill_hpf_tbl(st);
> +}
> +
> +static int adxl380_find_match_1d_tbl(const int *array, unsigned int size,
> + int val)
I think this was copied from adxl372.
But, I am wondering (at a later point in time), if it makes sense to
use (or create) a common utility function for this.
I haven't looked yet, if there is one.
> +{
> + int i;
> +
> + for (i = 0; i < size; i++) {
> + if (val == array[i])
> + return i;
> + }
> +
> + return size - 1;
> +}
> +
> +static int adxl380_find_match_2d_tbl(const int (*freq_tbl)[2], int n, int val, int val2)
> +{
> + int i;
> +
> + for (i = 0; i < n; i++) {
> + if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2)
> + return i;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int adxl380_get_lpf(struct adxl380_state *st, int *lpf)
> +{
> + int ret;
> + unsigned int trig_cfg, lpf_idx;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg);
> + if (ret)
> + return ret;
> +
> + lpf_idx = FIELD_GET(ADXL380_FILTER_LPF_MODE_MSK, trig_cfg);
> +
> + *lpf = st->lpf_tbl[lpf_idx];
> +
> + return 0;
> +}
> +
> +static int adxl380_set_lpf(struct adxl380_state *st, u8 lpf)
> +{
> + int ret;
> + u8 eq_bypass = 0;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + if (lpf)
> + eq_bypass = 1;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
> + ADXL380_FILTER_EQ_FILT_MSK,
> + FIELD_PREP(ADXL380_FILTER_EQ_FILT_MSK, eq_bypass));
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
> + ADXL380_FILTER_LPF_MODE_MSK,
> + FIELD_PREP(ADXL380_FILTER_LPF_MODE_MSK, lpf));
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_get_hpf(struct adxl380_state *st, int *hpf_int, int *hpf_frac)
> +{
> + int ret;
> + unsigned int trig_cfg, hpf_idx;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg);
> + if (ret)
> + return ret;
> +
> + hpf_idx = FIELD_GET(ADXL380_FILTER_HPF_CORNER_MSK, trig_cfg);
> +
> + *hpf_int = st->hpf_tbl[hpf_idx][0];
> + *hpf_frac = st->hpf_tbl[hpf_idx][1];
> +
> + return 0;
> +}
> +
> +static int adxl380_set_hpf(struct adxl380_state *st, u8 hpf)
> +{
> + int ret;
> + u8 hpf_path = 0;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + if (hpf)
> + hpf_path = 1;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
> + ADXL380_FILTER_HPF_PATH_MSK,
> + FIELD_PREP(ADXL380_FILTER_HPF_PATH_MSK, hpf_path));
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
> + ADXL380_FILTER_HPF_CORNER_MSK,
> + FIELD_PREP(ADXL380_FILTER_HPF_CORNER_MSK, hpf));
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int _adxl380_set_act_inact_time_ms(struct adxl380_state *st,
> + enum adxl380_activity_type act,
> + u32 ms)
> +{
> + u8 reg = adxl380_time_reg_high_addr[act];
> + unsigned int reg_val;
> + int ret;
> +
> + /* 500us per code is the scale factor of TIME_ACT / TIME_INACT registers */
> + reg_val = min(DIV_ROUND_CLOSEST(ms * 1000, 500), ADXL380_TIME_MAX);
> +
> + put_unaligned_be24(reg_val, &st->transf_buf[0]);
> +
> + ret = regmap_bulk_write(st->regmap, reg, st->transf_buf, sizeof(st->transf_buf));
> + if (ret)
> + return ret;
> +
> + if (act == ADXL380_ACTIVITY)
> + st->act_time_ms = ms;
> + else
> + st->inact_time_ms = ms;
> +
> + return 0;
> +}
> +
> +static int adxl380_set_act_inact_time_ms(struct adxl380_state *st,
> + enum adxl380_activity_type act,
> + u32 ms)
> +{
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = _adxl380_set_act_inact_time_ms(st, act, ms);
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_set_range(struct adxl380_state *st, u8 range)
> +{
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG,
> + ADXL380_OP_MODE_RANGE_MSK,
> + FIELD_PREP(ADXL380_OP_MODE_RANGE_MSK, range));
> +
> + if (ret)
> + return ret;
> +
> + adxl380_scale_act_inact_thresholds(st, st->range, range);
> +
> + /* Activity thresholds depend on range */
> + ret = adxl380_write_act_inact_threshold(st, ADXL380_ACTIVITY,
> + st->act_threshold);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_write_act_inact_threshold(st, ADXL380_INACTIVITY,
> + st->inact_threshold);
> + if (ret)
> + return ret;
> +
> + st->range = range;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_write_act_inact_en(struct adxl380_state *st,
> + enum adxl380_activity_type type,
> + bool en)
> +{
> + if (type == ADXL380_ACTIVITY)
> + return regmap_update_bits(st->regmap, ADXL380_ACT_INACT_CTL_REG,
> + ADXL380_ACT_EN_MSK,
> + FIELD_PREP(ADXL380_ACT_EN_MSK, en));
> +
> + return regmap_update_bits(st->regmap, ADXL380_ACT_INACT_CTL_REG,
> + ADXL380_INACT_EN_MSK,
> + FIELD_PREP(ADXL380_INACT_EN_MSK, en));
> +}
> +
> +static int adxl380_read_act_inact_int(struct adxl380_state *st,
> + enum adxl380_activity_type type,
> + bool *en)
> +{
> + int ret;
> + unsigned int reg_val;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = regmap_read(st->regmap, st->int_map[0], ®_val);
> + if (ret)
> + return ret;
> +
> + if (type == ADXL380_ACTIVITY)
> + *en = FIELD_GET(ADXL380_INT_MAP0_ACT_INT0_MSK, reg_val);
> + else
> + *en = FIELD_GET(ADXL380_INT_MAP0_INACT_INT0_MSK, reg_val);
> +
> + return 0;
> +}
> +
> +static int adxl380_write_act_inact_int(struct adxl380_state *st,
> + enum adxl380_activity_type act,
> + bool en)
> +{
> + if (act == ADXL380_ACTIVITY)
> + return regmap_update_bits(st->regmap, st->int_map[0],
> + ADXL380_INT_MAP0_ACT_INT0_MSK,
> + FIELD_PREP(ADXL380_INT_MAP0_ACT_INT0_MSK, en));
> +
> + return regmap_update_bits(st->regmap, st->int_map[0],
> + ADXL380_INT_MAP0_INACT_INT0_MSK,
> + FIELD_PREP(ADXL380_INT_MAP0_INACT_INT0_MSK, en));
> +}
> +
> +static int adxl380_act_inact_config(struct adxl380_state *st,
> + enum adxl380_activity_type type,
> + bool en)
> +{
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_write_act_inact_en(st, type, en);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_write_act_inact_int(st, type, en);
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_write_tap_axis(struct adxl380_state *st,
> + enum adxl380_axis axis)
> +{
> + int ret;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_TAP_CFG_REG,
> + ADXL380_TAP_AXIS_MSK,
> + FIELD_PREP(ADXL380_TAP_AXIS_MSK, axis));
> +
> + if (ret)
> + return ret;
> +
> + st->tap_axis_en = axis;
> +
> + return 0;
> +}
> +
> +static int adxl380_read_tap_int(struct adxl380_state *st, enum adxl380_tap_type type, bool *en)
> +{
> + int ret;
> + unsigned int reg_val;
> +
> + ret = regmap_read(st->regmap, st->int_map[1], ®_val);
> + if (ret)
> + return ret;
> +
> + if (type == ADXL380_SINGLE_TAP)
> + *en = FIELD_GET(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, reg_val);
> + else
> + *en = FIELD_GET(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, reg_val);
> +
> + return 0;
> +}
> +
> +static int adxl380_write_tap_int(struct adxl380_state *st, enum adxl380_tap_type type, bool en)
> +{
> + if (type == ADXL380_SINGLE_TAP)
> + return regmap_update_bits(st->regmap, st->int_map[1],
> + ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK,
> + FIELD_PREP(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, en));
> +
> + return regmap_update_bits(st->regmap, st->int_map[1],
> + ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK,
> + FIELD_PREP(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, en));
> +}
> +
> +static int adxl380_tap_config(struct adxl380_state *st,
> + enum adxl380_axis axis,
> + enum adxl380_tap_type type,
> + bool en)
> +{
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_write_tap_axis(st, axis);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_write_tap_int(st, type, en);
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_set_fifo_samples(struct adxl380_state *st)
> +{
> + int ret;
> + u16 fifo_samples = st->watermark * st->fifo_set_size;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG,
> + ADXL380_FIFO_SAMPLES_8_MSK,
> + FIELD_PREP(ADXL380_FIFO_SAMPLES_8_MSK,
> + (fifo_samples & BIT(8))));
> + if (ret)
> + return ret;
> +
> + return regmap_write(st->regmap, ADXL380_FIFO_CONFIG_1_REG,
> + fifo_samples & 0xFF);
> +}
> +
> +static int adxl380_get_status(struct adxl380_state *st, u8 *status0, u8 *status1)
> +{
> + int ret;
> +
> + /* STATUS0, STATUS1 are adjacent regs */
> + ret = regmap_bulk_read(st->regmap, ADXL380_STATUS_0_REG,
> + &st->transf_buf, 2);
> + if (ret)
> + return ret;
> +
> + *status0 = st->transf_buf[0];
> + *status1 = st->transf_buf[1];
> +
> + return 0;
> +}
> +
> +static int adxl380_get_fifo_entries(struct adxl380_state *st, u16 *fifo_entries)
> +{
> + int ret;
> +
> + ret = regmap_bulk_read(st->regmap, ADXL380_FIFO_STATUS_0_REG,
> + &st->transf_buf, 2);
> + if (ret)
> + return ret;
> +
> + *fifo_entries = st->transf_buf[0] | ((BIT(0) & st->transf_buf[1]) << 8);
> +
> + return 0;
> +}
> +
> +static void adxl380_push_event(struct iio_dev *indio_dev, s64 timestamp,
> + u8 status1)
> +{
> + if (FIELD_GET(ADXL380_STATUS_1_ACT_MSK, status1))
> + 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_RISING),
> + timestamp);
> +
> + if (FIELD_GET(ADXL380_STATUS_1_INACT_MSK, status1))
> + 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),
> + timestamp);
> + if (FIELD_GET(ADXL380_STATUS_1_SINGLE_TAP_MSK, status1))
> + 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),
> + timestamp);
> +
> + if (FIELD_GET(ADXL380_STATUS_1_DOUBLE_TAP_MSK, status1))
> + 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_DOUBLETAP),
> + timestamp);
> +}
> +
> +static irqreturn_t adxl380_irq_handler(int irq, void *p)
> +{
> + struct iio_dev *indio_dev = p;
> + struct adxl380_state *st = iio_priv(indio_dev);
> + u8 status0, status1;
> + u16 fifo_entries;
> + int i;
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_get_status(st, &status0, &status1);
> + if (ret)
> + return IRQ_HANDLED;
> +
> + adxl380_push_event(indio_dev, iio_get_time_ns(indio_dev), status1);
> +
> + if (!FIELD_GET(ADXL380_STATUS_0_FIFO_WM_MSK, status0))
> + return IRQ_HANDLED;
> +
> + ret = adxl380_get_fifo_entries(st, &fifo_entries);
> + if (ret)
> + return IRQ_HANDLED;
> +
> + for (i = 0; i < fifo_entries; i += st->fifo_set_size) {
> + ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA,
> + &st->fifo_buf[i],
> + 2 * st->fifo_set_size);
> + if (ret)
> + return IRQ_HANDLED;
> + iio_push_to_buffers(indio_dev, &st->fifo_buf[i]);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int adxl380_write_calibbias_value(struct adxl380_state *st,
> + unsigned long chan_addr,
> + s8 calibbias)
> +{
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, ADXL380_X_DSM_OFFSET_REG + chan_addr, calibbias);
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_read_calibbias_value(struct adxl380_state *st,
> + unsigned long chan_addr,
> + int *calibbias)
> +{
> + int ret;
> + unsigned int reg_val;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = regmap_read(st->regmap, ADXL380_X_DSM_OFFSET_REG + chan_addr, ®_val);
> + if (ret)
> + return ret;
> +
> + *calibbias = sign_extend32(reg_val, 7);
> +
> + return 0;
> +}
> +
> +static ssize_t hwfifo_watermark_min_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "1\n");
> +}
> +
> +static ssize_t hwfifo_watermark_max_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%lu\n", ADXL380_FIFO_SAMPLES);
> +}
> +
> +static ssize_t adxl380_get_fifo_watermark(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl380_state *st = iio_priv(indio_dev);
> +
> + return sysfs_emit(buf, "%d\n", st->watermark);
> +}
> +
> +static ssize_t adxl380_get_fifo_enabled(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl380_state *st = iio_priv(indio_dev);
> + int ret;
> + unsigned int reg_val;
> +
> + ret = regmap_read(st->regmap, ADXL380_DIG_EN_REG, ®_val);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%lu\n",
> + FIELD_GET(ADXL380_FIFO_EN_MSK, reg_val));
> +}
> +
> +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_min, 0);
> +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0);
> +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444,
> + adxl380_get_fifo_watermark, NULL, 0);
> +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444,
> + adxl380_get_fifo_enabled, NULL, 0);
> +
> +static const struct iio_dev_attr *adxl380_fifo_attributes[] = {
> + &iio_dev_attr_hwfifo_watermark_min,
> + &iio_dev_attr_hwfifo_watermark_max,
> + &iio_dev_attr_hwfifo_watermark,
> + &iio_dev_attr_hwfifo_enabled,
> + NULL
> +};
> +
> +static int adxl380_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> + int i;
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap,
> + st->int_map[0],
> + ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
> + FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 1));
> + if (ret)
> + return ret;
> +
> + for_each_clear_bit(i, indio_dev->active_scan_mask, ADXL380_CH_NUM) {
Would this need to be?:
for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength)
Or, is the logic intended to go over the cleared bits here?
Depending on what's needed here, this could make use of
"iio_for_each_active_channel()" later.
> + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> + ADXL380_CHAN_EN_MSK(i),
> + 0 << (4 + i));
> + if (ret)
> + return ret;
> + }
> +
> + st->fifo_set_size = bitmap_weight(indio_dev->active_scan_mask,
> + indio_dev->masklength);
Depending on Nuno's series (and if that gets accepted first), this
might need to use the new iio_get_masklength() wrapper.
That's not a reason against this going in first though.
> +
> + if ((st->watermark * st->fifo_set_size) > ADXL380_FIFO_SAMPLES)
> + st->watermark = (ADXL380_FIFO_SAMPLES / st->fifo_set_size);
> +
> + ret = adxl380_set_fifo_samples(st);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, ADXL380_FIFO_EN_MSK,
> + FIELD_PREP(ADXL380_FIFO_EN_MSK, 1));
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static int adxl380_buffer_predisable(struct iio_dev *indio_dev)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> + int ret, i;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = adxl380_set_measure_en(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap,
> + st->int_map[0],
> + ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
> + FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 0));
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < indio_dev->num_channels; i++) {
> + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> + ADXL380_CHAN_EN_MSK(i),
> + 1 << (4 + i));
> + if (ret)
> + return ret;
> + }
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, ADXL380_FIFO_EN_MSK,
> + FIELD_PREP(ADXL380_FIFO_EN_MSK, 0));
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +static const struct iio_buffer_setup_ops adxl380_buffer_ops = {
> + .postenable = adxl380_buffer_postenable,
> + .predisable = adxl380_buffer_predisable,
> +};
> +
> +static int adxl380_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long info)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + switch (info) {
> + case IIO_CHAN_INFO_RAW:
> + ret = iio_device_claim_direct_mode(indio_dev);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_read_chn(st, chan->address);
> + if (ret)
> + return ret;
> +
> + iio_device_release_direct_mode(indio_dev);
> +
> + *val = sign_extend32(ret >> chan->scan_type.shift,
> + chan->scan_type.realbits - 1);
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SCALE:
> + switch (chan->type) {
> + case IIO_ACCEL:
> + scoped_guard(mutex, &st->lock) {
> + *val = st->chip_info->scale_tbl[st->range][0];
> + *val2 = st->chip_info->scale_tbl[st->range][1];
> + }
> + return IIO_VAL_INT_PLUS_NANO;
> + case IIO_TEMP:
> + /* 10.2 LSB / Degree Celsius */
> + *val = 10000;
> + *val2 = 102;
> + return IIO_VAL_FRACTIONAL;
> + default:
> + return -EINVAL;
> + }
> + case IIO_CHAN_INFO_OFFSET:
> + switch (chan->type) {
> + case IIO_TEMP:
> + *val = st->chip_info->temp_offset;
> + return IIO_VAL_INT;
> + default:
> + return -EINVAL;
> + }
> + case IIO_CHAN_INFO_CALIBBIAS:
> + switch (chan->type) {
> + case IIO_ACCEL:
> + ret = adxl380_read_calibbias_value(st, chan->scan_index, val);
> + if (ret)
> + return ret;
> + return IIO_VAL_INT;
> + default:
> + return -EINVAL;
> + }
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + ret = adxl380_get_odr(st, val);
> + if (ret)
> + return ret;
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> + ret = adxl380_get_lpf(st, val);
> + if (ret)
> + return ret;
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
> + ret = adxl380_get_hpf(st, val, val2);
> + if (ret)
> + return ret;
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int adxl380_read_avail(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + const int **vals, int *type, int *length,
> + long mask)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> +
> + if (chan->type != IIO_ACCEL)
> + return -EINVAL;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + *vals = (const int *)st->chip_info->scale_tbl;
> + *type = IIO_VAL_INT_PLUS_NANO;
> + *length = ARRAY_SIZE(st->chip_info->scale_tbl) * 2;
> + return IIO_AVAIL_LIST;
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *vals = (const int *)st->chip_info->samp_freq_tbl;
> + *type = IIO_VAL_INT;
> + *length = ARRAY_SIZE(st->chip_info->samp_freq_tbl);
> + return IIO_AVAIL_LIST;
> + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> + *vals = (const int *)st->lpf_tbl;
> + *type = IIO_VAL_INT;
> + *length = ARRAY_SIZE(st->lpf_tbl);
> + return IIO_AVAIL_LIST;
> + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
> + *vals = (const int *)st->hpf_tbl;
> + *type = IIO_VAL_INT_PLUS_MICRO;
> + /* Values are stored in a 2D matrix */
> + *length = ARRAY_SIZE(st->hpf_tbl) * 2;
> + return IIO_AVAIL_LIST;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int adxl380_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long info)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> + int odr_index, lpf_index, hpf_index, range_index;
> +
> + switch (info) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + odr_index = adxl380_find_match_1d_tbl(st->chip_info->samp_freq_tbl,
> + ARRAY_SIZE(st->chip_info->samp_freq_tbl),
> + val);
> + return adxl380_set_odr(st, odr_index);
> + case IIO_CHAN_INFO_CALIBBIAS:
> + return adxl380_write_calibbias_value(st, chan->scan_index, val);
> + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> + lpf_index = adxl380_find_match_1d_tbl(st->lpf_tbl,
> + ARRAY_SIZE(st->lpf_tbl),
> + val);
> + if (lpf_index < 0)
> + return lpf_index;
The way I see adxl380_find_match_1d_tbl(), it will never return negative.
> + return adxl380_set_lpf(st, lpf_index);
> + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
> + hpf_index = adxl380_find_match_2d_tbl(st->hpf_tbl,
> + ARRAY_SIZE(st->hpf_tbl),
> + val, val2);
> + if (hpf_index < 0)
> + return hpf_index;
> + return adxl380_set_hpf(st, hpf_index);
> + case IIO_CHAN_INFO_SCALE:
> + range_index = adxl380_find_match_2d_tbl(st->chip_info->scale_tbl,
> + ARRAY_SIZE(st->chip_info->scale_tbl),
> + val, val2);
> + if (range_index < 0)
> + return range_index;
> + return adxl380_set_range(st, range_index);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int adxl380_write_raw_get_fmt(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + long info)
> +{
> + switch (info) {
> + case IIO_CHAN_INFO_SCALE:
> + if (chan->type != IIO_ACCEL)
> + return -EINVAL;
> +
> + return IIO_VAL_INT_PLUS_NANO;
> + default:
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> +}
> +
> +static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
> + int ret;
> + bool int_en;
> + bool tap_axis_en = false;
> +
> + switch (chan->channel2) {
> + case IIO_MOD_X:
> + tap_axis_en = st->tap_axis_en == ADXL380_X_AXIS;
> + break;
> + case IIO_MOD_Y:
> + tap_axis_en = st->tap_axis_en == ADXL380_Y_AXIS;
> + break;
> + case IIO_MOD_Z:
> + tap_axis_en = st->tap_axis_en == ADXL380_Z_AXIS;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + ret = adxl380_read_act_inact_int(st, ADXL380_ACTIVITY, &int_en);
> + if (ret)
> + return ret;
> + return int_en;
> + case IIO_EV_DIR_FALLING:
> + ret = adxl380_read_act_inact_int(st, ADXL380_INACTIVITY, &int_en);
> + if (ret)
> + return ret;
> + return int_en;
> + case IIO_EV_DIR_SINGLETAP:
> + ret = adxl380_read_tap_int(st, ADXL380_SINGLE_TAP, &int_en);
> + if (ret)
> + return ret;
> + return int_en && tap_axis_en;
> + case IIO_EV_DIR_DOUBLETAP:
> + ret = adxl380_read_tap_int(st, ADXL380_DOUBLE_TAP, &int_en);
> + if (ret)
> + return ret;
> + return int_en && tap_axis_en;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
> + enum adxl380_axis axis;
> +
> + switch (chan->channel2) {
> + case IIO_MOD_X:
> + axis = ADXL380_X_AXIS;
> + break;
> + case IIO_MOD_Y:
> + axis = ADXL380_Y_AXIS;
> + break;
> + case IIO_MOD_Z:
> + axis = ADXL380_Z_AXIS;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + return adxl380_act_inact_config(st, ADXL380_ACTIVITY, state);
> + case IIO_EV_DIR_FALLING:
> + return adxl380_act_inact_config(st, ADXL380_INACTIVITY, state);
> + case IIO_EV_DIR_SINGLETAP:
> + return adxl380_tap_config(st, axis, ADXL380_SINGLE_TAP, state);
> + case IIO_EV_DIR_DOUBLETAP:
> + return adxl380_tap_config(st, axis, ADXL380_DOUBLE_TAP, state);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
> +
> + guard(mutex)(&st->lock);
> +
> + switch (type) {
> + case IIO_EV_TYPE_THRESH:
> + switch (info) {
> + case IIO_EV_INFO_VALUE: {
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + *val = st->act_threshold;
> + return IIO_VAL_INT;
> + case IIO_EV_DIR_FALLING:
> + *val = st->inact_threshold;
> + return IIO_VAL_INT;
> + default:
> + return -EINVAL;
> + }
> + }
> + case IIO_EV_INFO_PERIOD:
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + *val = st->act_time_ms;
> + *val2 = 1000;
> + return IIO_VAL_FRACTIONAL;
> + case IIO_EV_DIR_FALLING:
> + *val = st->inact_time_ms;
> + *val2 = 1000;
> + return IIO_VAL_FRACTIONAL;
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> + case IIO_EV_TYPE_GESTURE:
> + switch (info) {
> + case IIO_EV_INFO_VALUE:
> + *val = st->tap_threshold;
> + return IIO_VAL_INT;
> + 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;
> + }
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
> + u32 val_ms, val_us;
> +
> + if (chan->type != IIO_ACCEL)
> + return -EINVAL;
> +
> + switch (type) {
> + case IIO_EV_TYPE_THRESH:
> + switch (info) {
> + case IIO_EV_INFO_VALUE:
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + return adxl380_set_act_inact_threshold(indio_dev,
> + ADXL380_ACTIVITY, val);
> + case IIO_EV_DIR_FALLING:
> + return adxl380_set_act_inact_threshold(indio_dev,
> + ADXL380_INACTIVITY, val);
> + default:
> + return -EINVAL;
> + }
> + case IIO_EV_INFO_PERIOD:
> + val_ms = val * 1000 + DIV_ROUND_UP(val2, 1000);
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + return adxl380_set_act_inact_time_ms(st,
> + ADXL380_ACTIVITY, val_ms);
> + case IIO_EV_DIR_FALLING:
> + return adxl380_set_act_inact_time_ms(st,
> + ADXL380_INACTIVITY, val_ms);
> + default:
> + return -EINVAL;
> + }
> +
> + default:
> + return -EINVAL;
> + }
> + case IIO_EV_TYPE_GESTURE:
> + switch (info) {
> + case IIO_EV_INFO_VALUE:
> + return adxl380_set_tap_threshold_value(indio_dev, val);
> + case IIO_EV_INFO_RESET_TIMEOUT:
> + val_us = val * 1000000 + val2;
> + return adxl380_write_tap_time_us(st,
> + ADXL380_TAP_TIME_WINDOW,
> + val_us);
> + case IIO_EV_INFO_TAP2_MIN_DELAY:
> + val_us = val * 1000000 + val2;
> + return adxl380_write_tap_time_us(st,
> + ADXL380_TAP_TIME_LATENT,
> + val_us);
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int vals[2];
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct adxl380_state *st = iio_priv(indio_dev);
> +
> + guard(mutex)(&st->lock);
> +
> + vals[0] = st->tap_duration_us;
> + vals[1] = MICRO;
> +
> + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals);
> +}
> +
> +static ssize_t in_accel_gesture_tap_maxtomin_time_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 adxl380_state *st = iio_priv(indio_dev);
> + int ret, val_int, val_fract_us;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us);
> + if (ret)
> + return ret;
> +
> + /* maximum value is 255 * 625 us = 0.159375 seconds */
> + if (val_int || val_fract_us > 159375 || val_fract_us < 0)
> + return -EINVAL;
> +
> + ret = adxl380_write_tap_dur_us(indio_dev, val_fract_us);
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +
> +static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_maxtomin_time, 0);
> +
> +static struct attribute *adxl380_event_attributes[] = {
> + &iio_dev_attr_in_accel_gesture_tap_maxtomin_time.dev_attr.attr,
> + NULL
> +};
> +
> +static const struct attribute_group adxl380_event_attribute_group = {
> + .attrs = adxl380_event_attributes,
> +};
> +
> +static int adxl380_reg_access(struct iio_dev *indio_dev,
> + unsigned int reg,
> + unsigned int writeval,
> + unsigned int *readval)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> +
> + if (readval)
> + return regmap_read(st->regmap, reg, readval);
> +
> + return regmap_write(st->regmap, reg, writeval);
> +}
> +
> +static int adxl380_set_watermark(struct iio_dev *indio_dev, unsigned int val)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> +
> + st->watermark = min(val, ADXL380_FIFO_SAMPLES);
> +
> + return 0;
> +}
> +
> +static const struct iio_info adxl380_info = {
> + .read_raw = adxl380_read_raw,
> + .read_avail = &adxl380_read_avail,
> + .write_raw = adxl380_write_raw,
> + .write_raw_get_fmt = adxl380_write_raw_get_fmt,
> + .read_event_config = adxl380_read_event_config,
> + .write_event_config = adxl380_write_event_config,
> + .read_event_value = adxl380_read_event_value,
> + .write_event_value = adxl380_write_event_value,
> + .event_attrs = &adxl380_event_attribute_group,
> + .debugfs_reg_access = &adxl380_reg_access,
> + .hwfifo_set_watermark = adxl380_set_watermark,
> +};
> +
> +static const struct iio_event_spec adxl380_events[] = {
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_RISING,
> + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
> + BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_PERIOD),
> + },
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_FALLING,
> + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
> + BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_PERIOD),
> + },
> + {
> + .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_RESET_TIMEOUT),
> + },
> + {
> + .type = IIO_EV_TYPE_GESTURE,
> + .dir = IIO_EV_DIR_DOUBLETAP,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_RESET_TIMEOUT) |
> + BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
> + },
> +};
> +
> +#define ADXL380_ACCEL_CHANNEL(index, reg, axis) { \
> + .type = IIO_ACCEL, \
> + .address = reg, \
> + .modified = 1, \
> + .channel2 = IIO_MOD_##axis, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_CALIBBIAS), \
> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .info_mask_shared_by_all_available = \
> + BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .info_mask_shared_by_type = \
> + BIT(IIO_CHAN_INFO_SCALE) | \
> + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \
> + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
> + .info_mask_shared_by_type_available = \
> + BIT(IIO_CHAN_INFO_SCALE) | \
> + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \
> + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
> + .scan_index = index, \
> + .scan_type = { \
> + .sign = 's', \
> + .realbits = 16, \
> + .storagebits = 16, \
> + .endianness = IIO_BE, \
> + }, \
> + .event_spec = adxl380_events, \
> + .num_event_specs = ARRAY_SIZE(adxl380_events) \
> +}
> +
> +static const struct iio_chan_spec adxl380_channels[] = {
> + ADXL380_ACCEL_CHANNEL(0, ADXL380_X_DATA_H_REG, X),
> + ADXL380_ACCEL_CHANNEL(1, ADXL380_Y_DATA_H_REG, Y),
> + ADXL380_ACCEL_CHANNEL(2, ADXL380_Z_DATA_H_REG, Z),
> + {
> + .type = IIO_TEMP,
> + .address = ADXL380_T_DATA_H_REG,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_SCALE) |
> + BIT(IIO_CHAN_INFO_OFFSET),
> + .scan_index = 3,
> + .scan_type = {
> + .sign = 's',
> + .realbits = 12,
> + .storagebits = 16,
> + .shift = 4,
> + .endianness = IIO_BE,
> + },
> + },
> +};
> +
> +static int adxl380_config_irq(struct iio_dev *indio_dev)
> +{
> + struct adxl380_state *st = iio_priv(indio_dev);
> + unsigned long irq_flag;
> + struct irq_data *desc;
> + u32 irq_type;
> + u8 polarity;
> + int ret;
> +
> + desc = irq_get_irq_data(st->irq);
> + if (!desc)
> + return dev_err_probe(st->dev, -EINVAL, "Could not find IRQ %d\n", st->irq);
> +
> + irq_type = irqd_get_trigger_type(desc);
> + if (irq_type == IRQ_TYPE_LEVEL_HIGH) {
> + polarity = 0;
> + irq_flag = IRQF_TRIGGER_HIGH | IRQF_ONESHOT;
> + } else if (irq_type == IRQ_TYPE_LEVEL_LOW) {
> + polarity = 1;
> + irq_flag = IRQF_TRIGGER_LOW | IRQF_ONESHOT;
> + } else {
> + return dev_err_probe(st->dev, -EINVAL,
> + "Invalid interrupt 0x%x. Only level interrupts supported\n",
> + irq_type);
> + }
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_INT0_REG,
> + ADXL380_INT0_POL_MSK,
> + FIELD_PREP(ADXL380_INT0_POL_MSK, polarity));
> + if (ret)
> + return ret;
> +
> + return devm_request_threaded_irq(st->dev, st->irq, NULL,
> + adxl380_irq_handler, irq_flag,
> + indio_dev->name, indio_dev);
> +}
> +
> +static int adxl380_setup(struct iio_dev *indio_dev)
> +{
> + unsigned int reg_val;
> + u16 part_id, chip_id;
> + int ret, i;
> + struct adxl380_state *st = iio_priv(indio_dev);
> +
> + ret = regmap_read(st->regmap, ADXL380_DEVID_AD_REG, ®_val);
> + if (ret)
> + return ret;
> +
> + if (reg_val != ADXL380_DEVID_AD_VAL)
> + dev_warn(st->dev, "Unknown chip id %x\n", reg_val);
> +
> + ret = regmap_bulk_read(st->regmap, ADLX380_PART_ID_REG,
> + &st->transf_buf, 2);
> + if (ret)
> + return ret;
> +
> + part_id = get_unaligned_be16(st->transf_buf);
> + part_id >>= 4;
> +
> + if (part_id != ADXL380_ID_VAL)
> + dev_warn(st->dev, "Unknown part id %x\n", part_id);
> +
> + ret = regmap_read(st->regmap, ADXL380_MISC_0_REG, ®_val);
> + if (ret)
> + return ret;
> +
> + /* Bit to differentiate between ADXL380/382. */
> + if (reg_val & ADXL380_XL382_MSK)
> + chip_id = ADXL382_ID_VAL;
> + else
> + chip_id = ADXL380_ID_VAL;
> +
> + if (chip_id != st->chip_info->chip_id)
> + dev_warn(st->dev, "Unknown chip id %x\n", chip_id);
> +
> + ret = regmap_write(st->regmap, ADXL380_RESET_REG, ADXL380_RESET_CODE);
> + if (ret)
> + return ret;
> +
> + /*
> + * A latency of approximately 0.5 ms is required after soft reset.
> + * Stated in the register REG_RESET description.
> + */
> + fsleep(500);
> +
> + for (i = 0; i < indio_dev->num_channels; i++) {
> + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> + ADXL380_CHAN_EN_MSK(i),
> + 1 << (4 + i));
> + if (ret)
> + return ret;
> + }
> +
> + ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG,
> + ADXL380_FIFO_MODE_MSK,
> + FIELD_PREP(ADXL380_FIFO_MODE_MSK, ADXL380_FIFO_STREAMED));
> + if (ret)
> + return ret;
> +
> + /* Select all 3 axis for act/inact detection. */
> + ret = regmap_update_bits(st->regmap, ADXL380_SNSR_AXIS_EN_REG,
> + ADXL380_ACT_INACT_AXIS_EN_MSK,
> + FIELD_PREP(ADXL380_ACT_INACT_AXIS_EN_MSK,
> + ADXL380_ACT_INACT_AXIS_EN_MSK));
> + if (ret)
> + return ret;
> +
> + ret = adxl380_config_irq(indio_dev);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_fill_lpf_tbl(st);
> + if (ret)
> + return ret;
> +
> + ret = adxl380_fill_hpf_tbl(st);
> + if (ret)
> + return ret;
> +
> + return adxl380_set_measure_en(st, true);
> +}
> +
> +int adxl380_probe(struct device *dev, struct regmap *regmap,
> + const struct adxl380_chip_info *chip_info)
> +{
> + struct iio_dev *indio_dev;
> + struct adxl380_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + st = iio_priv(indio_dev);
> +
> + st->dev = dev;
> + st->regmap = regmap;
> + st->chip_info = chip_info;
> +
> + mutex_init(&st->lock);
> +
> + indio_dev->channels = adxl380_channels;
> + indio_dev->num_channels = ARRAY_SIZE(adxl380_channels);
> + indio_dev->name = chip_info->name;
> + indio_dev->info = &adxl380_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + ret = devm_regulator_get_enable(dev, "vddio");
> + if (ret)
> + return dev_err_probe(st->dev, ret,
> + "Failed to get vddio regulator\n");
> +
> + ret = devm_regulator_get_enable(st->dev, "vsupply");
> + if (ret)
> + return dev_err_probe(st->dev, ret,
> + "Failed to get vsupply regulator\n");
> +
> + st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT0");
> + if (st->irq > 0) {
> + st->int_map[0] = ADXL380_INT0_MAP0_REG;
> + st->int_map[1] = ADXL380_INT0_MAP1_REG;
> + } else {
> + st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
> + if (st->irq > 0)
> + return dev_err_probe(dev, -ENODEV,
> + "no interrupt name specified");
> + st->int_map[0] = ADXL380_INT1_MAP0_REG;
> + st->int_map[1] = ADXL380_INT1_MAP1_REG;
> + }
Would it make sense fo this interrupt-register setup to go into
"adxl380_config_irq()"?
> +
> + ret = adxl380_setup(indio_dev);
> + if (ret)
> + return ret;
> +
> + ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev,
> + &adxl380_buffer_ops,
> + adxl380_fifo_attributes);
> + if (ret)
> + return ret;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
> +EXPORT_SYMBOL_NS_GPL(adxl380_probe, IIO_ADXL380);
> +
> +MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
> +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/iio/accel/adxl380.h b/drivers/iio/accel/adxl380.h
> new file mode 100644
> index 000000000000..a683625d897a
> --- /dev/null
> +++ b/drivers/iio/accel/adxl380.h
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * ADXL380 3-Axis Digital Accelerometer
> + *
> + * Copyright 2024 Analog Devices Inc.
> + */
> +
> +#ifndef _ADXL380_H_
> +#define _ADXL380_H_
> +
> +struct adxl380_chip_info {
> + const char *name;
> + const int scale_tbl[3][2];
> + const int samp_freq_tbl[3];
> + const int temp_offset;
> + const u16 chip_id;
> +};
> +
> +extern const struct adxl380_chip_info adxl380_chip_info;
> +extern const struct adxl380_chip_info adxl382_chip_info;
> +
> +int adxl380_probe(struct device *dev, struct regmap *regmap,
> + const struct adxl380_chip_info *chip_info);
> +bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg);
> +
> +#endif /* _ADXL380_H_ */
> diff --git a/drivers/iio/accel/adxl380_i2c.c b/drivers/iio/accel/adxl380_i2c.c
> new file mode 100644
> index 000000000000..1dc1e77be815
> --- /dev/null
> +++ b/drivers/iio/accel/adxl380_i2c.c
> @@ -0,0 +1,64 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * ADXL380 3-Axis Digital Accelerometer I2C driver
> + *
> + * Copyright 2024 Analog Devices Inc.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +
> +#include "adxl380.h"
> +
> +static const struct regmap_config adxl380_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .readable_noinc_reg = adxl380_readable_noinc_reg,
> +};
> +
> +static int adxl380_i2c_probe(struct i2c_client *client)
> +{
> + struct regmap *regmap;
> + const struct adxl380_chip_info *chip_data;
> +
> + chip_data = i2c_get_match_data(client);
> +
> + regmap = devm_regmap_init_i2c(client, &adxl380_regmap_config);
> + if (IS_ERR(regmap))
> + return PTR_ERR(regmap);
> +
> + return adxl380_probe(&client->dev, regmap, chip_data);
> +}
> +
> +static const struct i2c_device_id adxl380_i2c_id[] = {
> + { "adxl380", (kernel_ulong_t)&adxl380_chip_info },
> + { "adxl382", (kernel_ulong_t)&adxl382_chip_info },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, adxl380_i2c_id);
> +
> +static const struct of_device_id adxl380_of_match[] = {
> + { .compatible = "adi,adxl380", .data = &adxl380_chip_info },
> + { .compatible = "adi,adxl382", .data = &adxl382_chip_info },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, adxl380_of_match);
> +
> +static struct i2c_driver adxl380_i2c_driver = {
> + .driver = {
> + .name = "adxl380_i2c",
> + .of_match_table = adxl380_of_match,
> + },
> + .probe = adxl380_i2c_probe,
> + .id_table = adxl380_i2c_id,
> +};
> +
> +module_i2c_driver(adxl380_i2c_driver);
> +
> +MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
> +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer I2C driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS(IIO_ADXL380);
> diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c
> new file mode 100644
> index 000000000000..e7b5778cb6cf
> --- /dev/null
> +++ b/drivers/iio/accel/adxl380_spi.c
> @@ -0,0 +1,66 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * ADXL380 3-Axis Digital Accelerometer SPI driver
> + *
> + * Copyright 2024 Analog Devices Inc.
> + */
> +
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/spi/spi.h>
> +
> +#include "adxl380.h"
> +
> +static const struct regmap_config adxl380_spi_regmap_config = {
> + .reg_bits = 7,
> + .pad_bits = 1,
> + .val_bits = 8,
> + .read_flag_mask = BIT(0),
> + .readable_noinc_reg = adxl380_readable_noinc_reg,
> +};
> +
> +static int adxl380_spi_probe(struct spi_device *spi)
> +{
> + const struct adxl380_chip_info *chip_data;
> + struct regmap *regmap;
> +
> + chip_data = spi_get_device_match_data(spi);
> +
> + regmap = devm_regmap_init_spi(spi, &adxl380_spi_regmap_config);
> + if (IS_ERR(regmap))
> + return PTR_ERR(regmap);
> +
> + return adxl380_probe(&spi->dev, regmap, chip_data);
> +}
> +
> +static const struct spi_device_id adxl380_spi_id[] = {
> + { "adxl380", (kernel_ulong_t)&adxl380_chip_info },
> + { "adxl382", (kernel_ulong_t)&adxl382_chip_info },
> + { }
> +};
> +MODULE_DEVICE_TABLE(spi, adxl380_spi_id);
> +
> +static const struct of_device_id adxl380_of_match[] = {
> + { .compatible = "adi,adxl380", .data = &adxl380_chip_info },
> + { .compatible = "adi,adxl382", .data = &adxl382_chip_info },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, adxl380_of_match);
> +
> +static struct spi_driver adxl380_spi_driver = {
> + .driver = {
> + .name = "adxl380_spi",
> + .of_match_table = adxl380_of_match,
> + },
> + .probe = adxl380_spi_probe,
> + .id_table = adxl380_spi_id,
> +};
> +
> +module_spi_driver(adxl380_spi_driver);
> +
> +MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
> +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer SPI driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS(IIO_ADXL380);
> --
> 2.45.2
>
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 2/3] iio: accel: add ADXL380 driver
2024-07-03 7:06 ` Alexandru Ardelean
@ 2024-07-07 16:31 ` Jonathan Cameron
2024-07-08 10:22 ` Miclaus, Antoniu
1 sibling, 0 replies; 7+ messages in thread
From: Jonathan Cameron @ 2024-07-07 16:31 UTC (permalink / raw)
To: Alexandru Ardelean
Cc: Antoniu Miclaus, Ramona Gradinariu, Lars-Peter Clausen,
Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Jun Yan, Matti Vaittinen, Mario Limonciello,
Mehdi Djait, linux-iio, devicetree, linux-kernel, linux-doc
*grumpy*
Alexandru, crop out the irrelevant parts!
I initially only spotted the comma one due to too much scrolling.
Good to have your review though.
Jonathan
> > +#define ADXL380_FIFO_SAMPLES 315UL
> > +
> > +enum adxl380_channels {
> > + ADXL380_ACCEL_X,
> > + ADXL380_ACCEL_Y,
> > + ADXL380_ACCEL_Z,
> > + ADXL380_TEMP,
> > + ADXL380_CH_NUM,
>
> nitpick: If ADXL380_CH_NUM is the number of channels, then a trailing
> comma is not needed.
> Fine to also leave it.
> > +static int adxl380_buffer_postenable(struct iio_dev *indio_dev)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + int i;
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap,
> > + st->int_map[0],
> > + ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
> > + FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 1));
> > + if (ret)
> > + return ret;
> > +
> > + for_each_clear_bit(i, indio_dev->active_scan_mask, ADXL380_CH_NUM) {
>
> Would this need to be?:
> for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength)
>
> Or, is the logic intended to go over the cleared bits here?
>
> Depending on what's needed here, this could make use of
> "iio_for_each_active_channel()" later.
>
>
> > + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> > + ADXL380_CHAN_EN_MSK(i),
> > + 0 << (4 + i));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + st->fifo_set_size = bitmap_weight(indio_dev->active_scan_mask,
> > + indio_dev->masklength);
>
> Depending on Nuno's series (and if that gets accepted first), this
> might need to use the new iio_get_masklength() wrapper.
> That's not a reason against this going in first though.
There will be a bunch of races with that, so I'm not worried if drivers
use it yet. We'll fix them all up along with the existing cases before
taking the masklength private.
I have applied Nuno's initial series and some drivers that will need
converting today :)
...
> > +static int adxl380_write_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int val, int val2, long info)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + int odr_index, lpf_index, hpf_index, range_index;
> > +
> > + switch (info) {
> > + case IIO_CHAN_INFO_SAMP_FREQ:
> > + odr_index = adxl380_find_match_1d_tbl(st->chip_info->samp_freq_tbl,
> > + ARRAY_SIZE(st->chip_info->samp_freq_tbl),
> > + val);
> > + return adxl380_set_odr(st, odr_index);
> > + case IIO_CHAN_INFO_CALIBBIAS:
> > + return adxl380_write_calibbias_value(st, chan->scan_index, val);
> > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> > + lpf_index = adxl380_find_match_1d_tbl(st->lpf_tbl,
> > + ARRAY_SIZE(st->lpf_tbl),
> > + val);
> > + if (lpf_index < 0)
> > + return lpf_index;
>
> The way I see adxl380_find_match_1d_tbl(), it will never return negative.
>
> > + return adxl380_set_lpf(st, lpf_index);
> > + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
> > + hpf_index = adxl380_find_match_2d_tbl(st->hpf_tbl,
> > + ARRAY_SIZE(st->hpf_tbl),
> > + val, val2);
> > + if (hpf_index < 0)
> > + return hpf_index;
> > + return adxl380_set_hpf(st, hpf_index);
> > + case IIO_CHAN_INFO_SCALE:
> > + range_index = adxl380_find_match_2d_tbl(st->chip_info->scale_tbl,
> > + ARRAY_SIZE(st->chip_info->scale_tbl),
> > + val, val2);
> > + if (range_index < 0)
> > + return range_index;
> > + return adxl380_set_range(st, range_index);
> > + default:
> > + return -EINVAL;
> > + }
> > +}
>
> > +int adxl380_probe(struct device *dev, struct regmap *regmap,
> > + const struct adxl380_chip_info *chip_info)
> > +{
> > + struct iio_dev *indio_dev;
> > + struct adxl380_state *st;
> > + int ret;
> > +
> > + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> > + if (!indio_dev)
> > + return -ENOMEM;
> > +
> > + st = iio_priv(indio_dev);
> > +
> > + st->dev = dev;
> > + st->regmap = regmap;
> > + st->chip_info = chip_info;
> > +
> > + mutex_init(&st->lock);
> > +
> > + indio_dev->channels = adxl380_channels;
> > + indio_dev->num_channels = ARRAY_SIZE(adxl380_channels);
> > + indio_dev->name = chip_info->name;
> > + indio_dev->info = &adxl380_info;
> > + indio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > + ret = devm_regulator_get_enable(dev, "vddio");
> > + if (ret)
> > + return dev_err_probe(st->dev, ret,
> > + "Failed to get vddio regulator\n");
> > +
> > + ret = devm_regulator_get_enable(st->dev, "vsupply");
> > + if (ret)
> > + return dev_err_probe(st->dev, ret,
> > + "Failed to get vsupply regulator\n");
> > +
> > + st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT0");
> > + if (st->irq > 0) {
> > + st->int_map[0] = ADXL380_INT0_MAP0_REG;
> > + st->int_map[1] = ADXL380_INT0_MAP1_REG;
> > + } else {
> > + st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
> > + if (st->irq > 0)
> > + return dev_err_probe(dev, -ENODEV,
> > + "no interrupt name specified");
> > + st->int_map[0] = ADXL380_INT1_MAP0_REG;
> > + st->int_map[1] = ADXL380_INT1_MAP1_REG;
> > + }
>
> Would it make sense fo this interrupt-register setup to go into
> "adxl380_config_irq()"?
>
> > +
> > + ret = adxl380_setup(indio_dev);
> > + if (ret)
> > + return ret;
> > +
> > + ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev,
> > + &adxl380_buffer_ops,
> > + adxl380_fifo_attributes);
> > + if (ret)
> > + return ret;
> > +
> > + return devm_iio_device_register(dev, indio_dev);
> > +}
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [PATCH v4 2/3] iio: accel: add ADXL380 driver
2024-07-03 7:06 ` Alexandru Ardelean
2024-07-07 16:31 ` Jonathan Cameron
@ 2024-07-08 10:22 ` Miclaus, Antoniu
2024-07-08 16:09 ` Jonathan Cameron
1 sibling, 1 reply; 7+ messages in thread
From: Miclaus, Antoniu @ 2024-07-08 10:22 UTC (permalink / raw)
To: Alexandru-Aleodor Ardelean
Cc: Gradinariu, Ramona, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Jun Yan, Matti Vaittinen, Mario Limonciello,
Mehdi Djait, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-doc@vger.kernel.org
> -----Original Message-----
> From: Alexandru Ardelean <aardelean@baylibre.com>
> Sent: Wednesday, July 3, 2024 10:06 AM
> To: Miclaus, Antoniu <Antoniu.Miclaus@analog.com>
> Cc: Gradinariu, Ramona <Ramona.Gradinariu@analog.com>; Lars-Peter
> Clausen <lars@metafoo.de>; Hennerich, Michael
> <Michael.Hennerich@analog.com>; Jonathan Cameron <jic23@kernel.org>;
> Rob Herring <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>;
> Conor Dooley <conor+dt@kernel.org>; Jonathan Corbet <corbet@lwn.net>;
> Jun Yan <jerrysteve1101@gmail.com>; Matti Vaittinen
> <mazziesaccount@gmail.com>; Mario Limonciello
> <mario.limonciello@amd.com>; Mehdi Djait <mehdi.djait.k@gmail.com>;
> linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-
> kernel@vger.kernel.org; linux-doc@vger.kernel.org
> Subject: Re: [PATCH v4 2/3] iio: accel: add ADXL380 driver
>
> [External]
>
> On Mon, Jul 1, 2024 at 11:47 AM Antoniu Miclaus
> <antoniu.miclaus@analog.com> wrote:
> >
> > The ADXL380/ADXL382 is a low noise density, low power, 3-axis
> > accelerometer with selectable measurement ranges. The ADXL380 supports
> > the +/-4 g, +/-8 g, and +/-16 g ranges, and the ADXL382 supports
> > +/-15 g, +/-30 g and +/-60 g ranges.
> > The ADXL380/ADXL382 offers industry leading noise, enabling precision
> > applications with minimal calibration. The low noise, and low power
> > ADXL380/ADXL382 enables accurate measurement in an environment with
> > high vibration, heart sounds and audio.
> >
> > In addition to its low power consumption, the ADXL380/ADXL382 has many
> > features to enable true system level performance. These include a
> > built-in micropower temperature sensor, single / double / triple tap
> > detection and a state machine to prevent a false triggering. In
> > addition, the ADXL380/ADXL382 has provisions for external control of
> > the sampling time and/or an external clock.
> >
> > Signed-off-by: Ramona Gradinariu <ramona.gradinariu@analog.com>
> > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> > ---
> > changes in v4:
> > - drop __aligned(IIO_DMA_MINALIGN); from tables and use it on fifo_buf
> > - drop brackets around odr >> 1 and odr & 1
> > - wrap long line
> > - drop comma on null terminator
> > - fix odd indentation
> > - drop extra space before >
> > - add space before } on arrays where missing
> > MAINTAINERS | 4 +
> > drivers/iio/accel/Kconfig | 27 +
> > drivers/iio/accel/Makefile | 3 +
> > drivers/iio/accel/adxl380.c | 1908
> +++++++++++++++++++++++++++++++
> > drivers/iio/accel/adxl380.h | 26 +
> > drivers/iio/accel/adxl380_i2c.c | 64 ++
> > drivers/iio/accel/adxl380_spi.c | 66 ++
> > 7 files changed, 2098 insertions(+)
> > create mode 100644 drivers/iio/accel/adxl380.c
> > create mode 100644 drivers/iio/accel/adxl380.h
> > create mode 100644 drivers/iio/accel/adxl380_i2c.c
> > create mode 100644 drivers/iio/accel/adxl380_spi.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 1425182c85e2..67583f13da51 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -624,6 +624,10 @@ M: Antoniu Miclaus
> <antoniu.miclaus@analog.com>
> > S: Supported
> > W: https://ez.analog.com/linux-software-drivers
> > F: Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml
> > +F: drivers/iio/accel/adxl380.c
> > +F: drivers/iio/accel/adxl380.h
> > +F: drivers/iio/accel/adxl380_i2c.c
> > +F: drivers/iio/accel/adxl380_spi.c
> >
> > AF8133J THREE-AXIS MAGNETOMETER DRIVER
> > M: Ondřej Jirman <megi@xff.cz>
> > diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
> > index c2da5066e9a7..6572ab447e14 100644
> > --- a/drivers/iio/accel/Kconfig
> > +++ b/drivers/iio/accel/Kconfig
> > @@ -177,6 +177,33 @@ config ADXL372_I2C
> > To compile this driver as a module, choose M here: the
> > module will be called adxl372_i2c.
> >
> > +config ADXL380
> > + tristate
> > + select IIO_BUFFER
> > + select IIO_TRIGGERED_BUFFER
> > +
> > +config ADXL380_SPI
> > + tristate "Analog Devices ADXL380 3-Axis Accelerometer SPI Driver"
> > + depends on SPI
> > + select ADXL380
> > + select REGMAP_SPI
> > + help
> > + Say yes here to add support for the Analog Devices ADXL380 triaxial
> > + acceleration sensor.
> > + To compile this driver as a module, choose M here: the
> > + module will be called adxl380_spi.
> > +
> > +config ADXL380_I2C
> > + tristate "Analog Devices ADXL380 3-Axis Accelerometer I2C Driver"
> > + depends on I2C
> > + select ADXL380
> > + select REGMAP_I2C
> > + help
> > + Say yes here to add support for the Analog Devices ADXL380 triaxial
> > + acceleration sensor.
> > + To compile this driver as a module, choose M here: the
> > + module will be called adxl380_i2c.
> > +
> > config BMA180
> > tristate "Bosch BMA023/BMA1x0/BMA250 3-Axis Accelerometer
> Driver"
> > depends on I2C && INPUT_BMA150=n
> > diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile
> > index db90532ba24a..ca8569e25aba 100644
> > --- a/drivers/iio/accel/Makefile
> > +++ b/drivers/iio/accel/Makefile
> > @@ -21,6 +21,9 @@ obj-$(CONFIG_ADXL367_SPI) += adxl367_spi.o
> > obj-$(CONFIG_ADXL372) += adxl372.o
> > obj-$(CONFIG_ADXL372_I2C) += adxl372_i2c.o
> > obj-$(CONFIG_ADXL372_SPI) += adxl372_spi.o
> > +obj-$(CONFIG_ADXL380) += adxl380.o
> > +obj-$(CONFIG_ADXL380_I2C) += adxl380_i2c.o
> > +obj-$(CONFIG_ADXL380_SPI) += adxl380_spi.o
> > obj-$(CONFIG_BMA180) += bma180.o
> > obj-$(CONFIG_BMA220) += bma220_spi.o
> > obj-$(CONFIG_BMA400) += bma400_core.o
> > diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c
> > new file mode 100644
> > index 000000000000..d37f4d85cf84
> > --- /dev/null
> > +++ b/drivers/iio/accel/adxl380.c
> > @@ -0,0 +1,1908 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * ADXL380 3-Axis Digital Accelerometer core driver
> > + *
> > + * Copyright 2024 Analog Devices Inc.
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/irq.h>
> > +#include <linux/module.h>
> > +#include <linux/property.h>
> > +#include <linux/regmap.h>
> > +#include <linux/units.h>
> > +
> > +#include <asm/unaligned.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>
> > +
> > +#include <linux/regulator/consumer.h>
> > +
> > +#include "adxl380.h"
> > +
> > +#define ADXL380_ID_VAL 380
> > +#define ADXL382_ID_VAL 382
> > +
> > +#define ADXL380_DEVID_AD_REG 0x00
> > +#define ADLX380_PART_ID_REG 0x02
> > +
> > +#define ADXL380_X_DATA_H_REG 0x15
> > +#define ADXL380_Y_DATA_H_REG 0x17
> > +#define ADXL380_Z_DATA_H_REG 0x19
> > +#define ADXL380_T_DATA_H_REG 0x1B
> > +
> > +#define ADXL380_MISC_0_REG 0x20
> > +#define ADXL380_XL382_MSK BIT(7)
> > +
> > +#define ADXL380_MISC_1_REG 0x21
> > +
> > +#define ADXL380_X_DSM_OFFSET_REG 0x4D
> > +
> > +#define ADXL380_ACT_INACT_CTL_REG 0x37
> > +#define ADXL380_INACT_EN_MSK BIT(2)
> > +#define ADXL380_ACT_EN_MSK BIT(0)
> > +
> > +#define ADXL380_SNSR_AXIS_EN_REG 0x38
> > +#define ADXL380_ACT_INACT_AXIS_EN_MSK GENMASK(2, 0)
> > +
> > +#define ADXL380_THRESH_ACT_H_REG 0x39
> > +#define ADXL380_TIME_ACT_H_REG 0x3B
> > +#define ADXL380_THRESH_INACT_H_REG 0x3E
> > +#define ADXL380_TIME_INACT_H_REG 0x40
> > +#define ADXL380_THRESH_MAX GENMASK(12, 0)
> > +#define ADXL380_TIME_MAX GENMASK(24, 0)
> > +
> > +#define ADXL380_FIFO_CONFIG_0_REG 0x30
> > +#define ADXL380_FIFO_SAMPLES_8_MSK BIT(0)
> > +#define ADXL380_FIFO_MODE_MSK GENMASK(5, 4)
> > +
> > +#define ADXL380_FIFO_DISABLED 0
> > +#define ADXL380_FIFO_NORMAL 1
> > +#define ADXL380_FIFO_STREAMED 2
> > +#define ADXL380_FIFO_TRIGGERED 3
> > +
> > +#define ADXL380_FIFO_CONFIG_1_REG 0x31
> > +#define ADXL380_FIFO_STATUS_0_REG 0x1E
> > +
> > +#define ADXL380_TAP_THRESH_REG 0x43
> > +#define ADXL380_TAP_DUR_REG 0x44
> > +#define ADXL380_TAP_LATENT_REG 0x45
> > +#define ADXL380_TAP_WINDOW_REG 0x46
> > +#define ADXL380_TAP_TIME_MAX GENMASK(7, 0)
> > +
> > +#define ADXL380_TAP_CFG_REG 0x47
> > +#define ADXL380_TAP_AXIS_MSK GENMASK(1, 0)
> > +
> > +#define ADXL380_TRIG_CFG_REG 0x49
> > +#define ADXL380_TRIG_CFG_DEC_2X_MSK BIT(7)
> > +#define ADXL380_TRIG_CFG_SINC_RATE_MSK BIT(6)
> > +
> > +#define ADXL380_FILTER_REG 0x50
> > +#define ADXL380_FILTER_EQ_FILT_MSK BIT(6)
> > +#define ADXL380_FILTER_LPF_MODE_MSK GENMASK(5, 4)
> > +#define ADXL380_FILTER_HPF_PATH_MSK BIT(3)
> > +#define ADXL380_FILTER_HPF_CORNER_MSK GENMASK(2, 0)
> > +
> > +#define ADXL380_OP_MODE_REG 0x26
> > +#define ADXL380_OP_MODE_RANGE_MSK GENMASK(7, 6)
> > +#define ADXL380_OP_MODE_MSK GENMASK(3, 0)
> > +#define ADXL380_OP_MODE_STANDBY 0
> > +#define ADXL380_OP_MODE_HEART_SOUND 1
> > +#define ADXL380_OP_MODE_ULP 2
> > +#define ADXL380_OP_MODE_VLP 3
> > +#define ADXL380_OP_MODE_LP 4
> > +#define ADXL380_OP_MODE_LP_ULP 6
> > +#define ADXL380_OP_MODE_LP_VLP 7
> > +#define ADXL380_OP_MODE_RBW 8
> > +#define ADXL380_OP_MODE_RBW_ULP 10
> > +#define ADXL380_OP_MODE_RBW_VLP 11
> > +#define ADXL380_OP_MODE_HP 12
> > +#define ADXL380_OP_MODE_HP_ULP 14
> > +#define ADXL380_OP_MODE_HP_VLP 15
> > +
> > +#define ADXL380_OP_MODE_4G_RANGE 0
> > +#define ADXL382_OP_MODE_15G_RANGE 0
> > +#define ADXL380_OP_MODE_8G_RANGE 1
> > +#define ADXL382_OP_MODE_30G_RANGE 1
> > +#define ADXL380_OP_MODE_16G_RANGE 2
> > +#define ADXL382_OP_MODE_60G_RANGE 2
> > +
> > +#define ADXL380_DIG_EN_REG 0x27
> > +#define ADXL380_CHAN_EN_MSK(chan) BIT(4 + (chan))
> > +#define ADXL380_FIFO_EN_MSK BIT(3)
> > +
> > +#define ADXL380_INT0_MAP0_REG 0x2B
> > +#define ADXL380_INT1_MAP0_REG 0x2D
> > +#define ADXL380_INT_MAP0_INACT_INT0_MSK BIT(6)
> > +#define ADXL380_INT_MAP0_ACT_INT0_MSK BIT(5)
> > +#define ADXL380_INT_MAP0_FIFO_WM_INT0_MSK BIT(3)
> > +
> > +#define ADXL380_INT0_MAP1_REG 0x2C
> > +#define ADXL380_INT1_MAP1_REG 0x2E
> > +#define ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK BIT(1)
> > +#define ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK BIT(0)
> > +
> > +#define ADXL380_INT0_REG 0x5D
> > +#define ADXL380_INT0_POL_MSK BIT(7)
> > +
> > +#define ADXL380_RESET_REG 0x2A
> > +#define ADXL380_FIFO_DATA 0x1D
> > +
> > +#define ADXL380_DEVID_AD_VAL 0xAD
> > +#define ADXL380_RESET_CODE 0x52
> > +
> > +#define ADXL380_STATUS_0_REG 0x11
> > +#define ADXL380_STATUS_0_FIFO_FULL_MSK BIT(1)
> > +#define ADXL380_STATUS_0_FIFO_WM_MSK BIT(3)
> > +
> > +#define ADXL380_STATUS_1_INACT_MSK BIT(6)
> > +#define ADXL380_STATUS_1_ACT_MSK BIT(5)
> > +#define ADXL380_STATUS_1_DOUBLE_TAP_MSK BIT(1)
> > +#define ADXL380_STATUS_1_SINGLE_TAP_MSK BIT(0)
> > +
> > +#define ADXL380_FIFO_SAMPLES 315UL
> > +
> > +enum adxl380_channels {
> > + ADXL380_ACCEL_X,
> > + ADXL380_ACCEL_Y,
> > + ADXL380_ACCEL_Z,
> > + ADXL380_TEMP,
> > + ADXL380_CH_NUM,
>
> nitpick: If ADXL380_CH_NUM is the number of channels, then a trailing
> comma is not needed.
> Fine to also leave it.
>
> > +};
> > +
> > +enum adxl380_axis {
> > + ADXL380_X_AXIS,
> > + ADXL380_Y_AXIS,
> > + ADXL380_Z_AXIS,
> > +};
> > +
> > +enum adxl380_activity_type {
> > + ADXL380_ACTIVITY,
> > + ADXL380_INACTIVITY,
> > +};
> > +
> > +enum adxl380_tap_type {
> > + ADXL380_SINGLE_TAP,
> > + ADXL380_DOUBLE_TAP,
> > +};
> > +
> > +enum adxl380_tap_time_type {
> > + ADXL380_TAP_TIME_LATENT,
> > + ADXL380_TAP_TIME_WINDOW,
> > +};
> > +
> > +static const int adxl380_range_scale_factor_tbl[] = { 1, 2, 4 };
> > +
> > +const struct adxl380_chip_info adxl380_chip_info = {
> > + .name = "adxl380",
> > + .chip_id = ADXL380_ID_VAL,
> > + .scale_tbl = {
> > + [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 },
> > + [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 },
> > + [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 },
> > + },
> > + .samp_freq_tbl = { 8000, 16000, 32000 },
> > + /*
> > + * The datasheet defines an intercept of 470 LSB at 25 degC
> > + * and a sensitivity of 10.2 LSB/C.
> > + */
> > + .temp_offset = 25 * 102 / 10 - 470,
> > +
> > +};
> > +EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, IIO_ADXL380);
> > +
> > +const struct adxl380_chip_info adxl382_chip_info = {
> > + .name = "adxl382",
> > + .chip_id = ADXL382_ID_VAL,
> > + .scale_tbl = {
> > + [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 },
> > + [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 },
> > + [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 },
> > + },
> > + .samp_freq_tbl = { 16000, 32000, 64000 },
> > + /*
> > + * The datasheet defines an intercept of 570 LSB at 25 degC
> > + * and a sensitivity of 10.2 LSB/C.
> > + */
> > + .temp_offset = 25 * 102 / 10 - 570,
> > +};
> > +EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, IIO_ADXL380);
> > +
> > +static const unsigned int adxl380_th_reg_high_addr[2] = {
> > + [ADXL380_ACTIVITY] = ADXL380_THRESH_ACT_H_REG,
> > + [ADXL380_INACTIVITY] = ADXL380_THRESH_INACT_H_REG,
> > +};
> > +
> > +static const unsigned int adxl380_time_reg_high_addr[2] = {
> > + [ADXL380_ACTIVITY] = ADXL380_TIME_ACT_H_REG,
> > + [ADXL380_INACTIVITY] = ADXL380_TIME_INACT_H_REG,
> > +};
> > +
> > +static const unsigned int adxl380_tap_time_reg[2] = {
> > + [ADXL380_TAP_TIME_LATENT] = ADXL380_TAP_LATENT_REG,
> > + [ADXL380_TAP_TIME_WINDOW] = ADXL380_TAP_WINDOW_REG,
> > +};
> > +
> > +struct adxl380_state {
> > + struct regmap *regmap;
> > + struct device *dev;
> > + const struct adxl380_chip_info *chip_info;
> > + /*
> > + * Synchronize access to members of driver state, and ensure atomicity
> > + * of consecutive regmap operations.
> > + */
> > + struct mutex lock;
> > + enum adxl380_axis tap_axis_en;
> > + u8 range;
> > + u8 odr;
> > + u8 fifo_set_size;
> > + u8 transf_buf[3];
> > + u16 watermark;
> > + u32 act_time_ms;
> > + u32 act_threshold;
> > + u32 inact_time_ms;
> > + u32 inact_threshold;
> > + u32 tap_latent_us;
> > + u32 tap_window_us;
> > + u32 tap_duration_us;
> > + u32 tap_threshold;
> > + int irq;
> > + int int_map[2];
> > + int lpf_tbl[4];
> > + int hpf_tbl[7][2];
> > +
> > + __be16 fifo_buf[ADXL380_FIFO_SAMPLES]
> __aligned(IIO_DMA_MINALIGN);
> > +};
> > +
> > +bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg)
> > +{
> > + return reg == ADXL380_FIFO_DATA;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(adxl380_readable_noinc_reg, IIO_ADXL380);
> > +
> > +static int adxl380_set_measure_en(struct adxl380_state *st, bool en)
> > +{
> > + int ret;
> > + unsigned int act_inact_ctl;
> > + u8 op_mode = ADXL380_OP_MODE_STANDBY;
> > +
> > + if (en) {
> > + ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG,
> &act_inact_ctl);
> > + if (ret)
> > + return ret;
> > +
> > + /* Activity/ Inactivity detection available only in VLP/ULP mode */
> > + if (FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) ||
> > + FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl))
> > + op_mode = ADXL380_OP_MODE_VLP;
> > + else
> > + op_mode = ADXL380_OP_MODE_HP;
> > + }
> > +
> > + return regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG,
> > + ADXL380_OP_MODE_MSK,
> > + FIELD_PREP(ADXL380_OP_MODE_MSK, op_mode));
> > +}
> > +
> > +static void adxl380_scale_act_inact_thresholds(struct adxl380_state *st,
> > + u8 old_range,
> > + u8 new_range)
> > +{
> > + st->act_threshold = mult_frac(st->act_threshold,
> > + adxl380_range_scale_factor_tbl[old_range],
> > + adxl380_range_scale_factor_tbl[new_range]);
> > + st->inact_threshold = mult_frac(st->inact_threshold,
> > + adxl380_range_scale_factor_tbl[old_range],
> > + adxl380_range_scale_factor_tbl[new_range]);
> > +}
> > +
> > +static int adxl380_write_act_inact_threshold(struct adxl380_state *st,
> > + enum adxl380_activity_type act,
> > + unsigned int th)
> > +{
> > + int ret;
> > + u8 reg = adxl380_th_reg_high_addr[act];
> > +
> > + if (th > ADXL380_THRESH_MAX)
> > + return -EINVAL;
> > +
> > + ret = regmap_write(st->regmap, reg + 1, th & GENMASK(7, 0));
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap, reg, GENMASK(2, 0), th >> 8);
> > + if (ret)
> > + return ret;
> > +
> > + if (act == ADXL380_ACTIVITY)
> > + st->act_threshold = th;
> > + else
> > + st->inact_threshold = th;
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_set_act_inact_threshold(struct iio_dev *indio_dev,
> > + enum adxl380_activity_type act,
> > + u16 th)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_write_act_inact_threshold(st, act, th);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_set_tap_threshold_value(struct iio_dev *indio_dev, u8
> th)
> > +{
> > + int ret;
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(st->regmap, ADXL380_TAP_THRESH_REG, th);
> > + if (ret)
> > + return ret;
> > +
> > + st->tap_threshold = th;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int _adxl380_write_tap_time_us(struct adxl380_state *st,
> > + enum adxl380_tap_time_type tap_time_type,
> > + u32 us)
> > +{
> > + u8 reg = adxl380_tap_time_reg[tap_time_type];
> > + unsigned int reg_val;
> > + int ret;
> > +
> > + /* scale factor for tap window is 1250us / LSB */
> > + reg_val = DIV_ROUND_CLOSEST(us, 1250);
> > + if (reg_val > ADXL380_TAP_TIME_MAX)
> > + reg_val = ADXL380_TAP_TIME_MAX;
> > +
> > + ret = regmap_write(st->regmap, reg, reg_val);
> > + if (ret)
> > + return ret;
> > +
> > + if (tap_time_type == ADXL380_TAP_TIME_WINDOW)
> > + st->tap_window_us = us;
> > + else
> > + st->tap_latent_us = us;
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_write_tap_time_us(struct adxl380_state *st,
> > + enum adxl380_tap_time_type tap_time_type, u32 us)
> > +{
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = _adxl380_write_tap_time_us(st, tap_time_type, us);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_write_tap_dur_us(struct iio_dev *indio_dev, u32 us)
> > +{
> > + int ret;
> > + unsigned int reg_val;
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > +
> > + /* 625us per code is the scale factor of TAP_DUR register */
> > + reg_val = DIV_ROUND_CLOSEST(us, 625);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(st->regmap, ADXL380_TAP_DUR_REG, reg_val);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_read_chn(struct adxl380_state *st, u8 addr)
> > +{
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = regmap_bulk_read(st->regmap, addr, &st->transf_buf, 2);
> > + if (ret)
> > + return ret;
> > +
> > + return get_unaligned_be16(st->transf_buf);
> > +}
> > +
> > +static int adxl380_get_odr(struct adxl380_state *st, int *odr)
> > +{
> > + int ret;
> > + unsigned int trig_cfg, odr_idx;
> > +
> > + ret = regmap_read(st->regmap, ADXL380_TRIG_CFG_REG, &trig_cfg);
> > + if (ret)
> > + return ret;
> > +
> > + odr_idx = (FIELD_GET(ADXL380_TRIG_CFG_SINC_RATE_MSK, trig_cfg)
> << 1) |
> > + (FIELD_GET(ADXL380_TRIG_CFG_DEC_2X_MSK, trig_cfg) & 1);
> > +
> > + *odr = st->chip_info->samp_freq_tbl[odr_idx];
> > +
> > + return 0;
> > +}
> > +
> > +static const int adxl380_lpf_div[] = {
> > + 1, 4, 8, 16,
> > +};
> > +
> > +static int adxl380_fill_lpf_tbl(struct adxl380_state *st)
> > +{
> > + int ret, i;
> > + int odr;
> > +
> > + ret = adxl380_get_odr(st, &odr);
> > + if (ret)
> > + return ret;
> > +
> > + for (i = 0; i < ARRAY_SIZE(st->lpf_tbl); i++)
> > + st->lpf_tbl[i] = DIV_ROUND_CLOSEST(odr, adxl380_lpf_div[i]);
> > +
> > + return 0;
> > +}
> > +
> > +static const int adxl380_hpf_mul[] = {
> > + 0, 247000, 62084, 15545, 3862, 954, 238,
> > +};
> > +
> > +static int adxl380_fill_hpf_tbl(struct adxl380_state *st)
> > +{
> > + int i, ret, odr_hz;
> > + u32 multiplier;
> > + u64 div, rem, odr;
> > +
> > + ret = adxl380_get_odr(st, &odr_hz);
> > + if (ret)
> > + return ret;
> > +
> > + for (i = 0; i < ARRAY_SIZE(adxl380_hpf_mul); i++) {
> > + odr = mul_u64_u32_shr(odr_hz, MEGA, 0);
> > + multiplier = adxl380_hpf_mul[i];
> > + div = div64_u64_rem(mul_u64_u32_shr(odr, multiplier, 0),
> > + TERA * 100, &rem);
> > +
> > + st->hpf_tbl[i][0] = div;
> > + st->hpf_tbl[i][1] = div_u64(rem, MEGA * 100);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_set_odr(struct adxl380_state *st, u8 odr)
> > +{
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG,
> > + ADXL380_TRIG_CFG_DEC_2X_MSK,
> > + FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, odr & 1));
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG,
> > + ADXL380_TRIG_CFG_SINC_RATE_MSK,
> > + FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, odr >>
> 1));
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_set_measure_en(st, true);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_fill_lpf_tbl(st);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_fill_hpf_tbl(st);
> > +}
> > +
> > +static int adxl380_find_match_1d_tbl(const int *array, unsigned int size,
> > + int val)
>
> I think this was copied from adxl372.
> But, I am wondering (at a later point in time), if it makes sense to
> use (or create) a common utility function for this.
> I haven't looked yet, if there is one.
>
> > +{
> > + int i;
> > +
> > + for (i = 0; i < size; i++) {
> > + if (val == array[i])
> > + return i;
> > + }
> > +
> > + return size - 1;
> > +}
> > +
> > +static int adxl380_find_match_2d_tbl(const int (*freq_tbl)[2], int n, int val,
> int val2)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < n; i++) {
> > + if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2)
> > + return i;
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int adxl380_get_lpf(struct adxl380_state *st, int *lpf)
> > +{
> > + int ret;
> > + unsigned int trig_cfg, lpf_idx;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg);
> > + if (ret)
> > + return ret;
> > +
> > + lpf_idx = FIELD_GET(ADXL380_FILTER_LPF_MODE_MSK, trig_cfg);
> > +
> > + *lpf = st->lpf_tbl[lpf_idx];
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_set_lpf(struct adxl380_state *st, u8 lpf)
> > +{
> > + int ret;
> > + u8 eq_bypass = 0;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + if (lpf)
> > + eq_bypass = 1;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
> > + ADXL380_FILTER_EQ_FILT_MSK,
> > + FIELD_PREP(ADXL380_FILTER_EQ_FILT_MSK, eq_bypass));
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
> > + ADXL380_FILTER_LPF_MODE_MSK,
> > + FIELD_PREP(ADXL380_FILTER_LPF_MODE_MSK, lpf));
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_get_hpf(struct adxl380_state *st, int *hpf_int, int
> *hpf_frac)
> > +{
> > + int ret;
> > + unsigned int trig_cfg, hpf_idx;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg);
> > + if (ret)
> > + return ret;
> > +
> > + hpf_idx = FIELD_GET(ADXL380_FILTER_HPF_CORNER_MSK, trig_cfg);
> > +
> > + *hpf_int = st->hpf_tbl[hpf_idx][0];
> > + *hpf_frac = st->hpf_tbl[hpf_idx][1];
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_set_hpf(struct adxl380_state *st, u8 hpf)
> > +{
> > + int ret;
> > + u8 hpf_path = 0;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + if (hpf)
> > + hpf_path = 1;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
> > + ADXL380_FILTER_HPF_PATH_MSK,
> > + FIELD_PREP(ADXL380_FILTER_HPF_PATH_MSK,
> hpf_path));
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
> > + ADXL380_FILTER_HPF_CORNER_MSK,
> > + FIELD_PREP(ADXL380_FILTER_HPF_CORNER_MSK, hpf));
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int _adxl380_set_act_inact_time_ms(struct adxl380_state *st,
> > + enum adxl380_activity_type act,
> > + u32 ms)
> > +{
> > + u8 reg = adxl380_time_reg_high_addr[act];
> > + unsigned int reg_val;
> > + int ret;
> > +
> > + /* 500us per code is the scale factor of TIME_ACT / TIME_INACT
> registers */
> > + reg_val = min(DIV_ROUND_CLOSEST(ms * 1000, 500),
> ADXL380_TIME_MAX);
> > +
> > + put_unaligned_be24(reg_val, &st->transf_buf[0]);
> > +
> > + ret = regmap_bulk_write(st->regmap, reg, st->transf_buf, sizeof(st-
> >transf_buf));
> > + if (ret)
> > + return ret;
> > +
> > + if (act == ADXL380_ACTIVITY)
> > + st->act_time_ms = ms;
> > + else
> > + st->inact_time_ms = ms;
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_set_act_inact_time_ms(struct adxl380_state *st,
> > + enum adxl380_activity_type act,
> > + u32 ms)
> > +{
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = _adxl380_set_act_inact_time_ms(st, act, ms);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_set_range(struct adxl380_state *st, u8 range)
> > +{
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG,
> > + ADXL380_OP_MODE_RANGE_MSK,
> > + FIELD_PREP(ADXL380_OP_MODE_RANGE_MSK, range));
> > +
> > + if (ret)
> > + return ret;
> > +
> > + adxl380_scale_act_inact_thresholds(st, st->range, range);
> > +
> > + /* Activity thresholds depend on range */
> > + ret = adxl380_write_act_inact_threshold(st, ADXL380_ACTIVITY,
> > + st->act_threshold);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_write_act_inact_threshold(st, ADXL380_INACTIVITY,
> > + st->inact_threshold);
> > + if (ret)
> > + return ret;
> > +
> > + st->range = range;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_write_act_inact_en(struct adxl380_state *st,
> > + enum adxl380_activity_type type,
> > + bool en)
> > +{
> > + if (type == ADXL380_ACTIVITY)
> > + return regmap_update_bits(st->regmap,
> ADXL380_ACT_INACT_CTL_REG,
> > + ADXL380_ACT_EN_MSK,
> > + FIELD_PREP(ADXL380_ACT_EN_MSK, en));
> > +
> > + return regmap_update_bits(st->regmap,
> ADXL380_ACT_INACT_CTL_REG,
> > + ADXL380_INACT_EN_MSK,
> > + FIELD_PREP(ADXL380_INACT_EN_MSK, en));
> > +}
> > +
> > +static int adxl380_read_act_inact_int(struct adxl380_state *st,
> > + enum adxl380_activity_type type,
> > + bool *en)
> > +{
> > + int ret;
> > + unsigned int reg_val;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = regmap_read(st->regmap, st->int_map[0], ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + if (type == ADXL380_ACTIVITY)
> > + *en = FIELD_GET(ADXL380_INT_MAP0_ACT_INT0_MSK, reg_val);
> > + else
> > + *en = FIELD_GET(ADXL380_INT_MAP0_INACT_INT0_MSK, reg_val);
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_write_act_inact_int(struct adxl380_state *st,
> > + enum adxl380_activity_type act,
> > + bool en)
> > +{
> > + if (act == ADXL380_ACTIVITY)
> > + return regmap_update_bits(st->regmap, st->int_map[0],
> > + ADXL380_INT_MAP0_ACT_INT0_MSK,
> > + FIELD_PREP(ADXL380_INT_MAP0_ACT_INT0_MSK,
> en));
> > +
> > + return regmap_update_bits(st->regmap, st->int_map[0],
> > + ADXL380_INT_MAP0_INACT_INT0_MSK,
> > + FIELD_PREP(ADXL380_INT_MAP0_INACT_INT0_MSK,
> en));
> > +}
> > +
> > +static int adxl380_act_inact_config(struct adxl380_state *st,
> > + enum adxl380_activity_type type,
> > + bool en)
> > +{
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_write_act_inact_en(st, type, en);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_write_act_inact_int(st, type, en);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_write_tap_axis(struct adxl380_state *st,
> > + enum adxl380_axis axis)
> > +{
> > + int ret;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_TAP_CFG_REG,
> > + ADXL380_TAP_AXIS_MSK,
> > + FIELD_PREP(ADXL380_TAP_AXIS_MSK, axis));
> > +
> > + if (ret)
> > + return ret;
> > +
> > + st->tap_axis_en = axis;
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_read_tap_int(struct adxl380_state *st, enum
> adxl380_tap_type type, bool *en)
> > +{
> > + int ret;
> > + unsigned int reg_val;
> > +
> > + ret = regmap_read(st->regmap, st->int_map[1], ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + if (type == ADXL380_SINGLE_TAP)
> > + *en = FIELD_GET(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK,
> reg_val);
> > + else
> > + *en = FIELD_GET(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK,
> reg_val);
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_write_tap_int(struct adxl380_state *st, enum
> adxl380_tap_type type, bool en)
> > +{
> > + if (type == ADXL380_SINGLE_TAP)
> > + return regmap_update_bits(st->regmap, st->int_map[1],
> > + ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK,
> > +
> FIELD_PREP(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, en));
> > +
> > + return regmap_update_bits(st->regmap, st->int_map[1],
> > + ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK,
> > +
> FIELD_PREP(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, en));
> > +}
> > +
> > +static int adxl380_tap_config(struct adxl380_state *st,
> > + enum adxl380_axis axis,
> > + enum adxl380_tap_type type,
> > + bool en)
> > +{
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_write_tap_axis(st, axis);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_write_tap_int(st, type, en);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_set_fifo_samples(struct adxl380_state *st)
> > +{
> > + int ret;
> > + u16 fifo_samples = st->watermark * st->fifo_set_size;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG,
> > + ADXL380_FIFO_SAMPLES_8_MSK,
> > + FIELD_PREP(ADXL380_FIFO_SAMPLES_8_MSK,
> > + (fifo_samples & BIT(8))));
> > + if (ret)
> > + return ret;
> > +
> > + return regmap_write(st->regmap, ADXL380_FIFO_CONFIG_1_REG,
> > + fifo_samples & 0xFF);
> > +}
> > +
> > +static int adxl380_get_status(struct adxl380_state *st, u8 *status0, u8
> *status1)
> > +{
> > + int ret;
> > +
> > + /* STATUS0, STATUS1 are adjacent regs */
> > + ret = regmap_bulk_read(st->regmap, ADXL380_STATUS_0_REG,
> > + &st->transf_buf, 2);
> > + if (ret)
> > + return ret;
> > +
> > + *status0 = st->transf_buf[0];
> > + *status1 = st->transf_buf[1];
> > +
> > + return 0;
> > +}
> > +
> > +static int adxl380_get_fifo_entries(struct adxl380_state *st, u16
> *fifo_entries)
> > +{
> > + int ret;
> > +
> > + ret = regmap_bulk_read(st->regmap, ADXL380_FIFO_STATUS_0_REG,
> > + &st->transf_buf, 2);
> > + if (ret)
> > + return ret;
> > +
> > + *fifo_entries = st->transf_buf[0] | ((BIT(0) & st->transf_buf[1]) << 8);
> > +
> > + return 0;
> > +}
> > +
> > +static void adxl380_push_event(struct iio_dev *indio_dev, s64 timestamp,
> > + u8 status1)
> > +{
> > + if (FIELD_GET(ADXL380_STATUS_1_ACT_MSK, status1))
> > + 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_RISING),
> > + timestamp);
> > +
> > + if (FIELD_GET(ADXL380_STATUS_1_INACT_MSK, status1))
> > + 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),
> > + timestamp);
> > + if (FIELD_GET(ADXL380_STATUS_1_SINGLE_TAP_MSK, status1))
> > + 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),
> > + timestamp);
> > +
> > + if (FIELD_GET(ADXL380_STATUS_1_DOUBLE_TAP_MSK, status1))
> > + 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_DOUBLETAP),
> > + timestamp);
> > +}
> > +
> > +static irqreturn_t adxl380_irq_handler(int irq, void *p)
> > +{
> > + struct iio_dev *indio_dev = p;
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + u8 status0, status1;
> > + u16 fifo_entries;
> > + int i;
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_get_status(st, &status0, &status1);
> > + if (ret)
> > + return IRQ_HANDLED;
> > +
> > + adxl380_push_event(indio_dev, iio_get_time_ns(indio_dev), status1);
> > +
> > + if (!FIELD_GET(ADXL380_STATUS_0_FIFO_WM_MSK, status0))
> > + return IRQ_HANDLED;
> > +
> > + ret = adxl380_get_fifo_entries(st, &fifo_entries);
> > + if (ret)
> > + return IRQ_HANDLED;
> > +
> > + for (i = 0; i < fifo_entries; i += st->fifo_set_size) {
> > + ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA,
> > + &st->fifo_buf[i],
> > + 2 * st->fifo_set_size);
> > + if (ret)
> > + return IRQ_HANDLED;
> > + iio_push_to_buffers(indio_dev, &st->fifo_buf[i]);
> > + }
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static int adxl380_write_calibbias_value(struct adxl380_state *st,
> > + unsigned long chan_addr,
> > + s8 calibbias)
> > +{
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(st->regmap, ADXL380_X_DSM_OFFSET_REG +
> chan_addr, calibbias);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_read_calibbias_value(struct adxl380_state *st,
> > + unsigned long chan_addr,
> > + int *calibbias)
> > +{
> > + int ret;
> > + unsigned int reg_val;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = regmap_read(st->regmap, ADXL380_X_DSM_OFFSET_REG +
> chan_addr, ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + *calibbias = sign_extend32(reg_val, 7);
> > +
> > + return 0;
> > +}
> > +
> > +static ssize_t hwfifo_watermark_min_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "1\n");
> > +}
> > +
> > +static ssize_t hwfifo_watermark_max_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "%lu\n", ADXL380_FIFO_SAMPLES);
> > +}
> > +
> > +static ssize_t adxl380_get_fifo_watermark(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > +
> > + return sysfs_emit(buf, "%d\n", st->watermark);
> > +}
> > +
> > +static ssize_t adxl380_get_fifo_enabled(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + int ret;
> > + unsigned int reg_val;
> > +
> > + ret = regmap_read(st->regmap, ADXL380_DIG_EN_REG, ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + return sysfs_emit(buf, "%lu\n",
> > + FIELD_GET(ADXL380_FIFO_EN_MSK, reg_val));
> > +}
> > +
> > +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_min, 0);
> > +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0);
> > +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444,
> > + adxl380_get_fifo_watermark, NULL, 0);
> > +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444,
> > + adxl380_get_fifo_enabled, NULL, 0);
> > +
> > +static const struct iio_dev_attr *adxl380_fifo_attributes[] = {
> > + &iio_dev_attr_hwfifo_watermark_min,
> > + &iio_dev_attr_hwfifo_watermark_max,
> > + &iio_dev_attr_hwfifo_watermark,
> > + &iio_dev_attr_hwfifo_enabled,
> > + NULL
> > +};
> > +
> > +static int adxl380_buffer_postenable(struct iio_dev *indio_dev)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + int i;
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap,
> > + st->int_map[0],
> > + ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
> > + FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
> 1));
> > + if (ret)
> > + return ret;
> > +
> > + for_each_clear_bit(i, indio_dev->active_scan_mask,
> ADXL380_CH_NUM) {
>
> Would this need to be?:
> for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength)
>
> Or, is the logic intended to go over the cleared bits here?
>
> Depending on what's needed here, this could make use of
> "iio_for_each_active_channel()" later.
This logic is intended. By default all channels are enabled for single raw readings
(see adxl380_setup() function).
For the buffer readings channels that are not intended to be used are disabled.
>
>
> > + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> > + ADXL380_CHAN_EN_MSK(i),
> > + 0 << (4 + i));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + st->fifo_set_size = bitmap_weight(indio_dev->active_scan_mask,
> > + indio_dev->masklength);
>
> Depending on Nuno's series (and if that gets accepted first), this
> might need to use the new iio_get_masklength() wrapper.
> That's not a reason against this going in first though.
>
> > +
> > + if ((st->watermark * st->fifo_set_size) > ADXL380_FIFO_SAMPLES)
> > + st->watermark = (ADXL380_FIFO_SAMPLES / st->fifo_set_size);
> > +
> > + ret = adxl380_set_fifo_samples(st);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> ADXL380_FIFO_EN_MSK,
> > + FIELD_PREP(ADXL380_FIFO_EN_MSK, 1));
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static int adxl380_buffer_predisable(struct iio_dev *indio_dev)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + int ret, i;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = adxl380_set_measure_en(st, false);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->regmap,
> > + st->int_map[0],
> > + ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
> > + FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
> 0));
> > + if (ret)
> > + return ret;
> > +
> > + for (i = 0; i < indio_dev->num_channels; i++) {
> > + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> > + ADXL380_CHAN_EN_MSK(i),
> > + 1 << (4 + i));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> ADXL380_FIFO_EN_MSK,
> > + FIELD_PREP(ADXL380_FIFO_EN_MSK, 0));
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +static const struct iio_buffer_setup_ops adxl380_buffer_ops = {
> > + .postenable = adxl380_buffer_postenable,
> > + .predisable = adxl380_buffer_predisable,
> > +};
> > +
> > +static int adxl380_read_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int *val, int *val2, long info)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + int ret;
> > +
> > + switch (info) {
> > + case IIO_CHAN_INFO_RAW:
> > + ret = iio_device_claim_direct_mode(indio_dev);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_read_chn(st, chan->address);
> > + if (ret)
> > + return ret;
> > +
> > + iio_device_release_direct_mode(indio_dev);
> > +
> > + *val = sign_extend32(ret >> chan->scan_type.shift,
> > + chan->scan_type.realbits - 1);
> > + return IIO_VAL_INT;
> > + case IIO_CHAN_INFO_SCALE:
> > + switch (chan->type) {
> > + case IIO_ACCEL:
> > + scoped_guard(mutex, &st->lock) {
> > + *val = st->chip_info->scale_tbl[st->range][0];
> > + *val2 = st->chip_info->scale_tbl[st->range][1];
> > + }
> > + return IIO_VAL_INT_PLUS_NANO;
> > + case IIO_TEMP:
> > + /* 10.2 LSB / Degree Celsius */
> > + *val = 10000;
> > + *val2 = 102;
> > + return IIO_VAL_FRACTIONAL;
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_CHAN_INFO_OFFSET:
> > + switch (chan->type) {
> > + case IIO_TEMP:
> > + *val = st->chip_info->temp_offset;
> > + return IIO_VAL_INT;
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_CHAN_INFO_CALIBBIAS:
> > + switch (chan->type) {
> > + case IIO_ACCEL:
> > + ret = adxl380_read_calibbias_value(st, chan->scan_index, val);
> > + if (ret)
> > + return ret;
> > + return IIO_VAL_INT;
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_CHAN_INFO_SAMP_FREQ:
> > + ret = adxl380_get_odr(st, val);
> > + if (ret)
> > + return ret;
> > + return IIO_VAL_INT;
> > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> > + ret = adxl380_get_lpf(st, val);
> > + if (ret)
> > + return ret;
> > + return IIO_VAL_INT;
> > + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
> > + ret = adxl380_get_hpf(st, val, val2);
> > + if (ret)
> > + return ret;
> > + return IIO_VAL_INT_PLUS_MICRO;
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int adxl380_read_avail(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + const int **vals, int *type, int *length,
> > + long mask)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > +
> > + if (chan->type != IIO_ACCEL)
> > + return -EINVAL;
> > +
> > + switch (mask) {
> > + case IIO_CHAN_INFO_SCALE:
> > + *vals = (const int *)st->chip_info->scale_tbl;
> > + *type = IIO_VAL_INT_PLUS_NANO;
> > + *length = ARRAY_SIZE(st->chip_info->scale_tbl) * 2;
> > + return IIO_AVAIL_LIST;
> > + case IIO_CHAN_INFO_SAMP_FREQ:
> > + *vals = (const int *)st->chip_info->samp_freq_tbl;
> > + *type = IIO_VAL_INT;
> > + *length = ARRAY_SIZE(st->chip_info->samp_freq_tbl);
> > + return IIO_AVAIL_LIST;
> > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> > + *vals = (const int *)st->lpf_tbl;
> > + *type = IIO_VAL_INT;
> > + *length = ARRAY_SIZE(st->lpf_tbl);
> > + return IIO_AVAIL_LIST;
> > + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
> > + *vals = (const int *)st->hpf_tbl;
> > + *type = IIO_VAL_INT_PLUS_MICRO;
> > + /* Values are stored in a 2D matrix */
> > + *length = ARRAY_SIZE(st->hpf_tbl) * 2;
> > + return IIO_AVAIL_LIST;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int adxl380_write_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int val, int val2, long info)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + int odr_index, lpf_index, hpf_index, range_index;
> > +
> > + switch (info) {
> > + case IIO_CHAN_INFO_SAMP_FREQ:
> > + odr_index = adxl380_find_match_1d_tbl(st->chip_info-
> >samp_freq_tbl,
> > + ARRAY_SIZE(st->chip_info->samp_freq_tbl),
> > + val);
> > + return adxl380_set_odr(st, odr_index);
> > + case IIO_CHAN_INFO_CALIBBIAS:
> > + return adxl380_write_calibbias_value(st, chan->scan_index, val);
> > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> > + lpf_index = adxl380_find_match_1d_tbl(st->lpf_tbl,
> > + ARRAY_SIZE(st->lpf_tbl),
> > + val);
> > + if (lpf_index < 0)
> > + return lpf_index;
>
> The way I see adxl380_find_match_1d_tbl(), it will never return negative.
>
> > + return adxl380_set_lpf(st, lpf_index);
> > + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
> > + hpf_index = adxl380_find_match_2d_tbl(st->hpf_tbl,
> > + ARRAY_SIZE(st->hpf_tbl),
> > + val, val2);
> > + if (hpf_index < 0)
> > + return hpf_index;
> > + return adxl380_set_hpf(st, hpf_index);
> > + case IIO_CHAN_INFO_SCALE:
> > + range_index = adxl380_find_match_2d_tbl(st->chip_info->scale_tbl,
> > + ARRAY_SIZE(st->chip_info->scale_tbl),
> > + val, val2);
> > + if (range_index < 0)
> > + return range_index;
> > + return adxl380_set_range(st, range_index);
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int adxl380_write_raw_get_fmt(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + long info)
> > +{
> > + switch (info) {
> > + case IIO_CHAN_INFO_SCALE:
> > + if (chan->type != IIO_ACCEL)
> > + return -EINVAL;
> > +
> > + return IIO_VAL_INT_PLUS_NANO;
> > + default:
> > + return IIO_VAL_INT_PLUS_MICRO;
> > + }
> > +}
> > +
> > +static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
> > + int ret;
> > + bool int_en;
> > + bool tap_axis_en = false;
> > +
> > + switch (chan->channel2) {
> > + case IIO_MOD_X:
> > + tap_axis_en = st->tap_axis_en == ADXL380_X_AXIS;
> > + break;
> > + case IIO_MOD_Y:
> > + tap_axis_en = st->tap_axis_en == ADXL380_Y_AXIS;
> > + break;
> > + case IIO_MOD_Z:
> > + tap_axis_en = st->tap_axis_en == ADXL380_Z_AXIS;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + ret = adxl380_read_act_inact_int(st, ADXL380_ACTIVITY, &int_en);
> > + if (ret)
> > + return ret;
> > + return int_en;
> > + case IIO_EV_DIR_FALLING:
> > + ret = adxl380_read_act_inact_int(st, ADXL380_INACTIVITY,
> &int_en);
> > + if (ret)
> > + return ret;
> > + return int_en;
> > + case IIO_EV_DIR_SINGLETAP:
> > + ret = adxl380_read_tap_int(st, ADXL380_SINGLE_TAP, &int_en);
> > + if (ret)
> > + return ret;
> > + return int_en && tap_axis_en;
> > + case IIO_EV_DIR_DOUBLETAP:
> > + ret = adxl380_read_tap_int(st, ADXL380_DOUBLE_TAP, &int_en);
> > + if (ret)
> > + return ret;
> > + return int_en && tap_axis_en;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
> > + enum adxl380_axis axis;
> > +
> > + switch (chan->channel2) {
> > + case IIO_MOD_X:
> > + axis = ADXL380_X_AXIS;
> > + break;
> > + case IIO_MOD_Y:
> > + axis = ADXL380_Y_AXIS;
> > + break;
> > + case IIO_MOD_Z:
> > + axis = ADXL380_Z_AXIS;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + return adxl380_act_inact_config(st, ADXL380_ACTIVITY, state);
> > + case IIO_EV_DIR_FALLING:
> > + return adxl380_act_inact_config(st, ADXL380_INACTIVITY, state);
> > + case IIO_EV_DIR_SINGLETAP:
> > + return adxl380_tap_config(st, axis, ADXL380_SINGLE_TAP, state);
> > + case IIO_EV_DIR_DOUBLETAP:
> > + return adxl380_tap_config(st, axis, ADXL380_DOUBLE_TAP, state);
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + switch (type) {
> > + case IIO_EV_TYPE_THRESH:
> > + switch (info) {
> > + case IIO_EV_INFO_VALUE: {
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + *val = st->act_threshold;
> > + return IIO_VAL_INT;
> > + case IIO_EV_DIR_FALLING:
> > + *val = st->inact_threshold;
> > + return IIO_VAL_INT;
> > + default:
> > + return -EINVAL;
> > + }
> > + }
> > + case IIO_EV_INFO_PERIOD:
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + *val = st->act_time_ms;
> > + *val2 = 1000;
> > + return IIO_VAL_FRACTIONAL;
> > + case IIO_EV_DIR_FALLING:
> > + *val = st->inact_time_ms;
> > + *val2 = 1000;
> > + return IIO_VAL_FRACTIONAL;
> > + default:
> > + return -EINVAL;
> > + }
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_EV_TYPE_GESTURE:
> > + switch (info) {
> > + case IIO_EV_INFO_VALUE:
> > + *val = st->tap_threshold;
> > + return IIO_VAL_INT;
> > + 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;
> > + }
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
> > + u32 val_ms, val_us;
> > +
> > + if (chan->type != IIO_ACCEL)
> > + return -EINVAL;
> > +
> > + switch (type) {
> > + case IIO_EV_TYPE_THRESH:
> > + switch (info) {
> > + case IIO_EV_INFO_VALUE:
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + return adxl380_set_act_inact_threshold(indio_dev,
> > + ADXL380_ACTIVITY, val);
> > + case IIO_EV_DIR_FALLING:
> > + return adxl380_set_act_inact_threshold(indio_dev,
> > + ADXL380_INACTIVITY, val);
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_EV_INFO_PERIOD:
> > + val_ms = val * 1000 + DIV_ROUND_UP(val2, 1000);
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + return adxl380_set_act_inact_time_ms(st,
> > + ADXL380_ACTIVITY, val_ms);
> > + case IIO_EV_DIR_FALLING:
> > + return adxl380_set_act_inact_time_ms(st,
> > + ADXL380_INACTIVITY, val_ms);
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_EV_TYPE_GESTURE:
> > + switch (info) {
> > + case IIO_EV_INFO_VALUE:
> > + return adxl380_set_tap_threshold_value(indio_dev, val);
> > + case IIO_EV_INFO_RESET_TIMEOUT:
> > + val_us = val * 1000000 + val2;
> > + return adxl380_write_tap_time_us(st,
> > + ADXL380_TAP_TIME_WINDOW,
> > + val_us);
> > + case IIO_EV_INFO_TAP2_MIN_DELAY:
> > + val_us = val * 1000000 + val2;
> > + return adxl380_write_tap_time_us(st,
> > + ADXL380_TAP_TIME_LATENT,
> > + val_us);
> > + default:
> > + return -EINVAL;
> > + }
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device
> *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + int vals[2];
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + vals[0] = st->tap_duration_us;
> > + vals[1] = MICRO;
> > +
> > + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals);
> > +}
> > +
> > +static ssize_t in_accel_gesture_tap_maxtomin_time_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 adxl380_state *st = iio_priv(indio_dev);
> > + int ret, val_int, val_fract_us;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us);
> > + if (ret)
> > + return ret;
> > +
> > + /* maximum value is 255 * 625 us = 0.159375 seconds */
> > + if (val_int || val_fract_us > 159375 || val_fract_us < 0)
> > + return -EINVAL;
> > +
> > + ret = adxl380_write_tap_dur_us(indio_dev, val_fract_us);
> > + if (ret)
> > + return ret;
> > +
> > + return len;
> > +}
> > +
> > +static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_maxtomin_time, 0);
> > +
> > +static struct attribute *adxl380_event_attributes[] = {
> > + &iio_dev_attr_in_accel_gesture_tap_maxtomin_time.dev_attr.attr,
> > + NULL
> > +};
> > +
> > +static const struct attribute_group adxl380_event_attribute_group = {
> > + .attrs = adxl380_event_attributes,
> > +};
> > +
> > +static int adxl380_reg_access(struct iio_dev *indio_dev,
> > + unsigned int reg,
> > + unsigned int writeval,
> > + unsigned int *readval)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > +
> > + if (readval)
> > + return regmap_read(st->regmap, reg, readval);
> > +
> > + return regmap_write(st->regmap, reg, writeval);
> > +}
> > +
> > +static int adxl380_set_watermark(struct iio_dev *indio_dev, unsigned int
> val)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > +
> > + st->watermark = min(val, ADXL380_FIFO_SAMPLES);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct iio_info adxl380_info = {
> > + .read_raw = adxl380_read_raw,
> > + .read_avail = &adxl380_read_avail,
> > + .write_raw = adxl380_write_raw,
> > + .write_raw_get_fmt = adxl380_write_raw_get_fmt,
> > + .read_event_config = adxl380_read_event_config,
> > + .write_event_config = adxl380_write_event_config,
> > + .read_event_value = adxl380_read_event_value,
> > + .write_event_value = adxl380_write_event_value,
> > + .event_attrs = &adxl380_event_attribute_group,
> > + .debugfs_reg_access = &adxl380_reg_access,
> > + .hwfifo_set_watermark = adxl380_set_watermark,
> > +};
> > +
> > +static const struct iio_event_spec adxl380_events[] = {
> > + {
> > + .type = IIO_EV_TYPE_THRESH,
> > + .dir = IIO_EV_DIR_RISING,
> > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
> > + BIT(IIO_EV_INFO_VALUE) |
> > + BIT(IIO_EV_INFO_PERIOD),
> > + },
> > + {
> > + .type = IIO_EV_TYPE_THRESH,
> > + .dir = IIO_EV_DIR_FALLING,
> > + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
> > + BIT(IIO_EV_INFO_VALUE) |
> > + BIT(IIO_EV_INFO_PERIOD),
> > + },
> > + {
> > + .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_RESET_TIMEOUT),
> > + },
> > + {
> > + .type = IIO_EV_TYPE_GESTURE,
> > + .dir = IIO_EV_DIR_DOUBLETAP,
> > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
> > + BIT(IIO_EV_INFO_RESET_TIMEOUT) |
> > + BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
> > + },
> > +};
> > +
> > +#define ADXL380_ACCEL_CHANNEL(index, reg, axis) { \
> > + .type = IIO_ACCEL, \
> > + .address = reg, \
> > + .modified = 1, \
> > + .channel2 = IIO_MOD_##axis, \
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> > + BIT(IIO_CHAN_INFO_CALIBBIAS), \
> > + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> > + .info_mask_shared_by_all_available = \
> > + BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> > + .info_mask_shared_by_type = \
> > + BIT(IIO_CHAN_INFO_SCALE) | \
> > + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \
> > + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
> > + .info_mask_shared_by_type_available = \
> > + BIT(IIO_CHAN_INFO_SCALE) | \
> > + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \
> > + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
> > + .scan_index = index, \
> > + .scan_type = { \
> > + .sign = 's', \
> > + .realbits = 16, \
> > + .storagebits = 16, \
> > + .endianness = IIO_BE, \
> > + }, \
> > + .event_spec = adxl380_events, \
> > + .num_event_specs = ARRAY_SIZE(adxl380_events) \
> > +}
> > +
> > +static const struct iio_chan_spec adxl380_channels[] = {
> > + ADXL380_ACCEL_CHANNEL(0, ADXL380_X_DATA_H_REG, X),
> > + ADXL380_ACCEL_CHANNEL(1, ADXL380_Y_DATA_H_REG, Y),
> > + ADXL380_ACCEL_CHANNEL(2, ADXL380_Z_DATA_H_REG, Z),
> > + {
> > + .type = IIO_TEMP,
> > + .address = ADXL380_T_DATA_H_REG,
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> > + BIT(IIO_CHAN_INFO_SCALE) |
> > + BIT(IIO_CHAN_INFO_OFFSET),
> > + .scan_index = 3,
> > + .scan_type = {
> > + .sign = 's',
> > + .realbits = 12,
> > + .storagebits = 16,
> > + .shift = 4,
> > + .endianness = IIO_BE,
> > + },
> > + },
> > +};
> > +
> > +static int adxl380_config_irq(struct iio_dev *indio_dev)
> > +{
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > + unsigned long irq_flag;
> > + struct irq_data *desc;
> > + u32 irq_type;
> > + u8 polarity;
> > + int ret;
> > +
> > + desc = irq_get_irq_data(st->irq);
> > + if (!desc)
> > + return dev_err_probe(st->dev, -EINVAL, "Could not find IRQ %d\n",
> st->irq);
> > +
> > + irq_type = irqd_get_trigger_type(desc);
> > + if (irq_type == IRQ_TYPE_LEVEL_HIGH) {
> > + polarity = 0;
> > + irq_flag = IRQF_TRIGGER_HIGH | IRQF_ONESHOT;
> > + } else if (irq_type == IRQ_TYPE_LEVEL_LOW) {
> > + polarity = 1;
> > + irq_flag = IRQF_TRIGGER_LOW | IRQF_ONESHOT;
> > + } else {
> > + return dev_err_probe(st->dev, -EINVAL,
> > + "Invalid interrupt 0x%x. Only level interrupts
> supported\n",
> > + irq_type);
> > + }
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_INT0_REG,
> > + ADXL380_INT0_POL_MSK,
> > + FIELD_PREP(ADXL380_INT0_POL_MSK, polarity));
> > + if (ret)
> > + return ret;
> > +
> > + return devm_request_threaded_irq(st->dev, st->irq, NULL,
> > + adxl380_irq_handler, irq_flag,
> > + indio_dev->name, indio_dev);
> > +}
> > +
> > +static int adxl380_setup(struct iio_dev *indio_dev)
> > +{
> > + unsigned int reg_val;
> > + u16 part_id, chip_id;
> > + int ret, i;
> > + struct adxl380_state *st = iio_priv(indio_dev);
> > +
> > + ret = regmap_read(st->regmap, ADXL380_DEVID_AD_REG, ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + if (reg_val != ADXL380_DEVID_AD_VAL)
> > + dev_warn(st->dev, "Unknown chip id %x\n", reg_val);
> > +
> > + ret = regmap_bulk_read(st->regmap, ADLX380_PART_ID_REG,
> > + &st->transf_buf, 2);
> > + if (ret)
> > + return ret;
> > +
> > + part_id = get_unaligned_be16(st->transf_buf);
> > + part_id >>= 4;
> > +
> > + if (part_id != ADXL380_ID_VAL)
> > + dev_warn(st->dev, "Unknown part id %x\n", part_id);
> > +
> > + ret = regmap_read(st->regmap, ADXL380_MISC_0_REG, ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + /* Bit to differentiate between ADXL380/382. */
> > + if (reg_val & ADXL380_XL382_MSK)
> > + chip_id = ADXL382_ID_VAL;
> > + else
> > + chip_id = ADXL380_ID_VAL;
> > +
> > + if (chip_id != st->chip_info->chip_id)
> > + dev_warn(st->dev, "Unknown chip id %x\n", chip_id);
> > +
> > + ret = regmap_write(st->regmap, ADXL380_RESET_REG,
> ADXL380_RESET_CODE);
> > + if (ret)
> > + return ret;
> > +
> > + /*
> > + * A latency of approximately 0.5 ms is required after soft reset.
> > + * Stated in the register REG_RESET description.
> > + */
> > + fsleep(500);
> > +
> > + for (i = 0; i < indio_dev->num_channels; i++) {
> > + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
> > + ADXL380_CHAN_EN_MSK(i),
> > + 1 << (4 + i));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG,
> > + ADXL380_FIFO_MODE_MSK,
> > + FIELD_PREP(ADXL380_FIFO_MODE_MSK,
> ADXL380_FIFO_STREAMED));
> > + if (ret)
> > + return ret;
> > +
> > + /* Select all 3 axis for act/inact detection. */
> > + ret = regmap_update_bits(st->regmap, ADXL380_SNSR_AXIS_EN_REG,
> > + ADXL380_ACT_INACT_AXIS_EN_MSK,
> > + FIELD_PREP(ADXL380_ACT_INACT_AXIS_EN_MSK,
> > + ADXL380_ACT_INACT_AXIS_EN_MSK));
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_config_irq(indio_dev);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_fill_lpf_tbl(st);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl380_fill_hpf_tbl(st);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl380_set_measure_en(st, true);
> > +}
> > +
> > +int adxl380_probe(struct device *dev, struct regmap *regmap,
> > + const struct adxl380_chip_info *chip_info)
> > +{
> > + struct iio_dev *indio_dev;
> > + struct adxl380_state *st;
> > + int ret;
> > +
> > + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> > + if (!indio_dev)
> > + return -ENOMEM;
> > +
> > + st = iio_priv(indio_dev);
> > +
> > + st->dev = dev;
> > + st->regmap = regmap;
> > + st->chip_info = chip_info;
> > +
> > + mutex_init(&st->lock);
> > +
> > + indio_dev->channels = adxl380_channels;
> > + indio_dev->num_channels = ARRAY_SIZE(adxl380_channels);
> > + indio_dev->name = chip_info->name;
> > + indio_dev->info = &adxl380_info;
> > + indio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > + ret = devm_regulator_get_enable(dev, "vddio");
> > + if (ret)
> > + return dev_err_probe(st->dev, ret,
> > + "Failed to get vddio regulator\n");
> > +
> > + ret = devm_regulator_get_enable(st->dev, "vsupply");
> > + if (ret)
> > + return dev_err_probe(st->dev, ret,
> > + "Failed to get vsupply regulator\n");
> > +
> > + st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT0");
> > + if (st->irq > 0) {
> > + st->int_map[0] = ADXL380_INT0_MAP0_REG;
> > + st->int_map[1] = ADXL380_INT0_MAP1_REG;
> > + } else {
> > + st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
> > + if (st->irq > 0)
> > + return dev_err_probe(dev, -ENODEV,
> > + "no interrupt name specified");
> > + st->int_map[0] = ADXL380_INT1_MAP0_REG;
> > + st->int_map[1] = ADXL380_INT1_MAP1_REG;
> > + }
>
> Would it make sense fo this interrupt-register setup to go into
> "adxl380_config_irq()"?
>
> > +
> > + ret = adxl380_setup(indio_dev);
> > + if (ret)
> > + return ret;
> > +
> > + ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev,
> > + &adxl380_buffer_ops,
> > + adxl380_fifo_attributes);
> > + if (ret)
> > + return ret;
> > +
> > + return devm_iio_device_register(dev, indio_dev);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(adxl380_probe, IIO_ADXL380);
> > +
> > +MODULE_AUTHOR("Ramona Gradinariu
> <ramona.gradinariu@analog.com>");
> > +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
> > +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer
> driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/iio/accel/adxl380.h b/drivers/iio/accel/adxl380.h
> > new file mode 100644
> > index 000000000000..a683625d897a
> > --- /dev/null
> > +++ b/drivers/iio/accel/adxl380.h
> > @@ -0,0 +1,26 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ */
> > +/*
> > + * ADXL380 3-Axis Digital Accelerometer
> > + *
> > + * Copyright 2024 Analog Devices Inc.
> > + */
> > +
> > +#ifndef _ADXL380_H_
> > +#define _ADXL380_H_
> > +
> > +struct adxl380_chip_info {
> > + const char *name;
> > + const int scale_tbl[3][2];
> > + const int samp_freq_tbl[3];
> > + const int temp_offset;
> > + const u16 chip_id;
> > +};
> > +
> > +extern const struct adxl380_chip_info adxl380_chip_info;
> > +extern const struct adxl380_chip_info adxl382_chip_info;
> > +
> > +int adxl380_probe(struct device *dev, struct regmap *regmap,
> > + const struct adxl380_chip_info *chip_info);
> > +bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg);
> > +
> > +#endif /* _ADXL380_H_ */
> > diff --git a/drivers/iio/accel/adxl380_i2c.c b/drivers/iio/accel/adxl380_i2c.c
> > new file mode 100644
> > index 000000000000..1dc1e77be815
> > --- /dev/null
> > +++ b/drivers/iio/accel/adxl380_i2c.c
> > @@ -0,0 +1,64 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * ADXL380 3-Axis Digital Accelerometer I2C driver
> > + *
> > + * Copyright 2024 Analog Devices Inc.
> > + */
> > +
> > +#include <linux/i2c.h>
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/module.h>
> > +#include <linux/regmap.h>
> > +
> > +#include "adxl380.h"
> > +
> > +static const struct regmap_config adxl380_regmap_config = {
> > + .reg_bits = 8,
> > + .val_bits = 8,
> > + .readable_noinc_reg = adxl380_readable_noinc_reg,
> > +};
> > +
> > +static int adxl380_i2c_probe(struct i2c_client *client)
> > +{
> > + struct regmap *regmap;
> > + const struct adxl380_chip_info *chip_data;
> > +
> > + chip_data = i2c_get_match_data(client);
> > +
> > + regmap = devm_regmap_init_i2c(client, &adxl380_regmap_config);
> > + if (IS_ERR(regmap))
> > + return PTR_ERR(regmap);
> > +
> > + return adxl380_probe(&client->dev, regmap, chip_data);
> > +}
> > +
> > +static const struct i2c_device_id adxl380_i2c_id[] = {
> > + { "adxl380", (kernel_ulong_t)&adxl380_chip_info },
> > + { "adxl382", (kernel_ulong_t)&adxl382_chip_info },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, adxl380_i2c_id);
> > +
> > +static const struct of_device_id adxl380_of_match[] = {
> > + { .compatible = "adi,adxl380", .data = &adxl380_chip_info },
> > + { .compatible = "adi,adxl382", .data = &adxl382_chip_info },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, adxl380_of_match);
> > +
> > +static struct i2c_driver adxl380_i2c_driver = {
> > + .driver = {
> > + .name = "adxl380_i2c",
> > + .of_match_table = adxl380_of_match,
> > + },
> > + .probe = adxl380_i2c_probe,
> > + .id_table = adxl380_i2c_id,
> > +};
> > +
> > +module_i2c_driver(adxl380_i2c_driver);
> > +
> > +MODULE_AUTHOR("Ramona Gradinariu
> <ramona.gradinariu@analog.com>");
> > +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
> > +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer I2C
> driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS(IIO_ADXL380);
> > diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c
> > new file mode 100644
> > index 000000000000..e7b5778cb6cf
> > --- /dev/null
> > +++ b/drivers/iio/accel/adxl380_spi.c
> > @@ -0,0 +1,66 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * ADXL380 3-Axis Digital Accelerometer SPI driver
> > + *
> > + * Copyright 2024 Analog Devices Inc.
> > + */
> > +
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/module.h>
> > +#include <linux/regmap.h>
> > +#include <linux/spi/spi.h>
> > +
> > +#include "adxl380.h"
> > +
> > +static const struct regmap_config adxl380_spi_regmap_config = {
> > + .reg_bits = 7,
> > + .pad_bits = 1,
> > + .val_bits = 8,
> > + .read_flag_mask = BIT(0),
> > + .readable_noinc_reg = adxl380_readable_noinc_reg,
> > +};
> > +
> > +static int adxl380_spi_probe(struct spi_device *spi)
> > +{
> > + const struct adxl380_chip_info *chip_data;
> > + struct regmap *regmap;
> > +
> > + chip_data = spi_get_device_match_data(spi);
> > +
> > + regmap = devm_regmap_init_spi(spi, &adxl380_spi_regmap_config);
> > + if (IS_ERR(regmap))
> > + return PTR_ERR(regmap);
> > +
> > + return adxl380_probe(&spi->dev, regmap, chip_data);
> > +}
> > +
> > +static const struct spi_device_id adxl380_spi_id[] = {
> > + { "adxl380", (kernel_ulong_t)&adxl380_chip_info },
> > + { "adxl382", (kernel_ulong_t)&adxl382_chip_info },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(spi, adxl380_spi_id);
> > +
> > +static const struct of_device_id adxl380_of_match[] = {
> > + { .compatible = "adi,adxl380", .data = &adxl380_chip_info },
> > + { .compatible = "adi,adxl382", .data = &adxl382_chip_info },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, adxl380_of_match);
> > +
> > +static struct spi_driver adxl380_spi_driver = {
> > + .driver = {
> > + .name = "adxl380_spi",
> > + .of_match_table = adxl380_of_match,
> > + },
> > + .probe = adxl380_spi_probe,
> > + .id_table = adxl380_spi_id,
> > +};
> > +
> > +module_spi_driver(adxl380_spi_driver);
> > +
> > +MODULE_AUTHOR("Ramona Gradinariu
> <ramona.gradinariu@analog.com>");
> > +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
> > +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer SPI
> driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS(IIO_ADXL380);
> > --
> > 2.45.2
> >
> >
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 2/3] iio: accel: add ADXL380 driver
2024-07-08 10:22 ` Miclaus, Antoniu
@ 2024-07-08 16:09 ` Jonathan Cameron
0 siblings, 0 replies; 7+ messages in thread
From: Jonathan Cameron @ 2024-07-08 16:09 UTC (permalink / raw)
To: Miclaus, Antoniu
Cc: Alexandru-Aleodor Ardelean, Gradinariu, Ramona,
Lars-Peter Clausen, Hennerich, Michael, Jonathan Cameron,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Jun Yan, Matti Vaittinen, Mario Limonciello, Mehdi Djait,
linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org
*Grumpier*
Crop to relevant parts of email.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2024-07-08 16:09 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-07-01 8:30 [PATCH v4 1/3] dt-bindings: iio: accel: add ADXL380 Antoniu Miclaus
2024-07-01 8:30 ` [PATCH v4 2/3] iio: accel: add ADXL380 driver Antoniu Miclaus
2024-07-03 7:06 ` Alexandru Ardelean
2024-07-07 16:31 ` Jonathan Cameron
2024-07-08 10:22 ` Miclaus, Antoniu
2024-07-08 16:09 ` Jonathan Cameron
2024-07-01 8:30 ` [PATCH v4 3/3] docs: iio: add documentation for adxl380 driver Antoniu Miclaus
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).