| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2021 Analog Devices, Inc. |
| * Author: Cosmin Tanislav <cosmin.tanislav@analog.com> |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/bitops.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/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/unaligned.h> |
| |
| #include "adxl367.h" |
| |
| #define ADXL367_REG_DEVID 0x00 |
| #define ADXL367_DEVID_AD 0xAD |
| |
| #define ADXL367_REG_STATUS 0x0B |
| #define ADXL367_STATUS_INACT_MASK BIT(5) |
| #define ADXL367_STATUS_ACT_MASK BIT(4) |
| #define ADXL367_STATUS_FIFO_FULL_MASK BIT(2) |
| |
| #define ADXL367_FIFO_ENT_H_MASK GENMASK(1, 0) |
| |
| #define ADXL367_REG_X_DATA_H 0x0E |
| #define ADXL367_REG_Y_DATA_H 0x10 |
| #define ADXL367_REG_Z_DATA_H 0x12 |
| #define ADXL367_REG_TEMP_DATA_H 0x14 |
| #define ADXL367_REG_EX_ADC_DATA_H 0x16 |
| #define ADXL367_DATA_MASK GENMASK(15, 2) |
| |
| #define ADXL367_TEMP_25C 165 |
| #define ADXL367_TEMP_PER_C 54 |
| |
| #define ADXL367_VOLTAGE_OFFSET 8192 |
| #define ADXL367_VOLTAGE_MAX_MV 1000 |
| #define ADXL367_VOLTAGE_MAX_RAW GENMASK(13, 0) |
| |
| #define ADXL367_REG_RESET 0x1F |
| #define ADXL367_RESET_CODE 0x52 |
| |
| #define ADXL367_REG_THRESH_ACT_H 0x20 |
| #define ADXL367_REG_THRESH_INACT_H 0x23 |
| #define ADXL367_THRESH_MAX GENMASK(12, 0) |
| #define ADXL367_THRESH_VAL_H_MASK GENMASK(12, 6) |
| #define ADXL367_THRESH_H_MASK GENMASK(6, 0) |
| #define ADXL367_THRESH_VAL_L_MASK GENMASK(5, 0) |
| #define ADXL367_THRESH_L_MASK GENMASK(7, 2) |
| |
| #define ADXL367_REG_TIME_ACT 0x22 |
| #define ADXL367_REG_TIME_INACT_H 0x25 |
| #define ADXL367_TIME_ACT_MAX GENMASK(7, 0) |
| #define ADXL367_TIME_INACT_MAX GENMASK(15, 0) |
| #define ADXL367_TIME_INACT_VAL_H_MASK GENMASK(15, 8) |
| #define ADXL367_TIME_INACT_H_MASK GENMASK(7, 0) |
| #define ADXL367_TIME_INACT_VAL_L_MASK GENMASK(7, 0) |
| #define ADXL367_TIME_INACT_L_MASK GENMASK(7, 0) |
| |
| #define ADXL367_REG_ACT_INACT_CTL 0x27 |
| #define ADXL367_ACT_EN_MASK GENMASK(1, 0) |
| #define ADXL367_ACT_LINKLOOP_MASK GENMASK(5, 4) |
| |
| #define ADXL367_REG_FIFO_CTL 0x28 |
| #define ADXL367_FIFO_CTL_FORMAT_MASK GENMASK(6, 3) |
| #define ADXL367_FIFO_CTL_MODE_MASK GENMASK(1, 0) |
| |
| #define ADXL367_REG_FIFO_SAMPLES 0x29 |
| #define ADXL367_FIFO_SIZE 512 |
| #define ADXL367_FIFO_MAX_WATERMARK 511 |
| |
| #define ADXL367_SAMPLES_VAL_H_MASK BIT(8) |
| #define ADXL367_SAMPLES_H_MASK BIT(2) |
| #define ADXL367_SAMPLES_VAL_L_MASK GENMASK(7, 0) |
| #define ADXL367_SAMPLES_L_MASK GENMASK(7, 0) |
| |
| #define ADXL367_REG_INT1_MAP 0x2A |
| #define ADXL367_INT_INACT_MASK BIT(5) |
| #define ADXL367_INT_ACT_MASK BIT(4) |
| #define ADXL367_INT_FIFO_WATERMARK_MASK BIT(2) |
| |
| #define ADXL367_REG_FILTER_CTL 0x2C |
| #define ADXL367_FILTER_CTL_RANGE_MASK GENMASK(7, 6) |
| #define ADXL367_2G_RANGE_1G 4095 |
| #define ADXL367_2G_RANGE_100MG 409 |
| #define ADXL367_FILTER_CTL_ODR_MASK GENMASK(2, 0) |
| |
| #define ADXL367_REG_POWER_CTL 0x2D |
| #define ADXL367_POWER_CTL_MODE_MASK GENMASK(1, 0) |
| |
| #define ADXL367_REG_ADC_CTL 0x3C |
| #define ADXL367_REG_TEMP_CTL 0x3D |
| #define ADXL367_ADC_EN_MASK BIT(0) |
| |
| enum adxl367_range { |
| ADXL367_2G_RANGE, |
| ADXL367_4G_RANGE, |
| ADXL367_8G_RANGE, |
| }; |
| |
| enum adxl367_fifo_mode { |
| ADXL367_FIFO_MODE_DISABLED = 0b00, |
| ADXL367_FIFO_MODE_STREAM = 0b10, |
| }; |
| |
| enum adxl367_fifo_format { |
| ADXL367_FIFO_FORMAT_XYZ, |
| ADXL367_FIFO_FORMAT_X, |
| ADXL367_FIFO_FORMAT_Y, |
| ADXL367_FIFO_FORMAT_Z, |
| ADXL367_FIFO_FORMAT_XYZT, |
| ADXL367_FIFO_FORMAT_XT, |
| ADXL367_FIFO_FORMAT_YT, |
| ADXL367_FIFO_FORMAT_ZT, |
| ADXL367_FIFO_FORMAT_XYZA, |
| ADXL367_FIFO_FORMAT_XA, |
| ADXL367_FIFO_FORMAT_YA, |
| ADXL367_FIFO_FORMAT_ZA, |
| }; |
| |
| enum adxl367_op_mode { |
| ADXL367_OP_STANDBY = 0b00, |
| ADXL367_OP_MEASURE = 0b10, |
| }; |
| |
| enum adxl367_act_proc_mode { |
| ADXL367_LOOPED = 0b11, |
| }; |
| |
| enum adxl367_act_en_mode { |
| ADXL367_ACT_DISABLED = 0b00, |
| ADCL367_ACT_REF_ENABLED = 0b11, |
| }; |
| |
| enum adxl367_activity_type { |
| ADXL367_ACTIVITY, |
| ADXL367_INACTIVITY, |
| }; |
| |
| enum adxl367_odr { |
| ADXL367_ODR_12P5HZ, |
| ADXL367_ODR_25HZ, |
| ADXL367_ODR_50HZ, |
| ADXL367_ODR_100HZ, |
| ADXL367_ODR_200HZ, |
| ADXL367_ODR_400HZ, |
| }; |
| |
| struct adxl367_state { |
| const struct adxl367_ops *ops; |
| void *context; |
| |
| struct device *dev; |
| struct regmap *regmap; |
| |
| /* |
| * Synchronize access to members of driver state, and ensure atomicity |
| * of consecutive regmap operations. |
| */ |
| struct mutex lock; |
| |
| enum adxl367_odr odr; |
| enum adxl367_range range; |
| |
| unsigned int act_threshold; |
| unsigned int act_time_ms; |
| unsigned int inact_threshold; |
| unsigned int inact_time_ms; |
| |
| unsigned int fifo_set_size; |
| unsigned int fifo_watermark; |
| |
| __be16 fifo_buf[ADXL367_FIFO_SIZE] __aligned(IIO_DMA_MINALIGN); |
| __be16 sample_buf; |
| u8 act_threshold_buf[2]; |
| u8 inact_time_buf[2]; |
| u8 status_buf[3]; |
| }; |
| |
| static const unsigned int adxl367_threshold_h_reg_tbl[] = { |
| [ADXL367_ACTIVITY] = ADXL367_REG_THRESH_ACT_H, |
| [ADXL367_INACTIVITY] = ADXL367_REG_THRESH_INACT_H, |
| }; |
| |
| static const unsigned int adxl367_act_en_shift_tbl[] = { |
| [ADXL367_ACTIVITY] = 0, |
| [ADXL367_INACTIVITY] = 2, |
| }; |
| |
| static const unsigned int adxl367_act_int_mask_tbl[] = { |
| [ADXL367_ACTIVITY] = ADXL367_INT_ACT_MASK, |
| [ADXL367_INACTIVITY] = ADXL367_INT_INACT_MASK, |
| }; |
| |
| static const int adxl367_samp_freq_tbl[][2] = { |
| [ADXL367_ODR_12P5HZ] = {12, 500000}, |
| [ADXL367_ODR_25HZ] = {25, 0}, |
| [ADXL367_ODR_50HZ] = {50, 0}, |
| [ADXL367_ODR_100HZ] = {100, 0}, |
| [ADXL367_ODR_200HZ] = {200, 0}, |
| [ADXL367_ODR_400HZ] = {400, 0}, |
| }; |
| |
| /* (g * 2) * 9.80665 * 1000000 / (2^14 - 1) */ |
| static const int adxl367_range_scale_tbl[][2] = { |
| [ADXL367_2G_RANGE] = {0, 2394347}, |
| [ADXL367_4G_RANGE] = {0, 4788695}, |
| [ADXL367_8G_RANGE] = {0, 9577391}, |
| }; |
| |
| static const int adxl367_range_scale_factor_tbl[] = { |
| [ADXL367_2G_RANGE] = 1, |
| [ADXL367_4G_RANGE] = 2, |
| [ADXL367_8G_RANGE] = 4, |
| }; |
| |
| enum { |
| ADXL367_X_CHANNEL_INDEX, |
| ADXL367_Y_CHANNEL_INDEX, |
| ADXL367_Z_CHANNEL_INDEX, |
| ADXL367_TEMP_CHANNEL_INDEX, |
| ADXL367_EX_ADC_CHANNEL_INDEX |
| }; |
| |
| #define ADXL367_X_CHANNEL_MASK BIT(ADXL367_X_CHANNEL_INDEX) |
| #define ADXL367_Y_CHANNEL_MASK BIT(ADXL367_Y_CHANNEL_INDEX) |
| #define ADXL367_Z_CHANNEL_MASK BIT(ADXL367_Z_CHANNEL_INDEX) |
| #define ADXL367_TEMP_CHANNEL_MASK BIT(ADXL367_TEMP_CHANNEL_INDEX) |
| #define ADXL367_EX_ADC_CHANNEL_MASK BIT(ADXL367_EX_ADC_CHANNEL_INDEX) |
| |
| static const enum adxl367_fifo_format adxl367_fifo_formats[] = { |
| ADXL367_FIFO_FORMAT_X, |
| ADXL367_FIFO_FORMAT_Y, |
| ADXL367_FIFO_FORMAT_Z, |
| ADXL367_FIFO_FORMAT_XT, |
| ADXL367_FIFO_FORMAT_YT, |
| ADXL367_FIFO_FORMAT_ZT, |
| ADXL367_FIFO_FORMAT_XA, |
| ADXL367_FIFO_FORMAT_YA, |
| ADXL367_FIFO_FORMAT_ZA, |
| ADXL367_FIFO_FORMAT_XYZ, |
| ADXL367_FIFO_FORMAT_XYZT, |
| ADXL367_FIFO_FORMAT_XYZA, |
| }; |
| |
| static const unsigned long adxl367_channel_masks[] = { |
| ADXL367_X_CHANNEL_MASK, |
| ADXL367_Y_CHANNEL_MASK, |
| ADXL367_Z_CHANNEL_MASK, |
| ADXL367_X_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK, |
| ADXL367_Y_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK, |
| ADXL367_Z_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK, |
| ADXL367_X_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK, |
| ADXL367_Y_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK, |
| ADXL367_Z_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK, |
| ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK, |
| ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK | |
| ADXL367_TEMP_CHANNEL_MASK, |
| ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK | |
| ADXL367_EX_ADC_CHANNEL_MASK, |
| 0, |
| }; |
| |
| static int adxl367_set_measure_en(struct adxl367_state *st, bool en) |
| { |
| enum adxl367_op_mode op_mode = en ? ADXL367_OP_MEASURE |
| : ADXL367_OP_STANDBY; |
| int ret; |
| |
| ret = regmap_update_bits(st->regmap, ADXL367_REG_POWER_CTL, |
| ADXL367_POWER_CTL_MODE_MASK, |
| FIELD_PREP(ADXL367_POWER_CTL_MODE_MASK, |
| op_mode)); |
| if (ret) |
| return ret; |
| |
| /* |
| * Wait for acceleration output to settle after entering |
| * measure mode. |
| */ |
| if (en) |
| msleep(100); |
| |
| return 0; |
| } |
| |
| static void adxl367_scale_act_thresholds(struct adxl367_state *st, |
| enum adxl367_range old_range, |
| enum adxl367_range new_range) |
| { |
| st->act_threshold = st->act_threshold |
| * adxl367_range_scale_factor_tbl[old_range] |
| / adxl367_range_scale_factor_tbl[new_range]; |
| st->inact_threshold = st->inact_threshold |
| * adxl367_range_scale_factor_tbl[old_range] |
| / adxl367_range_scale_factor_tbl[new_range]; |
| } |
| |
| static int _adxl367_set_act_threshold(struct adxl367_state *st, |
| enum adxl367_activity_type act, |
| unsigned int threshold) |
| { |
| u8 reg = adxl367_threshold_h_reg_tbl[act]; |
| int ret; |
| |
| if (threshold > ADXL367_THRESH_MAX) |
| return -EINVAL; |
| |
| st->act_threshold_buf[0] = FIELD_PREP(ADXL367_THRESH_H_MASK, |
| FIELD_GET(ADXL367_THRESH_VAL_H_MASK, |
| threshold)); |
| st->act_threshold_buf[1] = FIELD_PREP(ADXL367_THRESH_L_MASK, |
| FIELD_GET(ADXL367_THRESH_VAL_L_MASK, |
| threshold)); |
| |
| ret = regmap_bulk_write(st->regmap, reg, st->act_threshold_buf, |
| sizeof(st->act_threshold_buf)); |
| if (ret) |
| return ret; |
| |
| if (act == ADXL367_ACTIVITY) |
| st->act_threshold = threshold; |
| else |
| st->inact_threshold = threshold; |
| |
| return 0; |
| } |
| |
| static int adxl367_set_act_threshold(struct adxl367_state *st, |
| enum adxl367_activity_type act, |
| unsigned int threshold) |
| { |
| int ret; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = _adxl367_set_act_threshold(st, act, threshold); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_measure_en(st, true); |
| } |
| |
| static int adxl367_set_act_proc_mode(struct adxl367_state *st, |
| enum adxl367_act_proc_mode mode) |
| { |
| return regmap_update_bits(st->regmap, ADXL367_REG_ACT_INACT_CTL, |
| ADXL367_ACT_LINKLOOP_MASK, |
| FIELD_PREP(ADXL367_ACT_LINKLOOP_MASK, |
| mode)); |
| } |
| |
| static int adxl367_set_act_interrupt_en(struct adxl367_state *st, |
| enum adxl367_activity_type act, |
| bool en) |
| { |
| unsigned int mask = adxl367_act_int_mask_tbl[act]; |
| |
| return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP, |
| mask, en ? mask : 0); |
| } |
| |
| static int adxl367_get_act_interrupt_en(struct adxl367_state *st, |
| enum adxl367_activity_type act, |
| bool *en) |
| { |
| unsigned int mask = adxl367_act_int_mask_tbl[act]; |
| unsigned int val; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADXL367_REG_INT1_MAP, &val); |
| if (ret) |
| return ret; |
| |
| *en = !!(val & mask); |
| |
| return 0; |
| } |
| |
| static int adxl367_set_act_en(struct adxl367_state *st, |
| enum adxl367_activity_type act, |
| enum adxl367_act_en_mode en) |
| { |
| unsigned int ctl_shift = adxl367_act_en_shift_tbl[act]; |
| |
| return regmap_update_bits(st->regmap, ADXL367_REG_ACT_INACT_CTL, |
| ADXL367_ACT_EN_MASK << ctl_shift, |
| en << ctl_shift); |
| } |
| |
| static int adxl367_set_fifo_watermark_interrupt_en(struct adxl367_state *st, |
| bool en) |
| { |
| return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP, |
| ADXL367_INT_FIFO_WATERMARK_MASK, |
| en ? ADXL367_INT_FIFO_WATERMARK_MASK : 0); |
| } |
| |
| static int adxl367_get_fifo_mode(struct adxl367_state *st, |
| enum adxl367_fifo_mode *fifo_mode) |
| { |
| unsigned int val; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADXL367_REG_FIFO_CTL, &val); |
| if (ret) |
| return ret; |
| |
| *fifo_mode = FIELD_GET(ADXL367_FIFO_CTL_MODE_MASK, val); |
| |
| return 0; |
| } |
| |
| static int adxl367_set_fifo_mode(struct adxl367_state *st, |
| enum adxl367_fifo_mode fifo_mode) |
| { |
| return regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL, |
| ADXL367_FIFO_CTL_MODE_MASK, |
| FIELD_PREP(ADXL367_FIFO_CTL_MODE_MASK, |
| fifo_mode)); |
| } |
| |
| static int adxl367_set_fifo_format(struct adxl367_state *st, |
| enum adxl367_fifo_format fifo_format) |
| { |
| return regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL, |
| ADXL367_FIFO_CTL_FORMAT_MASK, |
| FIELD_PREP(ADXL367_FIFO_CTL_FORMAT_MASK, |
| fifo_format)); |
| } |
| |
| static int adxl367_set_fifo_watermark(struct adxl367_state *st, |
| unsigned int fifo_watermark) |
| { |
| unsigned int fifo_samples = fifo_watermark * st->fifo_set_size; |
| unsigned int fifo_samples_h, fifo_samples_l; |
| int ret; |
| |
| if (fifo_samples > ADXL367_FIFO_MAX_WATERMARK) |
| fifo_samples = ADXL367_FIFO_MAX_WATERMARK; |
| |
| fifo_samples /= st->fifo_set_size; |
| |
| fifo_samples_h = FIELD_PREP(ADXL367_SAMPLES_H_MASK, |
| FIELD_GET(ADXL367_SAMPLES_VAL_H_MASK, |
| fifo_samples)); |
| fifo_samples_l = FIELD_PREP(ADXL367_SAMPLES_L_MASK, |
| FIELD_GET(ADXL367_SAMPLES_VAL_L_MASK, |
| fifo_samples)); |
| |
| ret = regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL, |
| ADXL367_SAMPLES_H_MASK, fifo_samples_h); |
| if (ret) |
| return ret; |
| |
| ret = regmap_update_bits(st->regmap, ADXL367_REG_FIFO_SAMPLES, |
| ADXL367_SAMPLES_L_MASK, fifo_samples_l); |
| if (ret) |
| return ret; |
| |
| st->fifo_watermark = fifo_watermark; |
| |
| return 0; |
| } |
| |
| static int adxl367_set_range(struct iio_dev *indio_dev, |
| enum adxl367_range range) |
| { |
| iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| int ret; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = regmap_update_bits(st->regmap, ADXL367_REG_FILTER_CTL, |
| ADXL367_FILTER_CTL_RANGE_MASK, |
| FIELD_PREP(ADXL367_FILTER_CTL_RANGE_MASK, |
| range)); |
| if (ret) |
| return ret; |
| |
| adxl367_scale_act_thresholds(st, st->range, range); |
| |
| /* Activity thresholds depend on range */ |
| ret = _adxl367_set_act_threshold(st, ADXL367_ACTIVITY, |
| st->act_threshold); |
| if (ret) |
| return ret; |
| |
| ret = _adxl367_set_act_threshold(st, ADXL367_INACTIVITY, |
| st->inact_threshold); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_measure_en(st, true); |
| if (ret) |
| return ret; |
| |
| st->range = range; |
| |
| return 0; |
| } |
| unreachable(); |
| } |
| |
| static int adxl367_time_ms_to_samples(struct adxl367_state *st, unsigned int ms) |
| { |
| int freq_hz = adxl367_samp_freq_tbl[st->odr][0]; |
| int freq_microhz = adxl367_samp_freq_tbl[st->odr][1]; |
| /* Scale to decihertz to prevent precision loss in 12.5Hz case. */ |
| int freq_dhz = freq_hz * 10 + freq_microhz / 100000; |
| |
| return DIV_ROUND_CLOSEST(ms * freq_dhz, 10000); |
| } |
| |
| static int _adxl367_set_act_time_ms(struct adxl367_state *st, unsigned int ms) |
| { |
| unsigned int val = adxl367_time_ms_to_samples(st, ms); |
| int ret; |
| |
| if (val > ADXL367_TIME_ACT_MAX) |
| val = ADXL367_TIME_ACT_MAX; |
| |
| ret = regmap_write(st->regmap, ADXL367_REG_TIME_ACT, val); |
| if (ret) |
| return ret; |
| |
| st->act_time_ms = ms; |
| |
| return 0; |
| } |
| |
| static int _adxl367_set_inact_time_ms(struct adxl367_state *st, unsigned int ms) |
| { |
| unsigned int val = adxl367_time_ms_to_samples(st, ms); |
| int ret; |
| |
| if (val > ADXL367_TIME_INACT_MAX) |
| val = ADXL367_TIME_INACT_MAX; |
| |
| st->inact_time_buf[0] = FIELD_PREP(ADXL367_TIME_INACT_H_MASK, |
| FIELD_GET(ADXL367_TIME_INACT_VAL_H_MASK, |
| val)); |
| st->inact_time_buf[1] = FIELD_PREP(ADXL367_TIME_INACT_L_MASK, |
| FIELD_GET(ADXL367_TIME_INACT_VAL_L_MASK, |
| val)); |
| |
| ret = regmap_bulk_write(st->regmap, ADXL367_REG_TIME_INACT_H, |
| st->inact_time_buf, sizeof(st->inact_time_buf)); |
| if (ret) |
| return ret; |
| |
| st->inact_time_ms = ms; |
| |
| return 0; |
| } |
| |
| static int adxl367_set_act_time_ms(struct adxl367_state *st, |
| enum adxl367_activity_type act, |
| unsigned int ms) |
| { |
| int ret; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| if (act == ADXL367_ACTIVITY) |
| ret = _adxl367_set_act_time_ms(st, ms); |
| else |
| ret = _adxl367_set_inact_time_ms(st, ms); |
| |
| if (ret) |
| return ret; |
| |
| return adxl367_set_measure_en(st, true); |
| } |
| |
| static int _adxl367_set_odr(struct adxl367_state *st, enum adxl367_odr odr) |
| { |
| int ret; |
| |
| ret = regmap_update_bits(st->regmap, ADXL367_REG_FILTER_CTL, |
| ADXL367_FILTER_CTL_ODR_MASK, |
| FIELD_PREP(ADXL367_FILTER_CTL_ODR_MASK, |
| odr)); |
| if (ret) |
| return ret; |
| |
| /* Activity timers depend on ODR */ |
| ret = _adxl367_set_act_time_ms(st, st->act_time_ms); |
| if (ret) |
| return ret; |
| |
| ret = _adxl367_set_inact_time_ms(st, st->inact_time_ms); |
| if (ret) |
| return ret; |
| |
| st->odr = odr; |
| |
| return 0; |
| } |
| |
| static int adxl367_set_odr(struct iio_dev *indio_dev, enum adxl367_odr odr) |
| { |
| iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| int ret; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = _adxl367_set_odr(st, odr); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_measure_en(st, true); |
| } |
| unreachable(); |
| } |
| |
| static int adxl367_set_temp_adc_en(struct adxl367_state *st, unsigned int reg, |
| bool en) |
| { |
| return regmap_update_bits(st->regmap, reg, ADXL367_ADC_EN_MASK, |
| en ? ADXL367_ADC_EN_MASK : 0); |
| } |
| |
| static int adxl367_set_temp_adc_reg_en(struct adxl367_state *st, |
| unsigned int reg, bool en) |
| { |
| int ret; |
| |
| switch (reg) { |
| case ADXL367_REG_TEMP_DATA_H: |
| ret = adxl367_set_temp_adc_en(st, ADXL367_REG_TEMP_CTL, en); |
| break; |
| case ADXL367_REG_EX_ADC_DATA_H: |
| ret = adxl367_set_temp_adc_en(st, ADXL367_REG_ADC_CTL, en); |
| break; |
| default: |
| return 0; |
| } |
| |
| if (ret) |
| return ret; |
| |
| if (en) |
| msleep(100); |
| |
| return 0; |
| } |
| |
| static int adxl367_set_temp_adc_mask_en(struct adxl367_state *st, |
| const unsigned long *active_scan_mask, |
| bool en) |
| { |
| if (*active_scan_mask & ADXL367_TEMP_CHANNEL_MASK) |
| return adxl367_set_temp_adc_en(st, ADXL367_REG_TEMP_CTL, en); |
| else if (*active_scan_mask & ADXL367_EX_ADC_CHANNEL_MASK) |
| return adxl367_set_temp_adc_en(st, ADXL367_REG_ADC_CTL, en); |
| |
| return 0; |
| } |
| |
| static int adxl367_find_odr(struct adxl367_state *st, int val, int val2, |
| enum adxl367_odr *odr) |
| { |
| size_t size = ARRAY_SIZE(adxl367_samp_freq_tbl); |
| int i; |
| |
| for (i = 0; i < size; i++) |
| if (val == adxl367_samp_freq_tbl[i][0] && |
| val2 == adxl367_samp_freq_tbl[i][1]) |
| break; |
| |
| if (i == size) |
| return -EINVAL; |
| |
| *odr = i; |
| |
| return 0; |
| } |
| |
| static int adxl367_find_range(struct adxl367_state *st, int val, int val2, |
| enum adxl367_range *range) |
| { |
| size_t size = ARRAY_SIZE(adxl367_range_scale_tbl); |
| int i; |
| |
| for (i = 0; i < size; i++) |
| if (val == adxl367_range_scale_tbl[i][0] && |
| val2 == adxl367_range_scale_tbl[i][1]) |
| break; |
| |
| if (i == size) |
| return -EINVAL; |
| |
| *range = i; |
| |
| return 0; |
| } |
| |
| static int adxl367_read_sample(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| int *val) |
| { |
| iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| u16 sample; |
| int ret; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_temp_adc_reg_en(st, chan->address, true); |
| if (ret) |
| return ret; |
| |
| ret = regmap_bulk_read(st->regmap, chan->address, &st->sample_buf, |
| sizeof(st->sample_buf)); |
| if (ret) |
| return ret; |
| |
| sample = FIELD_GET(ADXL367_DATA_MASK, be16_to_cpu(st->sample_buf)); |
| *val = sign_extend32(sample, chan->scan_type.realbits - 1); |
| |
| ret = adxl367_set_temp_adc_reg_en(st, chan->address, false); |
| if (ret) |
| return ret; |
| |
| return IIO_VAL_INT; |
| } |
| unreachable(); |
| } |
| |
| static int adxl367_get_status(struct adxl367_state *st, u8 *status, |
| u16 *fifo_entries) |
| { |
| int ret; |
| |
| /* Read STATUS, FIFO_ENT_L and FIFO_ENT_H */ |
| ret = regmap_bulk_read(st->regmap, ADXL367_REG_STATUS, |
| st->status_buf, sizeof(st->status_buf)); |
| if (ret) |
| return ret; |
| |
| st->status_buf[2] &= ADXL367_FIFO_ENT_H_MASK; |
| |
| *status = st->status_buf[0]; |
| *fifo_entries = get_unaligned_le16(&st->status_buf[1]); |
| |
| return 0; |
| } |
| |
| static bool adxl367_push_event(struct iio_dev *indio_dev, u8 status) |
| { |
| unsigned int ev_dir; |
| |
| if (FIELD_GET(ADXL367_STATUS_ACT_MASK, status)) |
| ev_dir = IIO_EV_DIR_RISING; |
| else if (FIELD_GET(ADXL367_STATUS_INACT_MASK, status)) |
| ev_dir = IIO_EV_DIR_FALLING; |
| else |
| return false; |
| |
| iio_push_event(indio_dev, |
| IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, |
| IIO_EV_TYPE_THRESH, ev_dir), |
| iio_get_time_ns(indio_dev)); |
| |
| return true; |
| } |
| |
| static bool adxl367_push_fifo_data(struct iio_dev *indio_dev, u8 status, |
| u16 fifo_entries) |
| { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| int ret; |
| int i; |
| |
| if (!FIELD_GET(ADXL367_STATUS_FIFO_FULL_MASK, status)) |
| return false; |
| |
| fifo_entries -= fifo_entries % st->fifo_set_size; |
| |
| ret = st->ops->read_fifo(st->context, st->fifo_buf, fifo_entries); |
| if (ret) { |
| dev_err(st->dev, "Failed to read FIFO: %d\n", ret); |
| return true; |
| } |
| |
| for (i = 0; i < fifo_entries; i += st->fifo_set_size) |
| iio_push_to_buffers(indio_dev, &st->fifo_buf[i]); |
| |
| return true; |
| } |
| |
| static irqreturn_t adxl367_irq_handler(int irq, void *private) |
| { |
| struct iio_dev *indio_dev = private; |
| struct adxl367_state *st = iio_priv(indio_dev); |
| u16 fifo_entries; |
| bool handled; |
| u8 status; |
| int ret; |
| |
| ret = adxl367_get_status(st, &status, &fifo_entries); |
| if (ret) |
| return IRQ_NONE; |
| |
| handled = adxl367_push_event(indio_dev, status); |
| handled |= adxl367_push_fifo_data(indio_dev, status, fifo_entries); |
| |
| return handled ? IRQ_HANDLED : IRQ_NONE; |
| } |
| |
| static int adxl367_reg_access(struct iio_dev *indio_dev, |
| unsigned int reg, |
| unsigned int writeval, |
| unsigned int *readval) |
| { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| |
| if (readval) |
| return regmap_read(st->regmap, reg, readval); |
| else |
| return regmap_write(st->regmap, reg, writeval); |
| } |
| |
| static int adxl367_read_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| int *val, int *val2, long info) |
| { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| |
| switch (info) { |
| case IIO_CHAN_INFO_RAW: |
| return adxl367_read_sample(indio_dev, chan, val); |
| case IIO_CHAN_INFO_SCALE: |
| switch (chan->type) { |
| case IIO_ACCEL: { |
| guard(mutex)(&st->lock); |
| *val = adxl367_range_scale_tbl[st->range][0]; |
| *val2 = adxl367_range_scale_tbl[st->range][1]; |
| return IIO_VAL_INT_PLUS_NANO; |
| } |
| case IIO_TEMP: |
| *val = 1000; |
| *val2 = ADXL367_TEMP_PER_C; |
| return IIO_VAL_FRACTIONAL; |
| case IIO_VOLTAGE: |
| *val = ADXL367_VOLTAGE_MAX_MV; |
| *val2 = ADXL367_VOLTAGE_MAX_RAW; |
| return IIO_VAL_FRACTIONAL; |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_OFFSET: |
| switch (chan->type) { |
| case IIO_TEMP: |
| *val = 25 * ADXL367_TEMP_PER_C - ADXL367_TEMP_25C; |
| return IIO_VAL_INT; |
| case IIO_VOLTAGE: |
| *val = ADXL367_VOLTAGE_OFFSET; |
| return IIO_VAL_INT; |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_SAMP_FREQ: { |
| guard(mutex)(&st->lock); |
| *val = adxl367_samp_freq_tbl[st->odr][0]; |
| *val2 = adxl367_samp_freq_tbl[st->odr][1]; |
| return IIO_VAL_INT_PLUS_MICRO; |
| } |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int adxl367_write_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| int val, int val2, long info) |
| { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| int ret; |
| |
| switch (info) { |
| case IIO_CHAN_INFO_SAMP_FREQ: { |
| enum adxl367_odr odr; |
| |
| ret = adxl367_find_odr(st, val, val2, &odr); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_odr(indio_dev, odr); |
| } |
| case IIO_CHAN_INFO_SCALE: { |
| enum adxl367_range range; |
| |
| ret = adxl367_find_range(st, val, val2, &range); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_range(indio_dev, range); |
| } |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int adxl367_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 adxl367_read_avail(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| const int **vals, int *type, int *length, |
| long info) |
| { |
| switch (info) { |
| case IIO_CHAN_INFO_SCALE: |
| if (chan->type != IIO_ACCEL) |
| return -EINVAL; |
| |
| *vals = (int *)adxl367_range_scale_tbl; |
| *type = IIO_VAL_INT_PLUS_NANO; |
| *length = ARRAY_SIZE(adxl367_range_scale_tbl) * 2; |
| return IIO_AVAIL_LIST; |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| *vals = (int *)adxl367_samp_freq_tbl; |
| *type = IIO_VAL_INT_PLUS_MICRO; |
| *length = ARRAY_SIZE(adxl367_samp_freq_tbl) * 2; |
| return IIO_AVAIL_LIST; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int adxl367_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 adxl367_state *st = iio_priv(indio_dev); |
| |
| guard(mutex)(&st->lock); |
| 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; |
| } |
| } |
| |
| static int adxl367_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 adxl367_state *st = iio_priv(indio_dev); |
| |
| switch (info) { |
| case IIO_EV_INFO_VALUE: |
| if (val < 0) |
| return -EINVAL; |
| |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| return adxl367_set_act_threshold(st, ADXL367_ACTIVITY, val); |
| case IIO_EV_DIR_FALLING: |
| return adxl367_set_act_threshold(st, ADXL367_INACTIVITY, val); |
| default: |
| return -EINVAL; |
| } |
| case IIO_EV_INFO_PERIOD: |
| if (val < 0) |
| return -EINVAL; |
| |
| val = val * 1000 + DIV_ROUND_UP(val2, 1000); |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| return adxl367_set_act_time_ms(st, ADXL367_ACTIVITY, val); |
| case IIO_EV_DIR_FALLING: |
| return adxl367_set_act_time_ms(st, ADXL367_INACTIVITY, val); |
| default: |
| return -EINVAL; |
| } |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int adxl367_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 adxl367_state *st = iio_priv(indio_dev); |
| bool en; |
| int ret; |
| |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| ret = adxl367_get_act_interrupt_en(st, ADXL367_ACTIVITY, &en); |
| return ret ?: en; |
| case IIO_EV_DIR_FALLING: |
| ret = adxl367_get_act_interrupt_en(st, ADXL367_INACTIVITY, &en); |
| return ret ?: en; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int adxl367_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) |
| { |
| enum adxl367_activity_type act; |
| |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| act = ADXL367_ACTIVITY; |
| break; |
| case IIO_EV_DIR_FALLING: |
| act = ADXL367_INACTIVITY; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| int ret; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_act_interrupt_en(st, act, state); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_act_en(st, act, state ? ADCL367_ACT_REF_ENABLED |
| : ADXL367_ACT_DISABLED); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_measure_en(st, true); |
| } |
| unreachable(); |
| } |
| |
| static ssize_t adxl367_get_fifo_enabled(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct adxl367_state *st = iio_priv(dev_to_iio_dev(dev)); |
| enum adxl367_fifo_mode fifo_mode; |
| int ret; |
| |
| ret = adxl367_get_fifo_mode(st, &fifo_mode); |
| if (ret) |
| return ret; |
| |
| return sysfs_emit(buf, "%d\n", fifo_mode != ADXL367_FIFO_MODE_DISABLED); |
| } |
| |
| static ssize_t adxl367_get_fifo_watermark(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct adxl367_state *st = iio_priv(dev_to_iio_dev(dev)); |
| unsigned int fifo_watermark; |
| |
| guard(mutex)(&st->lock); |
| fifo_watermark = st->fifo_watermark; |
| |
| return sysfs_emit(buf, "%d\n", fifo_watermark); |
| } |
| |
| IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1"); |
| IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, |
| __stringify(ADXL367_FIFO_MAX_WATERMARK)); |
| static IIO_DEVICE_ATTR(hwfifo_watermark, 0444, |
| adxl367_get_fifo_watermark, NULL, 0); |
| static IIO_DEVICE_ATTR(hwfifo_enabled, 0444, |
| adxl367_get_fifo_enabled, NULL, 0); |
| |
| static const struct iio_dev_attr *adxl367_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 adxl367_set_watermark(struct iio_dev *indio_dev, unsigned int val) |
| { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| int ret; |
| |
| if (val > ADXL367_FIFO_MAX_WATERMARK) |
| return -EINVAL; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_fifo_watermark(st, val); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_measure_en(st, true); |
| } |
| |
| static bool adxl367_find_mask_fifo_format(const unsigned long *scan_mask, |
| enum adxl367_fifo_format *fifo_format) |
| { |
| size_t size = ARRAY_SIZE(adxl367_fifo_formats); |
| int i; |
| |
| for (i = 0; i < size; i++) |
| if (*scan_mask == adxl367_channel_masks[i]) |
| break; |
| |
| if (i == size) |
| return false; |
| |
| *fifo_format = adxl367_fifo_formats[i]; |
| |
| return true; |
| } |
| |
| static int adxl367_update_scan_mode(struct iio_dev *indio_dev, |
| const unsigned long *active_scan_mask) |
| { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| enum adxl367_fifo_format fifo_format; |
| int ret; |
| |
| if (!adxl367_find_mask_fifo_format(active_scan_mask, &fifo_format)) |
| return -EINVAL; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_fifo_format(st, fifo_format); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_measure_en(st, true); |
| if (ret) |
| return ret; |
| |
| st->fifo_set_size = bitmap_weight(active_scan_mask, |
| iio_get_masklength(indio_dev)); |
| |
| return 0; |
| } |
| |
| static int adxl367_buffer_postenable(struct iio_dev *indio_dev) |
| { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| int ret; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_temp_adc_mask_en(st, indio_dev->active_scan_mask, |
| true); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_fifo_watermark_interrupt_en(st, true); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_fifo_mode(st, ADXL367_FIFO_MODE_STREAM); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_measure_en(st, true); |
| } |
| |
| static int adxl367_buffer_predisable(struct iio_dev *indio_dev) |
| { |
| struct adxl367_state *st = iio_priv(indio_dev); |
| int ret; |
| |
| guard(mutex)(&st->lock); |
| |
| ret = adxl367_set_measure_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_fifo_mode(st, ADXL367_FIFO_MODE_DISABLED); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_fifo_watermark_interrupt_en(st, false); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_measure_en(st, true); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_temp_adc_mask_en(st, indio_dev->active_scan_mask, |
| false); |
| } |
| |
| static const struct iio_buffer_setup_ops adxl367_buffer_ops = { |
| .postenable = adxl367_buffer_postenable, |
| .predisable = adxl367_buffer_predisable, |
| }; |
| |
| static const struct iio_info adxl367_info = { |
| .read_raw = adxl367_read_raw, |
| .write_raw = adxl367_write_raw, |
| .write_raw_get_fmt = adxl367_write_raw_get_fmt, |
| .read_avail = adxl367_read_avail, |
| .read_event_config = adxl367_read_event_config, |
| .write_event_config = adxl367_write_event_config, |
| .read_event_value = adxl367_read_event_value, |
| .write_event_value = adxl367_write_event_value, |
| .debugfs_reg_access = adxl367_reg_access, |
| .hwfifo_set_watermark = adxl367_set_watermark, |
| .update_scan_mode = adxl367_update_scan_mode, |
| }; |
| |
| static const struct iio_event_spec adxl367_events[] = { |
| { |
| .type = IIO_EV_TYPE_MAG_REFERENCED, |
| .dir = IIO_EV_DIR_RISING, |
| .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | |
| BIT(IIO_EV_INFO_PERIOD) | |
| BIT(IIO_EV_INFO_VALUE), |
| }, |
| { |
| .type = IIO_EV_TYPE_MAG_REFERENCED, |
| .dir = IIO_EV_DIR_FALLING, |
| .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | |
| BIT(IIO_EV_INFO_PERIOD) | |
| BIT(IIO_EV_INFO_VALUE), |
| }, |
| }; |
| |
| #define ADXL367_ACCEL_CHANNEL(index, reg, axis) { \ |
| .type = IIO_ACCEL, \ |
| .address = (reg), \ |
| .modified = 1, \ |
| .channel2 = IIO_MOD_##axis, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ |
| .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ |
| .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ |
| .info_mask_shared_by_all_available = \ |
| BIT(IIO_CHAN_INFO_SAMP_FREQ), \ |
| .event_spec = adxl367_events, \ |
| .num_event_specs = ARRAY_SIZE(adxl367_events), \ |
| .scan_index = (index), \ |
| .scan_type = { \ |
| .sign = 's', \ |
| .realbits = 14, \ |
| .storagebits = 16, \ |
| .endianness = IIO_BE, \ |
| }, \ |
| } |
| |
| #define ADXL367_CHANNEL(index, reg, _type) { \ |
| .type = (_type), \ |
| .address = (reg), \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
| BIT(IIO_CHAN_INFO_OFFSET) | \ |
| BIT(IIO_CHAN_INFO_SCALE), \ |
| .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ |
| .scan_index = (index), \ |
| .scan_type = { \ |
| .sign = 's', \ |
| .realbits = 14, \ |
| .storagebits = 16, \ |
| .endianness = IIO_BE, \ |
| }, \ |
| } |
| |
| static const struct iio_chan_spec adxl367_channels[] = { |
| ADXL367_ACCEL_CHANNEL(ADXL367_X_CHANNEL_INDEX, ADXL367_REG_X_DATA_H, X), |
| ADXL367_ACCEL_CHANNEL(ADXL367_Y_CHANNEL_INDEX, ADXL367_REG_Y_DATA_H, Y), |
| ADXL367_ACCEL_CHANNEL(ADXL367_Z_CHANNEL_INDEX, ADXL367_REG_Z_DATA_H, Z), |
| ADXL367_CHANNEL(ADXL367_TEMP_CHANNEL_INDEX, ADXL367_REG_TEMP_DATA_H, |
| IIO_TEMP), |
| ADXL367_CHANNEL(ADXL367_EX_ADC_CHANNEL_INDEX, ADXL367_REG_EX_ADC_DATA_H, |
| IIO_VOLTAGE), |
| }; |
| |
| static int adxl367_verify_devid(struct adxl367_state *st) |
| { |
| unsigned int val; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADXL367_REG_DEVID, &val); |
| if (ret) |
| return dev_err_probe(st->dev, ret, "Failed to read dev id\n"); |
| |
| if (val != ADXL367_DEVID_AD) |
| return dev_err_probe(st->dev, -ENODEV, |
| "Invalid dev id 0x%02X, expected 0x%02X\n", |
| val, ADXL367_DEVID_AD); |
| |
| return 0; |
| } |
| |
| static int adxl367_setup(struct adxl367_state *st) |
| { |
| int ret; |
| |
| ret = _adxl367_set_act_threshold(st, ADXL367_ACTIVITY, |
| ADXL367_2G_RANGE_1G); |
| if (ret) |
| return ret; |
| |
| ret = _adxl367_set_act_threshold(st, ADXL367_INACTIVITY, |
| ADXL367_2G_RANGE_100MG); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_set_act_proc_mode(st, ADXL367_LOOPED); |
| if (ret) |
| return ret; |
| |
| ret = _adxl367_set_odr(st, ADXL367_ODR_400HZ); |
| if (ret) |
| return ret; |
| |
| ret = _adxl367_set_act_time_ms(st, 10); |
| if (ret) |
| return ret; |
| |
| ret = _adxl367_set_inact_time_ms(st, 10000); |
| if (ret) |
| return ret; |
| |
| return adxl367_set_measure_en(st, true); |
| } |
| |
| int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, |
| void *context, struct regmap *regmap, int irq) |
| { |
| static const char * const regulator_names[] = { "vdd", "vddio" }; |
| struct iio_dev *indio_dev; |
| struct adxl367_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->context = context; |
| st->ops = ops; |
| |
| mutex_init(&st->lock); |
| |
| indio_dev->channels = adxl367_channels; |
| indio_dev->num_channels = ARRAY_SIZE(adxl367_channels); |
| indio_dev->available_scan_masks = adxl367_channel_masks; |
| indio_dev->name = "adxl367"; |
| indio_dev->info = &adxl367_info; |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| |
| ret = devm_regulator_bulk_get_enable(st->dev, |
| ARRAY_SIZE(regulator_names), |
| regulator_names); |
| if (ret) |
| return dev_err_probe(st->dev, ret, |
| "Failed to get regulators\n"); |
| |
| ret = regmap_write(st->regmap, ADXL367_REG_RESET, ADXL367_RESET_CODE); |
| if (ret) |
| return ret; |
| |
| fsleep(15000); |
| |
| ret = adxl367_verify_devid(st); |
| if (ret) |
| return ret; |
| |
| ret = adxl367_setup(st); |
| if (ret) |
| return ret; |
| |
| ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev, |
| &adxl367_buffer_ops, |
| adxl367_fifo_attributes); |
| if (ret) |
| return ret; |
| |
| ret = devm_request_threaded_irq(st->dev, irq, NULL, |
| adxl367_irq_handler, IRQF_ONESHOT, |
| indio_dev->name, indio_dev); |
| if (ret) |
| return dev_err_probe(st->dev, ret, "Failed to request irq\n"); |
| |
| return devm_iio_device_register(dev, indio_dev); |
| } |
| EXPORT_SYMBOL_NS_GPL(adxl367_probe, IIO_ADXL367); |
| |
| MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>"); |
| MODULE_DESCRIPTION("Analog Devices ADXL367 3-axis accelerometer driver"); |
| MODULE_LICENSE("GPL"); |