| /* |
| * Copyright (C) 2012 Invensense, Inc. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/err.h> |
| #include <linux/delay.h> |
| #include <linux/sysfs.h> |
| #include <linux/jiffies.h> |
| #include <linux/irq.h> |
| #include <linux/interrupt.h> |
| #include <linux/kfifo.h> |
| #include <linux/poll.h> |
| #include "inv_mpu_iio.h" |
| |
| int inv_reset_fifo(struct iio_dev *indio_dev) |
| { |
| int result; |
| u8 d; |
| struct inv_mpu6050_state *st = iio_priv(indio_dev); |
| |
| /* disable interrupt */ |
| result = inv_mpu6050_write_reg(st, st->reg->int_enable, 0); |
| if (result) { |
| dev_err(&st->client->dev, "int_enable failed %d\n", result); |
| return result; |
| } |
| /* disable the sensor output to FIFO */ |
| result = inv_mpu6050_write_reg(st, st->reg->fifo_en, 0); |
| if (result) |
| goto reset_fifo_fail; |
| /* disable fifo reading */ |
| result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, 0); |
| if (result) |
| goto reset_fifo_fail; |
| |
| /* reset FIFO*/ |
| result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, |
| INV_MPU6050_BIT_FIFO_RST); |
| if (result) |
| goto reset_fifo_fail; |
| /* enable interrupt */ |
| if (st->chip_config.accl_fifo_enable || |
| st->chip_config.gyro_fifo_enable) { |
| result = inv_mpu6050_write_reg(st, st->reg->int_enable, |
| INV_MPU6050_BIT_DATA_RDY_EN); |
| if (result) |
| return result; |
| } |
| /* enable FIFO reading and I2C master interface*/ |
| result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, |
| INV_MPU6050_BIT_FIFO_EN); |
| if (result) |
| goto reset_fifo_fail; |
| /* enable sensor output to FIFO */ |
| d = 0; |
| if (st->chip_config.gyro_fifo_enable) |
| d |= INV_MPU6050_BITS_GYRO_OUT; |
| if (st->chip_config.accl_fifo_enable) |
| d |= INV_MPU6050_BIT_ACCEL_OUT; |
| result = inv_mpu6050_write_reg(st, st->reg->fifo_en, d); |
| if (result) |
| goto reset_fifo_fail; |
| |
| return 0; |
| |
| reset_fifo_fail: |
| dev_err(&st->client->dev, "reset fifo failed %d\n", result); |
| result = inv_mpu6050_write_reg(st, st->reg->int_enable, |
| INV_MPU6050_BIT_DATA_RDY_EN); |
| |
| return result; |
| } |
| |
| static void inv_clear_kfifo(struct inv_mpu6050_state *st) |
| { |
| unsigned long flags; |
| |
| /* take the spin lock sem to avoid interrupt kick in */ |
| spin_lock_irqsave(&st->time_stamp_lock, flags); |
| kfifo_reset(&st->timestamps); |
| spin_unlock_irqrestore(&st->time_stamp_lock, flags); |
| } |
| |
| /** |
| * inv_mpu6050_irq_handler() - Cache a timestamp at each data ready interrupt. |
| */ |
| irqreturn_t inv_mpu6050_irq_handler(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); |
| s64 timestamp; |
| |
| timestamp = iio_get_time_ns(); |
| kfifo_in_spinlocked(&st->timestamps, ×tamp, 1, |
| &st->time_stamp_lock); |
| |
| return IRQ_WAKE_THREAD; |
| } |
| |
| /** |
| * 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; |
| u8 data[INV_MPU6050_OUTPUT_DATA_SIZE]; |
| u16 fifo_count; |
| s64 timestamp; |
| u64 *tmp; |
| |
| mutex_lock(&indio_dev->mlock); |
| if (!(st->chip_config.accl_fifo_enable | |
| st->chip_config.gyro_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; |
| |
| /* |
| * read fifo_count register to know how many bytes inside FIFO |
| * right now |
| */ |
| result = i2c_smbus_read_i2c_block_data(st->client, |
| st->reg->fifo_count_h, |
| INV_MPU6050_FIFO_COUNT_BYTE, data); |
| if (result != INV_MPU6050_FIFO_COUNT_BYTE) |
| goto end_session; |
| fifo_count = be16_to_cpup((__be16 *)(&data[0])); |
| if (fifo_count < bytes_per_datum) |
| goto end_session; |
| /* fifo count can't be odd number, if it is odd, reset fifo*/ |
| if (fifo_count & 1) |
| goto flush_fifo; |
| if (fifo_count > INV_MPU6050_FIFO_THRESHOLD) |
| goto flush_fifo; |
| /* Timestamp mismatch. */ |
| if (kfifo_len(&st->timestamps) > |
| fifo_count / bytes_per_datum + INV_MPU6050_TIME_STAMP_TOR) |
| goto flush_fifo; |
| while (fifo_count >= bytes_per_datum) { |
| result = i2c_smbus_read_i2c_block_data(st->client, |
| st->reg->fifo_r_w, |
| bytes_per_datum, data); |
| if (result != bytes_per_datum) |
| goto flush_fifo; |
| |
| result = kfifo_out(&st->timestamps, ×tamp, 1); |
| /* when there is no timestamp, put timestamp as 0 */ |
| if (0 == result) |
| timestamp = 0; |
| |
| tmp = (u64 *)data; |
| tmp[DIV_ROUND_UP(bytes_per_datum, 8)] = timestamp; |
| result = iio_push_to_buffers(indio_dev, data); |
| if (result) |
| goto flush_fifo; |
| fifo_count -= bytes_per_datum; |
| } |
| |
| end_session: |
| mutex_unlock(&indio_dev->mlock); |
| iio_trigger_notify_done(indio_dev->trig); |
| |
| return IRQ_HANDLED; |
| |
| flush_fifo: |
| /* Flush HW and SW FIFOs. */ |
| inv_reset_fifo(indio_dev); |
| inv_clear_kfifo(st); |
| mutex_unlock(&indio_dev->mlock); |
| iio_trigger_notify_done(indio_dev->trig); |
| |
| return IRQ_HANDLED; |
| } |