| // 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 <linux/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, |
| iio_get_masklength(indio_dev)); |
| |
| 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); |
| iio_device_release_direct_mode(indio_dev); |
| if (ret) |
| return ret; |
| |
| *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); |
| 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; |
| |
| st->irq = fwnode_irq_get_byname(dev_fwnode(st->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(st->dev), "INT1"); |
| if (st->irq > 0) |
| return dev_err_probe(st->dev, -ENODEV, |
| "no interrupt name specified"); |
| st->int_map[0] = ADXL380_INT1_MAP0_REG; |
| st->int_map[1] = ADXL380_INT1_MAP1_REG; |
| } |
| |
| 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"); |
| |
| 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"); |