// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2012 Invensense, Inc. */ #include #include #include #include #include #include #include #include #include #include #include "inv_mpu_iio.h" /** * inv_mpu6050_update_period() - Update chip internal period estimation * * @st: driver state * @timestamp: the interrupt timestamp * @nb: number of data set in the fifo * * This function uses interrupt timestamps to estimate the chip period and * to choose the data timestamp to come. */ static void inv_mpu6050_update_period(struct inv_mpu6050_state *st, s64 timestamp, size_t nb) { /* Period boundaries for accepting timestamp */ const s64 period_min = (NSEC_PER_MSEC * (100 - INV_MPU6050_TS_PERIOD_JITTER)) / 100; const s64 period_max = (NSEC_PER_MSEC * (100 + INV_MPU6050_TS_PERIOD_JITTER)) / 100; const s32 divider = INV_MPU6050_FREQ_DIVIDER(st); s64 delta, interval; bool use_it_timestamp = false; if (st->it_timestamp == 0) { /* not initialized, forced to use it_timestamp */ use_it_timestamp = true; } else if (nb == 1) { /* * Validate the use of it timestamp by checking if interrupt * has been delayed. * nb > 1 means interrupt was delayed for more than 1 sample, * so it's obviously not good. * Compute the chip period between 2 interrupts for validating. */ delta = div_s64(timestamp - st->it_timestamp, divider); if (delta > period_min && delta < period_max) { /* update chip period and use it timestamp */ st->chip_period = (st->chip_period + delta) / 2; use_it_timestamp = true; } } if (use_it_timestamp) { /* * Manage case of multiple samples in the fifo (nb > 1): * compute timestamp corresponding to the first sample using * estimated chip period. */ interval = (nb - 1) * st->chip_period * divider; st->data_timestamp = timestamp - interval; } /* save it timestamp */ st->it_timestamp = timestamp; } /** * inv_mpu6050_get_timestamp() - Return the current data timestamp * * @st: driver state * @return: current data timestamp * * This function returns the current data timestamp and prepares for next one. */ static s64 inv_mpu6050_get_timestamp(struct inv_mpu6050_state *st) { s64 ts; /* return current data timestamp and increment */ ts = st->data_timestamp; st->data_timestamp += st->chip_period * INV_MPU6050_FREQ_DIVIDER(st); return ts; } static int inv_reset_fifo(struct iio_dev *indio_dev) { int result; struct inv_mpu6050_state *st = iio_priv(indio_dev); /* disable fifo and reenable it */ inv_mpu6050_prepare_fifo(st, false); result = inv_mpu6050_prepare_fifo(st, true); if (result) goto reset_fifo_fail; return 0; reset_fifo_fail: dev_err(regmap_get_device(st->map), "reset fifo failed %d\n", result); result = regmap_write(st->map, st->reg->int_enable, INV_MPU6050_BIT_DATA_RDY_EN); return result; } /* * inv_mpu6050_read_fifo() - Transfer data from hardware FIFO to KFIFO. */ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) { struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct inv_mpu6050_state *st = iio_priv(indio_dev); size_t bytes_per_datum; int result; u16 fifo_count; s64 timestamp; int int_status; size_t i, nb; mutex_lock(&st->lock); /* ack interrupt and check status */ result = regmap_read(st->map, st->reg->int_status, &int_status); if (result) { dev_err(regmap_get_device(st->map), "failed to ack interrupt\n"); goto flush_fifo; } if (!(int_status & INV_MPU6050_BIT_RAW_DATA_RDY_INT)) goto end_session; if (!(st->chip_config.accl_fifo_enable | st->chip_config.gyro_fifo_enable | st->chip_config.magn_fifo_enable)) goto end_session; bytes_per_datum = 0; if (st->chip_config.accl_fifo_enable) bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; if (st->chip_config.gyro_fifo_enable) bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; if (st->chip_config.temp_fifo_enable) bytes_per_datum += INV_MPU6050_BYTES_PER_TEMP_SENSOR; if (st->chip_config.magn_fifo_enable) bytes_per_datum += INV_MPU9X50_BYTES_MAGN; /* * read fifo_count register to know how many bytes are inside the FIFO * right now */ result = regmap_bulk_read(st->map, st->reg->fifo_count_h, st->data, INV_MPU6050_FIFO_COUNT_BYTE); if (result) goto end_session; fifo_count = be16_to_cpup((__be16 *)&st->data[0]); /* * Handle fifo overflow by resetting fifo. * Reset if there is only 3 data set free remaining to mitigate * possible delay between reading fifo count and fifo data. */ nb = 3 * bytes_per_datum; if (fifo_count >= st->hw->fifo_size - nb) { dev_warn(regmap_get_device(st->map), "fifo overflow reset\n"); goto flush_fifo; } /* compute and process all complete datum */ nb = fifo_count / bytes_per_datum; inv_mpu6050_update_period(st, pf->timestamp, nb); for (i = 0; i < nb; ++i) { result = regmap_noinc_read(st->map, st->reg->fifo_r_w, st->data, bytes_per_datum); if (result) goto flush_fifo; /* skip first samples if needed */ if (st->skip_samples) { st->skip_samples--; continue; } timestamp = inv_mpu6050_get_timestamp(st); iio_push_to_buffers_with_timestamp(indio_dev, st->data, timestamp); } end_session: mutex_unlock(&st->lock); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; flush_fifo: /* Flush HW and SW FIFOs. */ inv_reset_fifo(indio_dev); mutex_unlock(&st->lock); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; }