Message ID | 20240701083138.15891-2-antoniu.miclaus@analog.com (mailing list archive) |
---|---|
State | Changes Requested |
Headers | show |
Series | [v4,1/3] dt-bindings: iio: accel: add ADXL380 | expand |
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 > >
*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); > > +}
> -----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 > > > >
*Grumpier* Crop to relevant parts of email.
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);