| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * IIO core driver for Bosch BMI323 6-Axis IMU. |
| * |
| * Copyright (C) 2023, Jagath Jog J <jagathjog1996@gmail.com> |
| * |
| * Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi323-ds000.pdf |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/cleanup.h> |
| #include <linux/device.h> |
| #include <linux/interrupt.h> |
| #include <linux/minmax.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/property.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.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/sysfs.h> |
| #include <linux/iio/trigger.h> |
| #include <linux/iio/trigger_consumer.h> |
| #include <linux/iio/triggered_buffer.h> |
| |
| #include "bmi323.h" |
| |
| enum bmi323_sensor_type { |
| BMI323_ACCEL, |
| BMI323_GYRO, |
| BMI323_SENSORS_CNT, |
| }; |
| |
| enum bmi323_opr_mode { |
| ACC_GYRO_MODE_DISABLE = 0x00, |
| GYRO_DRIVE_MODE_ENABLED = 0x01, |
| ACC_GYRO_MODE_DUTYCYCLE = 0x03, |
| ACC_GYRO_MODE_CONTINOUS = 0x04, |
| ACC_GYRO_MODE_HIGH_PERF = 0x07, |
| }; |
| |
| enum bmi323_state { |
| BMI323_IDLE, |
| BMI323_BUFFER_DRDY_TRIGGERED, |
| BMI323_BUFFER_FIFO, |
| }; |
| |
| enum bmi323_irq_pin { |
| BMI323_IRQ_DISABLED, |
| BMI323_IRQ_INT1, |
| BMI323_IRQ_INT2, |
| }; |
| |
| enum bmi323_3db_bw { |
| BMI323_BW_ODR_BY_2, |
| BMI323_BW_ODR_BY_4, |
| }; |
| |
| enum bmi323_scan { |
| BMI323_ACCEL_X, |
| BMI323_ACCEL_Y, |
| BMI323_ACCEL_Z, |
| BMI323_GYRO_X, |
| BMI323_GYRO_Y, |
| BMI323_GYRO_Z, |
| BMI323_CHAN_MAX |
| }; |
| |
| struct bmi323_hw { |
| u8 data; |
| u8 config; |
| const int (*scale_table)[2]; |
| int scale_table_len; |
| }; |
| |
| /* |
| * The accelerometer supports +-2G/4G/8G/16G ranges, and the resolution of |
| * each sample is 16 bits, signed. |
| * At +-8G the scale can calculated by |
| * ((8 + 8) * 9.80665 / (2^16 - 1)) * 10^6 = 2394.23819 scale in micro |
| * |
| */ |
| static const int bmi323_accel_scale[][2] = { |
| { 0, 598 }, |
| { 0, 1197 }, |
| { 0, 2394 }, |
| { 0, 4788 }, |
| }; |
| |
| static const int bmi323_gyro_scale[][2] = { |
| { 0, 66 }, |
| { 0, 133 }, |
| { 0, 266 }, |
| { 0, 532 }, |
| { 0, 1065 }, |
| }; |
| |
| static const int bmi323_accel_gyro_avrg[] = {0, 2, 4, 8, 16, 32, 64}; |
| |
| static const struct bmi323_hw bmi323_hw[2] = { |
| [BMI323_ACCEL] = { |
| .data = BMI323_ACCEL_X_REG, |
| .config = BMI323_ACC_CONF_REG, |
| .scale_table = bmi323_accel_scale, |
| .scale_table_len = ARRAY_SIZE(bmi323_accel_scale), |
| }, |
| [BMI323_GYRO] = { |
| .data = BMI323_GYRO_X_REG, |
| .config = BMI323_GYRO_CONF_REG, |
| .scale_table = bmi323_gyro_scale, |
| .scale_table_len = ARRAY_SIZE(bmi323_gyro_scale), |
| }, |
| }; |
| |
| static const unsigned int bmi323_reg_savestate[] = { |
| BMI323_INT_MAP1_REG, |
| BMI323_INT_MAP2_REG, |
| BMI323_IO_INT_CTR_REG, |
| BMI323_IO_INT_CONF_REG, |
| BMI323_ACC_CONF_REG, |
| BMI323_GYRO_CONF_REG, |
| BMI323_FEAT_IO0_REG, |
| BMI323_FIFO_WTRMRK_REG, |
| BMI323_FIFO_CONF_REG |
| }; |
| |
| static const unsigned int bmi323_ext_reg_savestate[] = { |
| BMI323_GEN_SET1_REG, |
| BMI323_TAP1_REG, |
| BMI323_TAP2_REG, |
| BMI323_TAP3_REG, |
| BMI323_FEAT_IO0_S_TAP_MSK, |
| BMI323_STEP_SC1_REG, |
| BMI323_ANYMO1_REG, |
| BMI323_NOMO1_REG, |
| BMI323_ANYMO1_REG + BMI323_MO2_OFFSET, |
| BMI323_NOMO1_REG + BMI323_MO2_OFFSET, |
| BMI323_ANYMO1_REG + BMI323_MO3_OFFSET, |
| BMI323_NOMO1_REG + BMI323_MO3_OFFSET |
| }; |
| |
| struct bmi323_regs_runtime_pm { |
| unsigned int reg_settings[ARRAY_SIZE(bmi323_reg_savestate)]; |
| unsigned int ext_reg_settings[ARRAY_SIZE(bmi323_ext_reg_savestate)]; |
| }; |
| |
| struct bmi323_data { |
| struct device *dev; |
| struct regmap *regmap; |
| struct iio_mount_matrix orientation; |
| enum bmi323_irq_pin irq_pin; |
| struct iio_trigger *trig; |
| bool drdy_trigger_enabled; |
| enum bmi323_state state; |
| s64 fifo_tstamp, old_fifo_tstamp; |
| u32 odrns[BMI323_SENSORS_CNT]; |
| u32 odrhz[BMI323_SENSORS_CNT]; |
| unsigned int feature_events; |
| struct bmi323_regs_runtime_pm runtime_pm_status; |
| |
| /* |
| * Lock to protect the members of device's private data from concurrent |
| * access and also to serialize the access of extended registers. |
| * See bmi323_write_ext_reg(..) for more info. |
| */ |
| struct mutex mutex; |
| int watermark; |
| __le16 fifo_buff[BMI323_FIFO_FULL_IN_WORDS] __aligned(IIO_DMA_MINALIGN); |
| struct { |
| __le16 channels[BMI323_CHAN_MAX]; |
| s64 ts __aligned(8); |
| } buffer; |
| __le16 steps_count[BMI323_STEP_LEN]; |
| }; |
| |
| static const struct iio_mount_matrix * |
| bmi323_get_mount_matrix(const struct iio_dev *idev, |
| const struct iio_chan_spec *chan) |
| { |
| struct bmi323_data *data = iio_priv(idev); |
| |
| return &data->orientation; |
| } |
| |
| static const struct iio_chan_spec_ext_info bmi323_ext_info[] = { |
| IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, bmi323_get_mount_matrix), |
| { } |
| }; |
| |
| static const struct iio_event_spec bmi323_step_wtrmrk_event = { |
| .type = IIO_EV_TYPE_CHANGE, |
| .dir = IIO_EV_DIR_NONE, |
| .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | |
| BIT(IIO_EV_INFO_VALUE), |
| }; |
| |
| static const struct iio_event_spec bmi323_accel_event[] = { |
| { |
| .type = IIO_EV_TYPE_MAG, |
| .dir = IIO_EV_DIR_FALLING, |
| .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | |
| BIT(IIO_EV_INFO_PERIOD) | |
| BIT(IIO_EV_INFO_HYSTERESIS) | |
| BIT(IIO_EV_INFO_ENABLE), |
| }, |
| { |
| .type = IIO_EV_TYPE_MAG, |
| .dir = IIO_EV_DIR_RISING, |
| .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | |
| BIT(IIO_EV_INFO_PERIOD) | |
| BIT(IIO_EV_INFO_HYSTERESIS) | |
| BIT(IIO_EV_INFO_ENABLE), |
| }, |
| { |
| .type = IIO_EV_TYPE_GESTURE, |
| .dir = IIO_EV_DIR_SINGLETAP, |
| .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | |
| BIT(IIO_EV_INFO_VALUE) | |
| BIT(IIO_EV_INFO_RESET_TIMEOUT), |
| }, |
| { |
| .type = IIO_EV_TYPE_GESTURE, |
| .dir = IIO_EV_DIR_DOUBLETAP, |
| .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | |
| BIT(IIO_EV_INFO_VALUE) | |
| BIT(IIO_EV_INFO_RESET_TIMEOUT) | |
| BIT(IIO_EV_INFO_TAP2_MIN_DELAY), |
| }, |
| }; |
| |
| #define BMI323_ACCEL_CHANNEL(_type, _axis, _index) { \ |
| .type = _type, \ |
| .modified = 1, \ |
| .channel2 = IIO_MOD_##_axis, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ |
| BIT(IIO_CHAN_INFO_SCALE) | \ |
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ |
| .info_mask_shared_by_type_available = \ |
| BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ |
| BIT(IIO_CHAN_INFO_SCALE) | \ |
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ |
| .scan_index = _index, \ |
| .scan_type = { \ |
| .sign = 's', \ |
| .realbits = 16, \ |
| .storagebits = 16, \ |
| .endianness = IIO_LE, \ |
| }, \ |
| .ext_info = bmi323_ext_info, \ |
| .event_spec = bmi323_accel_event, \ |
| .num_event_specs = ARRAY_SIZE(bmi323_accel_event), \ |
| } |
| |
| #define BMI323_GYRO_CHANNEL(_type, _axis, _index) { \ |
| .type = _type, \ |
| .modified = 1, \ |
| .channel2 = IIO_MOD_##_axis, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ |
| BIT(IIO_CHAN_INFO_SCALE) | \ |
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ |
| .info_mask_shared_by_type_available = \ |
| BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ |
| BIT(IIO_CHAN_INFO_SCALE) | \ |
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ |
| .scan_index = _index, \ |
| .scan_type = { \ |
| .sign = 's', \ |
| .realbits = 16, \ |
| .storagebits = 16, \ |
| .endianness = IIO_LE, \ |
| }, \ |
| .ext_info = bmi323_ext_info, \ |
| } |
| |
| static const struct iio_chan_spec bmi323_channels[] = { |
| BMI323_ACCEL_CHANNEL(IIO_ACCEL, X, BMI323_ACCEL_X), |
| BMI323_ACCEL_CHANNEL(IIO_ACCEL, Y, BMI323_ACCEL_Y), |
| BMI323_ACCEL_CHANNEL(IIO_ACCEL, Z, BMI323_ACCEL_Z), |
| BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, X, BMI323_GYRO_X), |
| BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Y, BMI323_GYRO_Y), |
| BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Z, BMI323_GYRO_Z), |
| { |
| .type = IIO_TEMP, |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
| BIT(IIO_CHAN_INFO_OFFSET) | |
| BIT(IIO_CHAN_INFO_SCALE), |
| .scan_index = -1, |
| }, |
| { |
| .type = IIO_STEPS, |
| .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | |
| BIT(IIO_CHAN_INFO_ENABLE), |
| .scan_index = -1, |
| .event_spec = &bmi323_step_wtrmrk_event, |
| .num_event_specs = 1, |
| |
| }, |
| IIO_CHAN_SOFT_TIMESTAMP(BMI323_CHAN_MAX), |
| }; |
| |
| static const int bmi323_acc_gyro_odr[][2] = { |
| { 0, 781250 }, |
| { 1, 562500 }, |
| { 3, 125000 }, |
| { 6, 250000 }, |
| { 12, 500000 }, |
| { 25, 0 }, |
| { 50, 0 }, |
| { 100, 0 }, |
| { 200, 0 }, |
| { 400, 0 }, |
| { 800, 0 }, |
| }; |
| |
| static const int bmi323_acc_gyro_odrns[] = { |
| 1280 * MEGA, |
| 640 * MEGA, |
| 320 * MEGA, |
| 160 * MEGA, |
| 80 * MEGA, |
| 40 * MEGA, |
| 20 * MEGA, |
| 10 * MEGA, |
| 5 * MEGA, |
| 2500 * KILO, |
| 1250 * KILO, |
| }; |
| |
| static enum bmi323_sensor_type bmi323_iio_to_sensor(enum iio_chan_type iio_type) |
| { |
| switch (iio_type) { |
| case IIO_ACCEL: |
| return BMI323_ACCEL; |
| case IIO_ANGL_VEL: |
| return BMI323_GYRO; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int bmi323_set_mode(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, |
| enum bmi323_opr_mode mode) |
| { |
| guard(mutex)(&data->mutex); |
| return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, |
| BMI323_ACC_GYRO_CONF_MODE_MSK, |
| FIELD_PREP(BMI323_ACC_GYRO_CONF_MODE_MSK, |
| mode)); |
| } |
| |
| /* |
| * When writing data to extended register there must be no communication to |
| * any other register before write transaction is complete. |
| * See datasheet section 6.2 Extended Register Map Description. |
| */ |
| static int bmi323_write_ext_reg(struct bmi323_data *data, unsigned int ext_addr, |
| unsigned int ext_data) |
| { |
| int ret, feature_status; |
| |
| ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS, |
| &feature_status); |
| if (ret) |
| return ret; |
| |
| if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status)) |
| return -EBUSY; |
| |
| ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr); |
| if (ret) |
| return ret; |
| |
| return regmap_write(data->regmap, BMI323_FEAT_DATA_TX, ext_data); |
| } |
| |
| /* |
| * When reading data from extended register there must be no communication to |
| * any other register before read transaction is complete. |
| * See datasheet section 6.2 Extended Register Map Description. |
| */ |
| static int bmi323_read_ext_reg(struct bmi323_data *data, unsigned int ext_addr, |
| unsigned int *ext_data) |
| { |
| int ret, feature_status; |
| |
| ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS, |
| &feature_status); |
| if (ret) |
| return ret; |
| |
| if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status)) |
| return -EBUSY; |
| |
| ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr); |
| if (ret) |
| return ret; |
| |
| return regmap_read(data->regmap, BMI323_FEAT_DATA_TX, ext_data); |
| } |
| |
| static int bmi323_update_ext_reg(struct bmi323_data *data, |
| unsigned int ext_addr, |
| unsigned int mask, unsigned int ext_data) |
| { |
| unsigned int value; |
| int ret; |
| |
| ret = bmi323_read_ext_reg(data, ext_addr, &value); |
| if (ret) |
| return ret; |
| |
| set_mask_bits(&value, mask, ext_data); |
| |
| return bmi323_write_ext_reg(data, ext_addr, value); |
| } |
| |
| static int bmi323_get_error_status(struct bmi323_data *data) |
| { |
| int error, ret; |
| |
| guard(mutex)(&data->mutex); |
| ret = regmap_read(data->regmap, BMI323_ERR_REG, &error); |
| if (ret) |
| return ret; |
| |
| if (error) |
| dev_err(data->dev, "Sensor error 0x%x\n", error); |
| |
| return error; |
| } |
| |
| static int bmi323_feature_engine_events(struct bmi323_data *data, |
| const unsigned int event_mask, |
| bool state) |
| { |
| unsigned int value; |
| int ret; |
| |
| ret = regmap_read(data->regmap, BMI323_FEAT_IO0_REG, &value); |
| if (ret) |
| return ret; |
| |
| /* Register must be cleared before changing an active config */ |
| ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, 0); |
| if (ret) |
| return ret; |
| |
| if (state) |
| value |= event_mask; |
| else |
| value &= ~event_mask; |
| |
| ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, value); |
| if (ret) |
| return ret; |
| |
| return regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG, |
| BMI323_FEAT_IO_STATUS_MSK); |
| } |
| |
| static int bmi323_step_wtrmrk_en(struct bmi323_data *data, int state) |
| { |
| enum bmi323_irq_pin step_irq; |
| int ret; |
| |
| guard(mutex)(&data->mutex); |
| if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events)) |
| return -EINVAL; |
| |
| if (state) |
| step_irq = data->irq_pin; |
| else |
| step_irq = BMI323_IRQ_DISABLED; |
| |
| ret = bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, |
| BMI323_STEP_SC1_WTRMRK_MSK, |
| FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK, |
| state ? 1 : 0)); |
| if (ret) |
| return ret; |
| |
| return regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG, |
| BMI323_STEP_CNT_MSK, |
| FIELD_PREP(BMI323_STEP_CNT_MSK, step_irq)); |
| } |
| |
| static int bmi323_motion_config_reg(enum iio_event_direction dir) |
| { |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| return BMI323_ANYMO1_REG; |
| case IIO_EV_DIR_FALLING: |
| return BMI323_NOMO1_REG; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int bmi323_motion_event_en(struct bmi323_data *data, |
| enum iio_event_direction dir, int state) |
| { |
| unsigned int state_value = state ? BMI323_FEAT_XYZ_MSK : 0; |
| int config, ret, msk, raw, field_value; |
| enum bmi323_irq_pin motion_irq; |
| int irq_msk, irq_field_val; |
| |
| if (state) |
| motion_irq = data->irq_pin; |
| else |
| motion_irq = BMI323_IRQ_DISABLED; |
| |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| msk = BMI323_FEAT_IO0_XYZ_MOTION_MSK; |
| raw = 512; |
| config = BMI323_ANYMO1_REG; |
| irq_msk = BMI323_MOTION_MSK; |
| irq_field_val = FIELD_PREP(BMI323_MOTION_MSK, motion_irq); |
| field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_MOTION_MSK, |
| state_value); |
| break; |
| case IIO_EV_DIR_FALLING: |
| msk = BMI323_FEAT_IO0_XYZ_NOMOTION_MSK; |
| raw = 0; |
| config = BMI323_NOMO1_REG; |
| irq_msk = BMI323_NOMOTION_MSK; |
| irq_field_val = FIELD_PREP(BMI323_NOMOTION_MSK, motion_irq); |
| field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK, |
| state_value); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| guard(mutex)(&data->mutex); |
| ret = bmi323_feature_engine_events(data, msk, state); |
| if (ret) |
| return ret; |
| |
| ret = bmi323_update_ext_reg(data, config, |
| BMI323_MO1_REF_UP_MSK, |
| FIELD_PREP(BMI323_MO1_REF_UP_MSK, 0)); |
| if (ret) |
| return ret; |
| |
| /* Set initial value to avoid interrupts while enabling*/ |
| ret = bmi323_update_ext_reg(data, config, |
| BMI323_MO1_SLOPE_TH_MSK, |
| FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK, raw)); |
| if (ret) |
| return ret; |
| |
| ret = regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG, irq_msk, |
| irq_field_val); |
| if (ret) |
| return ret; |
| |
| set_mask_bits(&data->feature_events, msk, field_value); |
| |
| return 0; |
| } |
| |
| static int bmi323_tap_event_en(struct bmi323_data *data, |
| enum iio_event_direction dir, int state) |
| { |
| enum bmi323_irq_pin tap_irq; |
| int ret, tap_enabled; |
| |
| guard(mutex)(&data->mutex); |
| |
| if (data->odrhz[BMI323_ACCEL] < 200) { |
| dev_err(data->dev, "Invalid accelerometer parameter\n"); |
| return -EINVAL; |
| } |
| |
| switch (dir) { |
| case IIO_EV_DIR_SINGLETAP: |
| ret = bmi323_feature_engine_events(data, |
| BMI323_FEAT_IO0_S_TAP_MSK, |
| state); |
| if (ret) |
| return ret; |
| |
| set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_S_TAP_MSK, |
| FIELD_PREP(BMI323_FEAT_IO0_S_TAP_MSK, state)); |
| break; |
| case IIO_EV_DIR_DOUBLETAP: |
| ret = bmi323_feature_engine_events(data, |
| BMI323_FEAT_IO0_D_TAP_MSK, |
| state); |
| if (ret) |
| return ret; |
| |
| set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_D_TAP_MSK, |
| FIELD_PREP(BMI323_FEAT_IO0_D_TAP_MSK, state)); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| tap_enabled = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK | |
| BMI323_FEAT_IO0_D_TAP_MSK, |
| data->feature_events); |
| |
| if (tap_enabled) |
| tap_irq = data->irq_pin; |
| else |
| tap_irq = BMI323_IRQ_DISABLED; |
| |
| ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, |
| BMI323_TAP_MSK, |
| FIELD_PREP(BMI323_TAP_MSK, tap_irq)); |
| if (ret) |
| return ret; |
| |
| if (!state) |
| return 0; |
| |
| ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, |
| BMI323_TAP1_MAX_PEAKS_MSK, |
| FIELD_PREP(BMI323_TAP1_MAX_PEAKS_MSK, |
| 0x04)); |
| if (ret) |
| return ret; |
| |
| ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, |
| BMI323_TAP1_AXIS_SEL_MSK, |
| FIELD_PREP(BMI323_TAP1_AXIS_SEL_MSK, |
| BMI323_AXIS_XYZ_MSK)); |
| if (ret) |
| return ret; |
| |
| return bmi323_update_ext_reg(data, BMI323_TAP1_REG, |
| BMI323_TAP1_TIMOUT_MSK, |
| FIELD_PREP(BMI323_TAP1_TIMOUT_MSK, |
| 0)); |
| } |
| |
| static ssize_t in_accel_gesture_tap_wait_dur_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct iio_dev *indio_dev = dev_to_iio_dev(dev); |
| struct bmi323_data *data = iio_priv(indio_dev); |
| unsigned int reg_value, raw; |
| int ret, val[2]; |
| |
| scoped_guard(mutex, &data->mutex) { |
| ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG, ®_value); |
| if (ret) |
| return ret; |
| } |
| |
| raw = FIELD_GET(BMI323_TAP2_MAX_DUR_MSK, reg_value); |
| val[0] = raw / BMI323_MAX_GES_DUR_SCALE; |
| val[1] = BMI323_RAW_TO_MICRO(raw, BMI323_MAX_GES_DUR_SCALE); |
| |
| return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(val), |
| val); |
| } |
| |
| static ssize_t in_accel_gesture_tap_wait_dur_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 bmi323_data *data = iio_priv(indio_dev); |
| int ret, val_int, val_fract, raw; |
| |
| ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract); |
| if (ret) |
| return ret; |
| |
| raw = BMI323_INT_MICRO_TO_RAW(val_int, val_fract, |
| BMI323_MAX_GES_DUR_SCALE); |
| if (!in_range(raw, 0, 64)) |
| return -EINVAL; |
| |
| guard(mutex)(&data->mutex); |
| ret = bmi323_update_ext_reg(data, BMI323_TAP2_REG, |
| BMI323_TAP2_MAX_DUR_MSK, |
| FIELD_PREP(BMI323_TAP2_MAX_DUR_MSK, raw)); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| /* |
| * Maximum duration from first tap within the second tap is expected to happen. |
| * This timeout is applicable only if gesture_tap_wait_timeout is enabled. |
| */ |
| static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_dur, 0); |
| |
| static ssize_t in_accel_gesture_tap_wait_timeout_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct iio_dev *indio_dev = dev_to_iio_dev(dev); |
| struct bmi323_data *data = iio_priv(indio_dev); |
| unsigned int reg_value, raw; |
| int ret; |
| |
| scoped_guard(mutex, &data->mutex) { |
| ret = bmi323_read_ext_reg(data, BMI323_TAP1_REG, ®_value); |
| if (ret) |
| return ret; |
| } |
| |
| raw = FIELD_GET(BMI323_TAP1_TIMOUT_MSK, reg_value); |
| |
| return iio_format_value(buf, IIO_VAL_INT, 1, &raw); |
| } |
| |
| static ssize_t in_accel_gesture_tap_wait_timeout_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 bmi323_data *data = iio_priv(indio_dev); |
| bool val; |
| int ret; |
| |
| ret = kstrtobool(buf, &val); |
| if (ret) |
| return ret; |
| |
| guard(mutex)(&data->mutex); |
| ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, |
| BMI323_TAP1_TIMOUT_MSK, |
| FIELD_PREP(BMI323_TAP1_TIMOUT_MSK, val)); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| /* Enable/disable gesture confirmation with wait time */ |
| static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_timeout, 0); |
| |
| static IIO_CONST_ATTR(in_accel_gesture_tap_wait_dur_available, |
| "[0.0 0.04 2.52]"); |
| |
| static IIO_CONST_ATTR(in_accel_gesture_doubletap_tap2_min_delay_available, |
| "[0.005 0.005 0.075]"); |
| |
| static IIO_CONST_ATTR(in_accel_gesture_tap_reset_timeout_available, |
| "[0.04 0.04 0.6]"); |
| |
| static IIO_CONST_ATTR(in_accel_gesture_tap_value_available, "[0.0 0.002 1.99]"); |
| |
| static IIO_CONST_ATTR(in_accel_mag_value_available, "[0.0 0.002 7.99]"); |
| |
| static IIO_CONST_ATTR(in_accel_mag_period_available, "[0.0 0.02 162.0]"); |
| |
| static IIO_CONST_ATTR(in_accel_mag_hysteresis_available, "[0.0 0.002 1.99]"); |
| |
| static struct attribute *bmi323_event_attributes[] = { |
| &iio_const_attr_in_accel_gesture_tap_value_available.dev_attr.attr, |
| &iio_const_attr_in_accel_gesture_tap_reset_timeout_available.dev_attr.attr, |
| &iio_const_attr_in_accel_gesture_doubletap_tap2_min_delay_available.dev_attr.attr, |
| &iio_const_attr_in_accel_gesture_tap_wait_dur_available.dev_attr.attr, |
| &iio_dev_attr_in_accel_gesture_tap_wait_timeout.dev_attr.attr, |
| &iio_dev_attr_in_accel_gesture_tap_wait_dur.dev_attr.attr, |
| &iio_const_attr_in_accel_mag_value_available.dev_attr.attr, |
| &iio_const_attr_in_accel_mag_period_available.dev_attr.attr, |
| &iio_const_attr_in_accel_mag_hysteresis_available.dev_attr.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group bmi323_event_attribute_group = { |
| .attrs = bmi323_event_attributes, |
| }; |
| |
| static int bmi323_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 bmi323_data *data = iio_priv(indio_dev); |
| |
| switch (type) { |
| case IIO_EV_TYPE_MAG: |
| return bmi323_motion_event_en(data, dir, state); |
| case IIO_EV_TYPE_GESTURE: |
| return bmi323_tap_event_en(data, dir, state); |
| case IIO_EV_TYPE_CHANGE: |
| return bmi323_step_wtrmrk_en(data, state); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int bmi323_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 bmi323_data *data = iio_priv(indio_dev); |
| int ret, value, reg_val; |
| |
| guard(mutex)(&data->mutex); |
| |
| switch (chan->type) { |
| case IIO_ACCEL: |
| switch (dir) { |
| case IIO_EV_DIR_SINGLETAP: |
| ret = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK, |
| data->feature_events); |
| break; |
| case IIO_EV_DIR_DOUBLETAP: |
| ret = FIELD_GET(BMI323_FEAT_IO0_D_TAP_MSK, |
| data->feature_events); |
| break; |
| case IIO_EV_DIR_RISING: |
| value = FIELD_GET(BMI323_FEAT_IO0_XYZ_MOTION_MSK, |
| data->feature_events); |
| ret = value ? 1 : 0; |
| break; |
| case IIO_EV_DIR_FALLING: |
| value = FIELD_GET(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK, |
| data->feature_events); |
| ret = value ? 1 : 0; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| return ret; |
| case IIO_STEPS: |
| ret = regmap_read(data->regmap, BMI323_INT_MAP1_REG, ®_val); |
| if (ret) |
| return ret; |
| |
| return FIELD_GET(BMI323_STEP_CNT_MSK, reg_val) ? 1 : 0; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int bmi323_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 bmi323_data *data = iio_priv(indio_dev); |
| unsigned int raw; |
| int reg; |
| |
| guard(mutex)(&data->mutex); |
| |
| switch (type) { |
| case IIO_EV_TYPE_GESTURE: |
| switch (info) { |
| case IIO_EV_INFO_VALUE: |
| if (!in_range(val, 0, 2)) |
| return -EINVAL; |
| |
| raw = BMI323_INT_MICRO_TO_RAW(val, val2, |
| BMI323_TAP_THRES_SCALE); |
| |
| return bmi323_update_ext_reg(data, BMI323_TAP2_REG, |
| BMI323_TAP2_THRES_MSK, |
| FIELD_PREP(BMI323_TAP2_THRES_MSK, |
| raw)); |
| case IIO_EV_INFO_RESET_TIMEOUT: |
| if (val || !in_range(val2, 40000, 560001)) |
| return -EINVAL; |
| |
| raw = BMI323_INT_MICRO_TO_RAW(val, val2, |
| BMI323_QUITE_TIM_GES_SCALE); |
| |
| return bmi323_update_ext_reg(data, BMI323_TAP3_REG, |
| BMI323_TAP3_QT_AFT_GES_MSK, |
| FIELD_PREP(BMI323_TAP3_QT_AFT_GES_MSK, |
| raw)); |
| case IIO_EV_INFO_TAP2_MIN_DELAY: |
| if (val || !in_range(val2, 5000, 70001)) |
| return -EINVAL; |
| |
| raw = BMI323_INT_MICRO_TO_RAW(val, val2, |
| BMI323_DUR_BW_TAP_SCALE); |
| |
| return bmi323_update_ext_reg(data, BMI323_TAP3_REG, |
| BMI323_TAP3_QT_BW_TAP_MSK, |
| FIELD_PREP(BMI323_TAP3_QT_BW_TAP_MSK, |
| raw)); |
| default: |
| return -EINVAL; |
| } |
| case IIO_EV_TYPE_MAG: |
| reg = bmi323_motion_config_reg(dir); |
| if (reg < 0) |
| return -EINVAL; |
| |
| switch (info) { |
| case IIO_EV_INFO_VALUE: |
| if (!in_range(val, 0, 8)) |
| return -EINVAL; |
| |
| raw = BMI323_INT_MICRO_TO_RAW(val, val2, |
| BMI323_MOTION_THRES_SCALE); |
| |
| return bmi323_update_ext_reg(data, reg, |
| BMI323_MO1_SLOPE_TH_MSK, |
| FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK, |
| raw)); |
| case IIO_EV_INFO_PERIOD: |
| if (!in_range(val, 0, 163)) |
| return -EINVAL; |
| |
| raw = BMI323_INT_MICRO_TO_RAW(val, val2, |
| BMI323_MOTION_DURAT_SCALE); |
| |
| return bmi323_update_ext_reg(data, |
| reg + BMI323_MO3_OFFSET, |
| BMI323_MO3_DURA_MSK, |
| FIELD_PREP(BMI323_MO3_DURA_MSK, |
| raw)); |
| case IIO_EV_INFO_HYSTERESIS: |
| if (!in_range(val, 0, 2)) |
| return -EINVAL; |
| |
| raw = BMI323_INT_MICRO_TO_RAW(val, val2, |
| BMI323_MOTION_HYSTR_SCALE); |
| |
| return bmi323_update_ext_reg(data, |
| reg + BMI323_MO2_OFFSET, |
| BMI323_MO2_HYSTR_MSK, |
| FIELD_PREP(BMI323_MO2_HYSTR_MSK, |
| raw)); |
| default: |
| return -EINVAL; |
| } |
| case IIO_EV_TYPE_CHANGE: |
| if (!in_range(val, 0, 20461)) |
| return -EINVAL; |
| |
| raw = val / 20; |
| return bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, |
| BMI323_STEP_SC1_WTRMRK_MSK, |
| FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK, |
| raw)); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int bmi323_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 bmi323_data *data = iio_priv(indio_dev); |
| unsigned int raw, reg_value; |
| int ret, reg; |
| |
| guard(mutex)(&data->mutex); |
| |
| switch (type) { |
| case IIO_EV_TYPE_GESTURE: |
| switch (info) { |
| case IIO_EV_INFO_VALUE: |
| ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG, |
| ®_value); |
| if (ret) |
| return ret; |
| |
| raw = FIELD_GET(BMI323_TAP2_THRES_MSK, reg_value); |
| *val = raw / BMI323_TAP_THRES_SCALE; |
| *val2 = BMI323_RAW_TO_MICRO(raw, BMI323_TAP_THRES_SCALE); |
| return IIO_VAL_INT_PLUS_MICRO; |
| case IIO_EV_INFO_RESET_TIMEOUT: |
| ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG, |
| ®_value); |
| if (ret) |
| return ret; |
| |
| raw = FIELD_GET(BMI323_TAP3_QT_AFT_GES_MSK, reg_value); |
| *val = 0; |
| *val2 = BMI323_RAW_TO_MICRO(raw, |
| BMI323_QUITE_TIM_GES_SCALE); |
| return IIO_VAL_INT_PLUS_MICRO; |
| case IIO_EV_INFO_TAP2_MIN_DELAY: |
| ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG, |
| ®_value); |
| if (ret) |
| return ret; |
| |
| raw = FIELD_GET(BMI323_TAP3_QT_BW_TAP_MSK, reg_value); |
| *val = 0; |
| *val2 = BMI323_RAW_TO_MICRO(raw, |
| BMI323_DUR_BW_TAP_SCALE); |
| return IIO_VAL_INT_PLUS_MICRO; |
| default: |
| return -EINVAL; |
| } |
| case IIO_EV_TYPE_MAG: |
| reg = bmi323_motion_config_reg(dir); |
| if (reg < 0) |
| return -EINVAL; |
| |
| switch (info) { |
| case IIO_EV_INFO_VALUE: |
| ret = bmi323_read_ext_reg(data, reg, ®_value); |
| if (ret) |
| return ret; |
| |
| raw = FIELD_GET(BMI323_MO1_SLOPE_TH_MSK, reg_value); |
| *val = raw / BMI323_MOTION_THRES_SCALE; |
| *val2 = BMI323_RAW_TO_MICRO(raw, |
| BMI323_MOTION_THRES_SCALE); |
| return IIO_VAL_INT_PLUS_MICRO; |
| case IIO_EV_INFO_PERIOD: |
| ret = bmi323_read_ext_reg(data, |
| reg + BMI323_MO3_OFFSET, |
| ®_value); |
| if (ret) |
| return ret; |
| |
| raw = FIELD_GET(BMI323_MO3_DURA_MSK, reg_value); |
| *val = raw / BMI323_MOTION_DURAT_SCALE; |
| *val2 = BMI323_RAW_TO_MICRO(raw, |
| BMI323_MOTION_DURAT_SCALE); |
| return IIO_VAL_INT_PLUS_MICRO; |
| case IIO_EV_INFO_HYSTERESIS: |
| ret = bmi323_read_ext_reg(data, |
| reg + BMI323_MO2_OFFSET, |
| ®_value); |
| if (ret) |
| return ret; |
| |
| raw = FIELD_GET(BMI323_MO2_HYSTR_MSK, reg_value); |
| *val = raw / BMI323_MOTION_HYSTR_SCALE; |
| *val2 = BMI323_RAW_TO_MICRO(raw, |
| BMI323_MOTION_HYSTR_SCALE); |
| return IIO_VAL_INT_PLUS_MICRO; |
| default: |
| return -EINVAL; |
| } |
| case IIO_EV_TYPE_CHANGE: |
| ret = bmi323_read_ext_reg(data, BMI323_STEP_SC1_REG, |
| ®_value); |
| if (ret) |
| return ret; |
| |
| raw = FIELD_GET(BMI323_STEP_SC1_WTRMRK_MSK, reg_value); |
| *val = raw * 20; |
| return IIO_VAL_INT; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int __bmi323_fifo_flush(struct iio_dev *indio_dev) |
| { |
| struct bmi323_data *data = iio_priv(indio_dev); |
| int i, ret, fifo_lvl, frame_count, bit, index; |
| __le16 *frame, *pchannels; |
| u64 sample_period; |
| s64 tstamp; |
| |
| guard(mutex)(&data->mutex); |
| ret = regmap_read(data->regmap, BMI323_FIFO_FILL_LEVEL_REG, &fifo_lvl); |
| if (ret) |
| return ret; |
| |
| fifo_lvl = min(fifo_lvl, BMI323_FIFO_FULL_IN_WORDS); |
| |
| frame_count = fifo_lvl / BMI323_FIFO_FRAME_LENGTH; |
| if (!frame_count) |
| return -EINVAL; |
| |
| if (fifo_lvl % BMI323_FIFO_FRAME_LENGTH) |
| dev_warn(data->dev, "Bad FIFO alignment\n"); |
| |
| /* |
| * Approximate timestamps for each of the sample based on the sampling |
| * frequency, timestamp for last sample and number of samples. |
| */ |
| if (data->old_fifo_tstamp) { |
| sample_period = data->fifo_tstamp - data->old_fifo_tstamp; |
| do_div(sample_period, frame_count); |
| } else { |
| sample_period = data->odrns[BMI323_ACCEL]; |
| } |
| |
| tstamp = data->fifo_tstamp - (frame_count - 1) * sample_period; |
| |
| ret = regmap_noinc_read(data->regmap, BMI323_FIFO_DATA_REG, |
| &data->fifo_buff[0], |
| fifo_lvl * BMI323_BYTES_PER_SAMPLE); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < frame_count; i++) { |
| frame = &data->fifo_buff[i * BMI323_FIFO_FRAME_LENGTH]; |
| pchannels = &data->buffer.channels[0]; |
| |
| index = 0; |
| for_each_set_bit(bit, indio_dev->active_scan_mask, |
| BMI323_CHAN_MAX) |
| pchannels[index++] = frame[bit]; |
| |
| iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, |
| tstamp); |
| |
| tstamp += sample_period; |
| } |
| |
| return frame_count; |
| } |
| |
| static int bmi323_set_watermark(struct iio_dev *indio_dev, unsigned int val) |
| { |
| struct bmi323_data *data = iio_priv(indio_dev); |
| |
| val = min(val, (u32)BMI323_FIFO_FULL_IN_FRAMES); |
| |
| guard(mutex)(&data->mutex); |
| data->watermark = val; |
| |
| return 0; |
| } |
| |
| static int bmi323_fifo_disable(struct bmi323_data *data) |
| { |
| int ret; |
| |
| guard(mutex)(&data->mutex); |
| ret = regmap_write(data->regmap, BMI323_FIFO_CONF_REG, 0); |
| if (ret) |
| return ret; |
| |
| ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, |
| BMI323_FIFO_WTRMRK_MSK, |
| FIELD_PREP(BMI323_FIFO_WTRMRK_MSK, 0)); |
| if (ret) |
| return ret; |
| |
| data->fifo_tstamp = 0; |
| data->state = BMI323_IDLE; |
| |
| return 0; |
| } |
| |
| static int bmi323_buffer_predisable(struct iio_dev *indio_dev) |
| { |
| struct bmi323_data *data = iio_priv(indio_dev); |
| |
| if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED) |
| return 0; |
| |
| return bmi323_fifo_disable(data); |
| } |
| |
| static int bmi323_update_watermark(struct bmi323_data *data) |
| { |
| int wtrmrk; |
| |
| wtrmrk = data->watermark * BMI323_FIFO_FRAME_LENGTH; |
| |
| return regmap_write(data->regmap, BMI323_FIFO_WTRMRK_REG, wtrmrk); |
| } |
| |
| static int bmi323_fifo_enable(struct bmi323_data *data) |
| { |
| int ret; |
| |
| guard(mutex)(&data->mutex); |
| ret = regmap_update_bits(data->regmap, BMI323_FIFO_CONF_REG, |
| BMI323_FIFO_CONF_ACC_GYR_EN_MSK, |
| FIELD_PREP(BMI323_FIFO_CONF_ACC_GYR_EN_MSK, |
| BMI323_FIFO_ACC_GYR_MSK)); |
| if (ret) |
| return ret; |
| |
| ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, |
| BMI323_FIFO_WTRMRK_MSK, |
| FIELD_PREP(BMI323_FIFO_WTRMRK_MSK, |
| data->irq_pin)); |
| if (ret) |
| return ret; |
| |
| ret = bmi323_update_watermark(data); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(data->regmap, BMI323_FIFO_CTRL_REG, |
| BMI323_FIFO_FLUSH_MSK); |
| if (ret) |
| return ret; |
| |
| data->state = BMI323_BUFFER_FIFO; |
| |
| return 0; |
| } |
| |
| static int bmi323_buffer_preenable(struct iio_dev *indio_dev) |
| { |
| struct bmi323_data *data = iio_priv(indio_dev); |
| |
| guard(mutex)(&data->mutex); |
| /* |
| * When the ODR of the accelerometer and gyroscope do not match, the |
| * maximum ODR value between the accelerometer and gyroscope is used |
| * for FIFO and the signal with lower ODR will insert dummy frame. |
| * So allow buffer read only when ODR's of accelero and gyro are equal. |
| * See datasheet section 5.7 "FIFO Data Buffering". |
| */ |
| if (data->odrns[BMI323_ACCEL] != data->odrns[BMI323_GYRO]) { |
| dev_err(data->dev, "Accelero and Gyro ODR doesn't match\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int bmi323_buffer_postenable(struct iio_dev *indio_dev) |
| { |
| struct bmi323_data *data = iio_priv(indio_dev); |
| |
| if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED) |
| return 0; |
| |
| return bmi323_fifo_enable(data); |
| } |
| |
| static ssize_t hwfifo_watermark_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct iio_dev *indio_dev = dev_to_iio_dev(dev); |
| struct bmi323_data *data = iio_priv(indio_dev); |
| int wm; |
| |
| scoped_guard(mutex, &data->mutex) |
| wm = data->watermark; |
| |
| return sysfs_emit(buf, "%d\n", wm); |
| } |
| static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0); |
| |
| static ssize_t hwfifo_enabled_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct iio_dev *indio_dev = dev_to_iio_dev(dev); |
| struct bmi323_data *data = iio_priv(indio_dev); |
| bool state; |
| |
| scoped_guard(mutex, &data->mutex) |
| state = data->state == BMI323_BUFFER_FIFO; |
| |
| return sysfs_emit(buf, "%d\n", state); |
| } |
| static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0); |
| |
| static const struct iio_dev_attr *bmi323_fifo_attributes[] = { |
| &iio_dev_attr_hwfifo_watermark, |
| &iio_dev_attr_hwfifo_enabled, |
| NULL |
| }; |
| |
| static const struct iio_buffer_setup_ops bmi323_buffer_ops = { |
| .preenable = bmi323_buffer_preenable, |
| .postenable = bmi323_buffer_postenable, |
| .predisable = bmi323_buffer_predisable, |
| }; |
| |
| static irqreturn_t bmi323_irq_thread_handler(int irq, void *private) |
| { |
| struct iio_dev *indio_dev = private; |
| struct bmi323_data *data = iio_priv(indio_dev); |
| unsigned int status_addr, status, feature_event; |
| s64 timestamp = iio_get_time_ns(indio_dev); |
| int ret; |
| |
| if (data->irq_pin == BMI323_IRQ_INT1) |
| status_addr = BMI323_STATUS_INT1_REG; |
| else |
| status_addr = BMI323_STATUS_INT2_REG; |
| |
| scoped_guard(mutex, &data->mutex) { |
| ret = regmap_read(data->regmap, status_addr, &status); |
| if (ret) |
| return IRQ_NONE; |
| } |
| |
| if (!status || FIELD_GET(BMI323_STATUS_ERROR_MSK, status)) |
| return IRQ_NONE; |
| |
| if (FIELD_GET(BMI323_STATUS_FIFO_WTRMRK_MSK, status)) { |
| data->old_fifo_tstamp = data->fifo_tstamp; |
| data->fifo_tstamp = iio_get_time_ns(indio_dev); |
| ret = __bmi323_fifo_flush(indio_dev); |
| if (ret < 0) |
| return IRQ_NONE; |
| } |
| |
| if (FIELD_GET(BMI323_STATUS_ACC_GYR_DRDY_MSK, status)) |
| iio_trigger_poll_nested(data->trig); |
| |
| if (FIELD_GET(BMI323_STATUS_MOTION_MSK, status)) |
| iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, |
| IIO_MOD_X_OR_Y_OR_Z, |
| IIO_EV_TYPE_MAG, |
| IIO_EV_DIR_RISING), |
| timestamp); |
| |
| if (FIELD_GET(BMI323_STATUS_NOMOTION_MSK, status)) |
| iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, |
| IIO_MOD_X_OR_Y_OR_Z, |
| IIO_EV_TYPE_MAG, |
| IIO_EV_DIR_FALLING), |
| timestamp); |
| |
| if (FIELD_GET(BMI323_STATUS_STP_WTR_MSK, status)) |
| iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_STEPS, 0, |
| IIO_NO_MOD, |
| IIO_EV_TYPE_CHANGE, |
| IIO_EV_DIR_NONE), |
| timestamp); |
| |
| if (FIELD_GET(BMI323_STATUS_TAP_MSK, status)) { |
| scoped_guard(mutex, &data->mutex) { |
| ret = regmap_read(data->regmap, |
| BMI323_FEAT_EVNT_EXT_REG, |
| &feature_event); |
| if (ret) |
| return IRQ_NONE; |
| } |
| |
| if (FIELD_GET(BMI323_FEAT_EVNT_EXT_S_MSK, feature_event)) { |
| 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(BMI323_FEAT_EVNT_EXT_D_MSK, feature_event)) |
| 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); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int bmi323_set_drdy_irq(struct bmi323_data *data, |
| enum bmi323_irq_pin irq_pin) |
| { |
| int ret; |
| |
| ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, |
| BMI323_GYR_DRDY_MSK, |
| FIELD_PREP(BMI323_GYR_DRDY_MSK, irq_pin)); |
| if (ret) |
| return ret; |
| |
| return regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, |
| BMI323_ACC_DRDY_MSK, |
| FIELD_PREP(BMI323_ACC_DRDY_MSK, irq_pin)); |
| } |
| |
| static int bmi323_data_rdy_trigger_set_state(struct iio_trigger *trig, |
| bool state) |
| { |
| struct bmi323_data *data = iio_trigger_get_drvdata(trig); |
| enum bmi323_irq_pin irq_pin; |
| |
| guard(mutex)(&data->mutex); |
| |
| if (data->state == BMI323_BUFFER_FIFO) { |
| dev_warn(data->dev, "Can't set trigger when FIFO enabled\n"); |
| return -EBUSY; |
| } |
| |
| if (state) { |
| data->state = BMI323_BUFFER_DRDY_TRIGGERED; |
| irq_pin = data->irq_pin; |
| } else { |
| data->state = BMI323_IDLE; |
| irq_pin = BMI323_IRQ_DISABLED; |
| } |
| |
| return bmi323_set_drdy_irq(data, irq_pin); |
| } |
| |
| static const struct iio_trigger_ops bmi323_trigger_ops = { |
| .set_trigger_state = &bmi323_data_rdy_trigger_set_state, |
| }; |
| |
| static irqreturn_t bmi323_trigger_handler(int irq, void *p) |
| { |
| struct iio_poll_func *pf = p; |
| struct iio_dev *indio_dev = pf->indio_dev; |
| struct bmi323_data *data = iio_priv(indio_dev); |
| int ret, bit, index = 0; |
| |
| /* Lock to protect the data->buffer */ |
| guard(mutex)(&data->mutex); |
| |
| if (*indio_dev->active_scan_mask == BMI323_ALL_CHAN_MSK) { |
| ret = regmap_bulk_read(data->regmap, BMI323_ACCEL_X_REG, |
| &data->buffer.channels, |
| ARRAY_SIZE(data->buffer.channels)); |
| if (ret) |
| goto out; |
| } else { |
| for_each_set_bit(bit, indio_dev->active_scan_mask, |
| BMI323_CHAN_MAX) { |
| ret = regmap_raw_read(data->regmap, |
| BMI323_ACCEL_X_REG + bit, |
| &data->buffer.channels[index++], |
| BMI323_BYTES_PER_SAMPLE); |
| if (ret) |
| goto out; |
| } |
| } |
| |
| iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, |
| iio_get_time_ns(indio_dev)); |
| |
| out: |
| iio_trigger_notify_done(indio_dev->trig); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int bmi323_set_average(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, int avg) |
| { |
| int raw = ARRAY_SIZE(bmi323_accel_gyro_avrg); |
| |
| while (raw--) |
| if (avg == bmi323_accel_gyro_avrg[raw]) |
| break; |
| if (raw < 0) |
| return -EINVAL; |
| |
| guard(mutex)(&data->mutex); |
| return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, |
| BMI323_ACC_GYRO_CONF_AVG_MSK, |
| FIELD_PREP(BMI323_ACC_GYRO_CONF_AVG_MSK, |
| raw)); |
| } |
| |
| static int bmi323_get_average(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, int *avg) |
| { |
| int ret, value, raw; |
| |
| scoped_guard(mutex, &data->mutex) { |
| ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value); |
| if (ret) |
| return ret; |
| } |
| |
| raw = FIELD_GET(BMI323_ACC_GYRO_CONF_AVG_MSK, value); |
| *avg = bmi323_accel_gyro_avrg[raw]; |
| |
| return IIO_VAL_INT; |
| } |
| |
| static int bmi323_enable_steps(struct bmi323_data *data, int val) |
| { |
| int ret; |
| |
| guard(mutex)(&data->mutex); |
| if (data->odrhz[BMI323_ACCEL] < 200) { |
| dev_err(data->dev, "Invalid accelerometer parameter\n"); |
| return -EINVAL; |
| } |
| |
| ret = bmi323_feature_engine_events(data, BMI323_FEAT_IO0_STP_CNT_MSK, |
| val ? 1 : 0); |
| if (ret) |
| return ret; |
| |
| set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_STP_CNT_MSK, |
| FIELD_PREP(BMI323_FEAT_IO0_STP_CNT_MSK, val ? 1 : 0)); |
| |
| return 0; |
| } |
| |
| static int bmi323_read_steps(struct bmi323_data *data, int *val) |
| { |
| int ret; |
| |
| guard(mutex)(&data->mutex); |
| if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events)) |
| return -EINVAL; |
| |
| ret = regmap_bulk_read(data->regmap, BMI323_FEAT_IO2_REG, |
| data->steps_count, |
| ARRAY_SIZE(data->steps_count)); |
| if (ret) |
| return ret; |
| |
| *val = get_unaligned_le32(data->steps_count); |
| |
| return IIO_VAL_INT; |
| } |
| |
| static int bmi323_read_axis(struct bmi323_data *data, |
| struct iio_chan_spec const *chan, int *val) |
| { |
| enum bmi323_sensor_type sensor; |
| unsigned int value; |
| u8 addr; |
| int ret; |
| |
| ret = bmi323_get_error_status(data); |
| if (ret) |
| return -EINVAL; |
| |
| sensor = bmi323_iio_to_sensor(chan->type); |
| addr = bmi323_hw[sensor].data + (chan->channel2 - IIO_MOD_X); |
| |
| scoped_guard(mutex, &data->mutex) { |
| ret = regmap_read(data->regmap, addr, &value); |
| if (ret) |
| return ret; |
| } |
| |
| *val = sign_extend32(value, chan->scan_type.realbits - 1); |
| |
| return IIO_VAL_INT; |
| } |
| |
| static int bmi323_get_temp_data(struct bmi323_data *data, int *val) |
| { |
| unsigned int value; |
| int ret; |
| |
| ret = bmi323_get_error_status(data); |
| if (ret) |
| return -EINVAL; |
| |
| scoped_guard(mutex, &data->mutex) { |
| ret = regmap_read(data->regmap, BMI323_TEMP_REG, &value); |
| if (ret) |
| return ret; |
| } |
| |
| *val = sign_extend32(value, 15); |
| |
| return IIO_VAL_INT; |
| } |
| |
| static int bmi323_get_odr(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, int *odr, int *uodr) |
| { |
| int ret, value, odr_raw; |
| |
| scoped_guard(mutex, &data->mutex) { |
| ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value); |
| if (ret) |
| return ret; |
| } |
| |
| odr_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_ODR_MSK, value); |
| *odr = bmi323_acc_gyro_odr[odr_raw - 1][0]; |
| *uodr = bmi323_acc_gyro_odr[odr_raw - 1][1]; |
| |
| return IIO_VAL_INT_PLUS_MICRO; |
| } |
| |
| static int bmi323_configure_power_mode(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, |
| int odr_index) |
| { |
| enum bmi323_opr_mode mode; |
| |
| if (bmi323_acc_gyro_odr[odr_index][0] > 25) |
| mode = ACC_GYRO_MODE_CONTINOUS; |
| else |
| mode = ACC_GYRO_MODE_DUTYCYCLE; |
| |
| return bmi323_set_mode(data, sensor, mode); |
| } |
| |
| static int bmi323_set_odr(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, int odr, int uodr) |
| { |
| int odr_raw, ret; |
| |
| odr_raw = ARRAY_SIZE(bmi323_acc_gyro_odr); |
| |
| while (odr_raw--) |
| if (odr == bmi323_acc_gyro_odr[odr_raw][0] && |
| uodr == bmi323_acc_gyro_odr[odr_raw][1]) |
| break; |
| if (odr_raw < 0) |
| return -EINVAL; |
| |
| ret = bmi323_configure_power_mode(data, sensor, odr_raw); |
| if (ret) |
| return -EINVAL; |
| |
| guard(mutex)(&data->mutex); |
| data->odrhz[sensor] = bmi323_acc_gyro_odr[odr_raw][0]; |
| data->odrns[sensor] = bmi323_acc_gyro_odrns[odr_raw]; |
| |
| odr_raw++; |
| |
| return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, |
| BMI323_ACC_GYRO_CONF_ODR_MSK, |
| FIELD_PREP(BMI323_ACC_GYRO_CONF_ODR_MSK, |
| odr_raw)); |
| } |
| |
| static int bmi323_get_scale(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, int *val2) |
| { |
| int ret, value, scale_raw; |
| |
| scoped_guard(mutex, &data->mutex) { |
| ret = regmap_read(data->regmap, bmi323_hw[sensor].config, |
| &value); |
| if (ret) |
| return ret; |
| } |
| |
| scale_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_SCL_MSK, value); |
| *val2 = bmi323_hw[sensor].scale_table[scale_raw][1]; |
| |
| return IIO_VAL_INT_PLUS_MICRO; |
| } |
| |
| static int bmi323_set_scale(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, int val, int val2) |
| { |
| int scale_raw; |
| |
| scale_raw = bmi323_hw[sensor].scale_table_len; |
| |
| while (scale_raw--) |
| if (val == bmi323_hw[sensor].scale_table[scale_raw][0] && |
| val2 == bmi323_hw[sensor].scale_table[scale_raw][1]) |
| break; |
| if (scale_raw < 0) |
| return -EINVAL; |
| |
| guard(mutex)(&data->mutex); |
| return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, |
| BMI323_ACC_GYRO_CONF_SCL_MSK, |
| FIELD_PREP(BMI323_ACC_GYRO_CONF_SCL_MSK, |
| scale_raw)); |
| } |
| |
| static int bmi323_read_avail(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| const int **vals, int *type, int *length, |
| long mask) |
| { |
| enum bmi323_sensor_type sensor; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| *type = IIO_VAL_INT_PLUS_MICRO; |
| *vals = (const int *)bmi323_acc_gyro_odr; |
| *length = ARRAY_SIZE(bmi323_acc_gyro_odr) * 2; |
| return IIO_AVAIL_LIST; |
| case IIO_CHAN_INFO_SCALE: |
| sensor = bmi323_iio_to_sensor(chan->type); |
| *type = IIO_VAL_INT_PLUS_MICRO; |
| *vals = (const int *)bmi323_hw[sensor].scale_table; |
| *length = bmi323_hw[sensor].scale_table_len * 2; |
| return IIO_AVAIL_LIST; |
| case IIO_CHAN_INFO_OVERSAMPLING_RATIO: |
| *type = IIO_VAL_INT; |
| *vals = (const int *)bmi323_accel_gyro_avrg; |
| *length = ARRAY_SIZE(bmi323_accel_gyro_avrg); |
| return IIO_AVAIL_LIST; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int bmi323_write_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, int val, |
| int val2, long mask) |
| { |
| struct bmi323_data *data = iio_priv(indio_dev); |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| iio_device_claim_direct_scoped(return -EBUSY, indio_dev) |
| return bmi323_set_odr(data, |
| bmi323_iio_to_sensor(chan->type), |
| val, val2); |
| unreachable(); |
| case IIO_CHAN_INFO_SCALE: |
| iio_device_claim_direct_scoped(return -EBUSY, indio_dev) |
| return bmi323_set_scale(data, |
| bmi323_iio_to_sensor(chan->type), |
| val, val2); |
| unreachable(); |
| case IIO_CHAN_INFO_OVERSAMPLING_RATIO: |
| iio_device_claim_direct_scoped(return -EBUSY, indio_dev) |
| return bmi323_set_average(data, |
| bmi323_iio_to_sensor(chan->type), |
| val); |
| unreachable(); |
| case IIO_CHAN_INFO_ENABLE: |
| return bmi323_enable_steps(data, val); |
| case IIO_CHAN_INFO_PROCESSED: { |
| guard(mutex)(&data->mutex); |
| |
| if (val || !FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, |
| data->feature_events)) |
| return -EINVAL; |
| |
| /* Clear step counter value */ |
| return bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, |
| BMI323_STEP_SC1_RST_CNT_MSK, |
| FIELD_PREP(BMI323_STEP_SC1_RST_CNT_MSK, |
| 1)); |
| } |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int bmi323_read_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, int *val, |
| int *val2, long mask) |
| { |
| struct bmi323_data *data = iio_priv(indio_dev); |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_PROCESSED: |
| return bmi323_read_steps(data, val); |
| case IIO_CHAN_INFO_RAW: |
| switch (chan->type) { |
| case IIO_ACCEL: |
| case IIO_ANGL_VEL: |
| iio_device_claim_direct_scoped(return -EBUSY, |
| indio_dev) |
| return bmi323_read_axis(data, chan, val); |
| unreachable(); |
| case IIO_TEMP: |
| return bmi323_get_temp_data(data, val); |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| return bmi323_get_odr(data, bmi323_iio_to_sensor(chan->type), |
| val, val2); |
| case IIO_CHAN_INFO_SCALE: |
| switch (chan->type) { |
| case IIO_ACCEL: |
| case IIO_ANGL_VEL: |
| *val = 0; |
| return bmi323_get_scale(data, |
| bmi323_iio_to_sensor(chan->type), |
| val2); |
| case IIO_TEMP: |
| *val = BMI323_TEMP_SCALE / MEGA; |
| *val2 = BMI323_TEMP_SCALE % MEGA; |
| return IIO_VAL_INT_PLUS_MICRO; |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_OVERSAMPLING_RATIO: |
| return bmi323_get_average(data, |
| bmi323_iio_to_sensor(chan->type), |
| val); |
| case IIO_CHAN_INFO_OFFSET: |
| switch (chan->type) { |
| case IIO_TEMP: |
| *val = BMI323_TEMP_OFFSET; |
| return IIO_VAL_INT; |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_ENABLE: |
| scoped_guard(mutex, &data->mutex) |
| *val = FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, |
| data->feature_events); |
| return IIO_VAL_INT; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static const struct iio_info bmi323_info = { |
| .read_raw = bmi323_read_raw, |
| .write_raw = bmi323_write_raw, |
| .read_avail = bmi323_read_avail, |
| .hwfifo_set_watermark = bmi323_set_watermark, |
| .write_event_config = bmi323_write_event_config, |
| .read_event_config = bmi323_read_event_config, |
| .write_event_value = bmi323_write_event_value, |
| .read_event_value = bmi323_read_event_value, |
| .event_attrs = &bmi323_event_attribute_group, |
| }; |
| |
| #define BMI323_SCAN_MASK_ACCEL_3AXIS \ |
| (BIT(BMI323_ACCEL_X) | BIT(BMI323_ACCEL_Y) | BIT(BMI323_ACCEL_Z)) |
| |
| #define BMI323_SCAN_MASK_GYRO_3AXIS \ |
| (BIT(BMI323_GYRO_X) | BIT(BMI323_GYRO_Y) | BIT(BMI323_GYRO_Z)) |
| |
| static const unsigned long bmi323_avail_scan_masks[] = { |
| /* 3-axis accel */ |
| BMI323_SCAN_MASK_ACCEL_3AXIS, |
| /* 3-axis gyro */ |
| BMI323_SCAN_MASK_GYRO_3AXIS, |
| /* 3-axis accel + 3-axis gyro */ |
| BMI323_SCAN_MASK_ACCEL_3AXIS | BMI323_SCAN_MASK_GYRO_3AXIS, |
| 0 |
| }; |
| |
| static int bmi323_int_pin_config(struct bmi323_data *data, |
| enum bmi323_irq_pin irq_pin, |
| bool active_high, bool open_drain, bool latch) |
| { |
| unsigned int mask, field_value; |
| int ret; |
| |
| ret = regmap_update_bits(data->regmap, BMI323_IO_INT_CONF_REG, |
| BMI323_IO_INT_LTCH_MSK, |
| FIELD_PREP(BMI323_IO_INT_LTCH_MSK, latch)); |
| if (ret) |
| return ret; |
| |
| ret = bmi323_update_ext_reg(data, BMI323_GEN_SET1_REG, |
| BMI323_GEN_HOLD_DUR_MSK, |
| FIELD_PREP(BMI323_GEN_HOLD_DUR_MSK, 0)); |
| if (ret) |
| return ret; |
| |
| switch (irq_pin) { |
| case BMI323_IRQ_INT1: |
| mask = BMI323_IO_INT1_LVL_OD_OP_MSK; |
| |
| field_value = FIELD_PREP(BMI323_IO_INT1_LVL_MSK, active_high) | |
| FIELD_PREP(BMI323_IO_INT1_OD_MSK, open_drain) | |
| FIELD_PREP(BMI323_IO_INT1_OP_EN_MSK, 1); |
| break; |
| case BMI323_IRQ_INT2: |
| mask = BMI323_IO_INT2_LVL_OD_OP_MSK; |
| |
| field_value = FIELD_PREP(BMI323_IO_INT2_LVL_MSK, active_high) | |
| FIELD_PREP(BMI323_IO_INT2_OD_MSK, open_drain) | |
| FIELD_PREP(BMI323_IO_INT2_OP_EN_MSK, 1); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return regmap_update_bits(data->regmap, BMI323_IO_INT_CTR_REG, mask, |
| field_value); |
| } |
| |
| static int bmi323_trigger_probe(struct bmi323_data *data, |
| struct iio_dev *indio_dev) |
| { |
| bool open_drain, active_high, latch; |
| struct fwnode_handle *fwnode; |
| enum bmi323_irq_pin irq_pin; |
| int ret, irq, irq_type; |
| struct irq_data *desc; |
| |
| fwnode = dev_fwnode(data->dev); |
| if (!fwnode) |
| return -ENODEV; |
| |
| irq = fwnode_irq_get_byname(fwnode, "INT1"); |
| if (irq > 0) { |
| irq_pin = BMI323_IRQ_INT1; |
| } else { |
| irq = fwnode_irq_get_byname(fwnode, "INT2"); |
| if (irq < 0) |
| return 0; |
| |
| irq_pin = BMI323_IRQ_INT2; |
| } |
| |
| desc = irq_get_irq_data(irq); |
| if (!desc) |
| return dev_err_probe(data->dev, -EINVAL, |
| "Could not find IRQ %d\n", irq); |
| |
| irq_type = irqd_get_trigger_type(desc); |
| switch (irq_type) { |
| case IRQF_TRIGGER_RISING: |
| latch = false; |
| active_high = true; |
| break; |
| case IRQF_TRIGGER_HIGH: |
| latch = true; |
| active_high = true; |
| break; |
| case IRQF_TRIGGER_FALLING: |
| latch = false; |
| active_high = false; |
| break; |
| case IRQF_TRIGGER_LOW: |
| latch = true; |
| active_high = false; |
| break; |
| default: |
| return dev_err_probe(data->dev, -EINVAL, |
| "Invalid interrupt type 0x%x specified\n", |
| irq_type); |
| } |
| |
| open_drain = fwnode_property_read_bool(fwnode, "drive-open-drain"); |
| |
| ret = bmi323_int_pin_config(data, irq_pin, active_high, open_drain, |
| latch); |
| if (ret) |
| return dev_err_probe(data->dev, ret, |
| "Failed to configure irq line\n"); |
| |
| data->trig = devm_iio_trigger_alloc(data->dev, "%s-trig-%d", |
| indio_dev->name, irq_pin); |
| if (!data->trig) |
| return -ENOMEM; |
| |
| data->trig->ops = &bmi323_trigger_ops; |
| iio_trigger_set_drvdata(data->trig, data); |
| |
| ret = devm_request_threaded_irq(data->dev, irq, NULL, |
| bmi323_irq_thread_handler, |
| IRQF_ONESHOT, "bmi323-int", indio_dev); |
| if (ret) |
| return dev_err_probe(data->dev, ret, "Failed to request IRQ\n"); |
| |
| ret = devm_iio_trigger_register(data->dev, data->trig); |
| if (ret) |
| return dev_err_probe(data->dev, ret, |
| "Trigger registration failed\n"); |
| |
| data->irq_pin = irq_pin; |
| |
| return 0; |
| } |
| |
| static int bmi323_feature_engine_enable(struct bmi323_data *data, bool en) |
| { |
| unsigned int feature_status; |
| int ret; |
| |
| if (!en) |
| return regmap_write(data->regmap, BMI323_FEAT_CTRL_REG, 0); |
| |
| ret = regmap_write(data->regmap, BMI323_FEAT_IO2_REG, 0x012c); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG, |
| BMI323_FEAT_IO_STATUS_MSK); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(data->regmap, BMI323_FEAT_CTRL_REG, |
| BMI323_FEAT_ENG_EN_MSK); |
| if (ret) |
| return ret; |
| |
| /* |
| * It takes around 4 msec to enable the Feature engine, so check |
| * the status of the feature engine every 2 msec for a maximum |
| * of 5 trials. |
| */ |
| ret = regmap_read_poll_timeout(data->regmap, BMI323_FEAT_IO1_REG, |
| feature_status, |
| FIELD_GET(BMI323_FEAT_IO1_ERR_MSK, |
| feature_status) == 1, |
| BMI323_FEAT_ENG_POLL, |
| BMI323_FEAT_ENG_TIMEOUT); |
| if (ret) |
| return dev_err_probe(data->dev, -EINVAL, |
| "Failed to enable feature engine\n"); |
| |
| return 0; |
| } |
| |
| static void bmi323_disable(void *data_ptr) |
| { |
| struct bmi323_data *data = data_ptr; |
| |
| bmi323_set_mode(data, BMI323_ACCEL, ACC_GYRO_MODE_DISABLE); |
| bmi323_set_mode(data, BMI323_GYRO, ACC_GYRO_MODE_DISABLE); |
| |
| /* |
| * Place the peripheral in its lowest power consuming state. |
| */ |
| regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL); |
| } |
| |
| static int bmi323_set_bw(struct bmi323_data *data, |
| enum bmi323_sensor_type sensor, enum bmi323_3db_bw bw) |
| { |
| return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, |
| BMI323_ACC_GYRO_CONF_BW_MSK, |
| FIELD_PREP(BMI323_ACC_GYRO_CONF_BW_MSK, bw)); |
| } |
| |
| static int bmi323_init(struct bmi323_data *data) |
| { |
| int ret, val; |
| |
| /* |
| * Perform soft reset to make sure the device is in a known state after |
| * start up. A delay of 1.5 ms is required after reset. |
| * See datasheet section 5.17 "Soft Reset". |
| */ |
| ret = regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL); |
| if (ret) |
| return ret; |
| |
| usleep_range(1500, 2000); |
| |
| /* |
| * Dummy read is required to enable SPI interface after reset. |
| * See datasheet section 7.2.1 "Protocol Selection". |
| */ |
| regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val); |
| |
| ret = regmap_read(data->regmap, BMI323_STATUS_REG, &val); |
| if (ret) |
| return ret; |
| |
| if (!FIELD_GET(BMI323_STATUS_POR_MSK, val)) |
| return dev_err_probe(data->dev, -EINVAL, |
| "Sensor initialization error\n"); |
| |
| ret = regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val); |
| if (ret) |
| return ret; |
| |
| if (FIELD_GET(BMI323_CHIP_ID_MSK, val) != BMI323_CHIP_ID_VAL) |
| return dev_err_probe(data->dev, -EINVAL, "Chip ID mismatch\n"); |
| |
| ret = bmi323_feature_engine_enable(data, true); |
| if (ret) |
| return ret; |
| |
| ret = regmap_read(data->regmap, BMI323_ERR_REG, &val); |
| if (ret) |
| return ret; |
| |
| if (val) |
| return dev_err_probe(data->dev, -EINVAL, |
| "Sensor power error = 0x%x\n", val); |
| |
| return 0; |
| } |
| |
| static int bmi323_init_reset(struct bmi323_data *data) |
| { |
| int ret; |
| |
| /* |
| * Set the Bandwidth coefficient which defines the 3 dB cutoff |
| * frequency in relation to the ODR. |
| */ |
| ret = bmi323_set_bw(data, BMI323_ACCEL, BMI323_BW_ODR_BY_2); |
| if (ret) |
| return ret; |
| |
| ret = bmi323_set_bw(data, BMI323_GYRO, BMI323_BW_ODR_BY_2); |
| if (ret) |
| return ret; |
| |
| ret = bmi323_set_odr(data, BMI323_ACCEL, 25, 0); |
| if (ret) |
| return ret; |
| |
| ret = bmi323_set_odr(data, BMI323_GYRO, 25, 0); |
| if (ret) |
| return ret; |
| |
| return devm_add_action_or_reset(data->dev, bmi323_disable, data); |
| } |
| |
| int bmi323_core_probe(struct device *dev) |
| { |
| static const char * const regulator_names[] = { "vdd", "vddio" }; |
| struct iio_dev *indio_dev; |
| struct bmi323_data *data; |
| struct regmap *regmap; |
| int ret; |
| |
| regmap = dev_get_regmap(dev, NULL); |
| if (!regmap) |
| return dev_err_probe(dev, -ENODEV, "Failed to get regmap\n"); |
| |
| indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); |
| if (!indio_dev) |
| return dev_err_probe(dev, -ENOMEM, |
| "Failed to allocate device\n"); |
| |
| ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), |
| regulator_names); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to enable regulators\n"); |
| |
| data = iio_priv(indio_dev); |
| data->dev = dev; |
| data->regmap = regmap; |
| data->irq_pin = BMI323_IRQ_DISABLED; |
| data->state = BMI323_IDLE; |
| mutex_init(&data->mutex); |
| |
| ret = bmi323_init(data); |
| if (ret) |
| return -EINVAL; |
| |
| ret = bmi323_init_reset(data); |
| if (ret) |
| return -EINVAL; |
| |
| if (!iio_read_acpi_mount_matrix(dev, &data->orientation, "ROTM")) { |
| ret = iio_read_mount_matrix(dev, &data->orientation); |
| if (ret) |
| return ret; |
| } |
| |
| indio_dev->name = "bmi323-imu"; |
| indio_dev->info = &bmi323_info; |
| indio_dev->channels = bmi323_channels; |
| indio_dev->num_channels = ARRAY_SIZE(bmi323_channels); |
| indio_dev->available_scan_masks = bmi323_avail_scan_masks; |
| indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; |
| dev_set_drvdata(data->dev, indio_dev); |
| |
| ret = bmi323_trigger_probe(data, indio_dev); |
| if (ret) |
| return -EINVAL; |
| |
| ret = devm_iio_triggered_buffer_setup_ext(data->dev, indio_dev, |
| &iio_pollfunc_store_time, |
| bmi323_trigger_handler, |
| IIO_BUFFER_DIRECTION_IN, |
| &bmi323_buffer_ops, |
| bmi323_fifo_attributes); |
| if (ret) |
| return dev_err_probe(data->dev, ret, |
| "Failed to setup trigger buffer\n"); |
| |
| ret = devm_iio_device_register(data->dev, indio_dev); |
| if (ret) |
| return dev_err_probe(data->dev, ret, |
| "Unable to register iio device\n"); |
| |
| return bmi323_fifo_disable(data); |
| } |
| EXPORT_SYMBOL_NS_GPL(bmi323_core_probe, IIO_BMI323); |
| |
| static int bmi323_core_runtime_suspend(struct device *dev) |
| { |
| struct iio_dev *indio_dev = dev_get_drvdata(dev); |
| struct bmi323_data *data = iio_priv(indio_dev); |
| struct bmi323_regs_runtime_pm *savestate = &data->runtime_pm_status; |
| int ret; |
| |
| guard(mutex)(&data->mutex); |
| |
| ret = iio_device_suspend_triggering(indio_dev); |
| if (ret) |
| return ret; |
| |
| /* Save registers meant to be restored by resume pm callback. */ |
| for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_reg_savestate); i++) { |
| ret = regmap_read(data->regmap, bmi323_reg_savestate[i], |
| &savestate->reg_settings[i]); |
| if (ret) { |
| dev_err(data->dev, |
| "Error reading bmi323 reg 0x%x: %d\n", |
| bmi323_reg_savestate[i], ret); |
| return ret; |
| } |
| } |
| |
| for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_ext_reg_savestate); i++) { |
| ret = bmi323_read_ext_reg(data, bmi323_ext_reg_savestate[i], |
| &savestate->ext_reg_settings[i]); |
| if (ret) { |
| dev_err(data->dev, |
| "Error reading bmi323 external reg 0x%x: %d\n", |
| bmi323_ext_reg_savestate[i], ret); |
| return ret; |
| } |
| } |
| |
| /* Perform soft reset to place the device in its lowest power state. */ |
| ret = regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int bmi323_core_runtime_resume(struct device *dev) |
| { |
| struct iio_dev *indio_dev = dev_get_drvdata(dev); |
| struct bmi323_data *data = iio_priv(indio_dev); |
| struct bmi323_regs_runtime_pm *savestate = &data->runtime_pm_status; |
| unsigned int val; |
| int ret; |
| |
| guard(mutex)(&data->mutex); |
| |
| /* |
| * Perform the device power-on and initial setup once again |
| * after being reset in the lower power state by runtime-pm. |
| */ |
| ret = bmi323_init(data); |
| if (ret) { |
| dev_err(data->dev, "Device power-on and init failed: %d", ret); |
| return ret; |
| } |
| |
| /* Register must be cleared before changing an active config */ |
| ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, 0); |
| if (ret) { |
| dev_err(data->dev, "Error stopping feature engine\n"); |
| return ret; |
| } |
| |
| for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_ext_reg_savestate); i++) { |
| ret = bmi323_write_ext_reg(data, bmi323_ext_reg_savestate[i], |
| savestate->ext_reg_settings[i]); |
| if (ret) { |
| dev_err(data->dev, |
| "Error writing bmi323 external reg 0x%x: %d\n", |
| bmi323_ext_reg_savestate[i], ret); |
| return ret; |
| } |
| } |
| |
| for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_reg_savestate); i++) { |
| ret = regmap_write(data->regmap, bmi323_reg_savestate[i], |
| savestate->reg_settings[i]); |
| if (ret) { |
| dev_err(data->dev, |
| "Error writing bmi323 reg 0x%x: %d\n", |
| bmi323_reg_savestate[i], ret); |
| return ret; |
| } |
| } |
| |
| /* |
| * Clear old FIFO samples that might be generated before suspend |
| * or generated from a peripheral state not equal to the saved one. |
| */ |
| if (data->state == BMI323_BUFFER_FIFO) { |
| ret = regmap_write(data->regmap, BMI323_FIFO_CTRL_REG, |
| BMI323_FIFO_FLUSH_MSK); |
| if (ret) { |
| dev_err(data->dev, "Error flushing FIFO buffer: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| ret = regmap_read(data->regmap, BMI323_ERR_REG, &val); |
| if (ret) { |
| dev_err(data->dev, |
| "Error reading bmi323 error register: %d\n", ret); |
| return ret; |
| } |
| |
| if (val) { |
| dev_err(data->dev, |
| "Sensor power error in PM = 0x%x\n", val); |
| return -EINVAL; |
| } |
| |
| return iio_device_resume_triggering(indio_dev); |
| } |
| |
| const struct dev_pm_ops bmi323_core_pm_ops = { |
| RUNTIME_PM_OPS(bmi323_core_runtime_suspend, |
| bmi323_core_runtime_resume, NULL) |
| }; |
| EXPORT_SYMBOL_NS_GPL(bmi323_core_pm_ops, IIO_BMI323); |
| |
| MODULE_DESCRIPTION("Bosch BMI323 IMU driver"); |
| MODULE_AUTHOR("Jagath Jog J <jagathjog1996@gmail.com>"); |
| MODULE_LICENSE("GPL"); |