summaryrefslogtreecommitdiff
path: root/drivers/iio/imu/inv_mpu/inv_mpu_ring.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/iio/imu/inv_mpu/inv_mpu_ring.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/iio/imu/inv_mpu/inv_mpu_ring.c')
-rw-r--r--drivers/iio/imu/inv_mpu/inv_mpu_ring.c1048
1 files changed, 1048 insertions, 0 deletions
diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu/inv_mpu_ring.c
new file mode 100644
index 00000000..b578b1c5
--- /dev/null
+++ b/drivers/iio/imu/inv_mpu/inv_mpu_ring.c
@@ -0,0 +1,1048 @@
+/*
+* 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.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_mpu_ring.c
+ * @brief A sysfs device driver for Invensense gyroscopes.
+ * @details This file is part of inv mpu iio driver code
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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 <linux/miscdevice.h>
+#include <linux/iio/iio.h>
+#include <linux/gpio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/sysfs.h>
+
+#include "inv_mpu_iio.h"
+
+/**
+ * reset_fifo_mpu3050() - Reset FIFO related registers
+ * @st: Device driver instance.
+ */
+static int reset_fifo_mpu3050(struct iio_dev *indio_dev)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ u8 val, user_ctrl;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ reg = &st->reg;
+
+ /* disable interrupt */
+ result = inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config);
+ if (result)
+ return result;
+ /* disable the sensor output to FIFO */
+ result = inv_i2c_single_write(st, reg->fifo_en, 0);
+ if (result)
+ goto reset_fifo_fail;
+ result = inv_i2c_read(st, reg->user_ctrl, 1, &user_ctrl);
+ if (result)
+ goto reset_fifo_fail;
+ /* disable fifo reading */
+ user_ctrl &= ~BIT_FIFO_EN;
+ st->chip_config.has_footer = 0;
+ /* reset fifo */
+ val = (BIT_3050_FIFO_RST | user_ctrl);
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+ st->last_isr_time = get_time_ns();
+ if (st->chip_config.dmp_on) {
+ /* enable interrupt when DMP is done */
+ result = inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config | BIT_DMP_INT_EN);
+ if (result)
+ return result;
+
+ result = inv_i2c_single_write(st, reg->user_ctrl,
+ BIT_FIFO_EN|user_ctrl);
+ if (result)
+ return result;
+ } else {
+ /* enable interrupt */
+ if (st->chip_config.accl_fifo_enable ||
+ st->chip_config.gyro_fifo_enable) {
+ result = inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config | BIT_DATA_RDY_EN);
+ if (result)
+ return result;
+ }
+ /* enable FIFO reading and I2C master interface*/
+ result = inv_i2c_single_write(st, reg->user_ctrl,
+ BIT_FIFO_EN | user_ctrl);
+ if (result)
+ return result;
+ /* enable sensor output to FIFO and FIFO footer*/
+ val = 1;
+ if (st->chip_config.accl_fifo_enable)
+ val |= BITS_3050_ACCL_OUT;
+ if (st->chip_config.gyro_fifo_enable)
+ val |= BITS_GYRO_OUT;
+ result = inv_i2c_single_write(st, reg->fifo_en, val);
+ if (result)
+ return result;
+ }
+
+ return 0;
+reset_fifo_fail:
+ if (st->chip_config.dmp_on)
+ val = BIT_DMP_INT_EN;
+ else
+ val = BIT_DATA_RDY_EN;
+ inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config | val);
+ pr_err("reset fifo failed\n");
+
+ return result;
+}
+
+/**
+ * inv_lpa_mode() - store current low power mode settings
+ */
+static int inv_lpa_mode(struct inv_mpu_iio_s *st, int lpa_mode)
+{
+ unsigned long result;
+ u8 d;
+ struct inv_reg_map_s *reg;
+
+ reg = &st->reg;
+ result = inv_i2c_read(st, reg->pwr_mgmt_1, 1, &d);
+ if (result)
+ return result;
+ if (lpa_mode)
+ d |= BIT_CYCLE;
+ else
+ d &= ~BIT_CYCLE;
+
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, d);
+ if (result)
+ return result;
+ if (INV_MPU6500 == st->chip_type) {
+ if (lpa_mode)
+ result = inv_i2c_single_write(st,
+ REG_6500_ACCEL_CONFIG2,
+ BIT_ACCEL_FCHOCIE_B);
+ else
+ result = inv_i2c_single_write(st, 0,
+ BIT_ACCEL_FCHOCIE_B);
+
+ if (result)
+ return result;
+ }
+
+ return 0;
+}
+
+/**
+ * reset_fifo_itg() - Reset FIFO related registers.
+ * @st: Device driver instance.
+ */
+static int reset_fifo_itg(struct iio_dev *indio_dev)
+{
+ struct inv_reg_map_s *reg;
+ int result, data;
+ u8 val, int_word;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ reg = &st->reg;
+
+ if (st->chip_config.lpa_mode) {
+ result = inv_lpa_mode(st, 0);
+ if (result) {
+ pr_err("reset lpa mode failed\n");
+ return result;
+ }
+ }
+ /* disable interrupt */
+ result = inv_i2c_single_write(st, reg->int_enable, 0);
+ if (result) {
+ pr_err("int_enable write failed\n");
+ return result;
+ }
+ /* disable the sensor output to FIFO */
+ result = inv_i2c_single_write(st, reg->fifo_en, 0);
+ if (result)
+ goto reset_fifo_fail;
+ /* disable fifo reading */
+ result = inv_i2c_single_write(st, reg->user_ctrl, 0);
+ if (result)
+ goto reset_fifo_fail;
+ int_word = 0;
+
+ /* MPU6500's BIT_6500_WOM_EN is the same as BIT_MOT_EN */
+ if (st->mot_int.mot_on)
+ int_word |= BIT_MOT_EN;
+
+ if (st->chip_config.dmp_on) {
+ val = (BIT_FIFO_RST | BIT_DMP_RST);
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+ st->last_isr_time = get_time_ns();
+ if (st->chip_config.dmp_int_on) {
+ int_word |= BIT_DMP_INT_EN;
+ result = inv_i2c_single_write(st, reg->int_enable,
+ int_word);
+ if (result)
+ return result;
+ }
+ val = (BIT_DMP_EN | BIT_FIFO_EN);
+ if (st->chip_config.compass_enable &
+ (!st->chip_config.dmp_event_int_on))
+ val |= BIT_I2C_MST_EN;
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+
+ if (st->chip_config.compass_enable) {
+ /* I2C_MST_DLY is set according to sample rate,
+ slow down the power*/
+ data = max(COMPASS_RATE_SCALE *
+ st->chip_config.fifo_rate / ONE_K_HZ,
+ st->chip_config.fifo_rate /
+ st->chip_config.dmp_output_rate);
+ if (data > 0)
+ data -= 1;
+ result = inv_i2c_single_write(st, REG_I2C_SLV4_CTRL,
+ data);
+ if (result)
+ return result;
+ }
+ val = 0;
+ if (st->chip_config.accl_fifo_enable)
+ val |= INV_ACCL_MASK;
+ if (st->chip_config.gyro_fifo_enable)
+ val |= INV_GYRO_MASK;
+ result = inv_send_sensor_data(st, val);
+ if (result)
+ return result;
+ if (st->chip_config.display_orient_on || st->chip_config.tap_on)
+ result = inv_send_interrupt_word(st, true);
+ else
+ result = inv_send_interrupt_word(st, false);
+ } else {
+ /* reset FIFO and possibly reset I2C*/
+ val = BIT_FIFO_RST;
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+ st->last_isr_time = get_time_ns();
+ /* enable interrupt */
+ if (st->chip_config.accl_fifo_enable ||
+ st->chip_config.gyro_fifo_enable ||
+ st->chip_config.compass_enable) {
+ int_word |= BIT_DATA_RDY_EN;
+ }
+ result = inv_i2c_single_write(st, reg->int_enable, int_word);
+ if (result)
+ return result;
+ /* enable FIFO reading and I2C master interface*/
+ val = BIT_FIFO_EN;
+ if (st->chip_config.compass_enable)
+ val |= BIT_I2C_MST_EN;
+ result = inv_i2c_single_write(st, reg->user_ctrl, val);
+ if (result)
+ goto reset_fifo_fail;
+ if (st->chip_config.compass_enable) {
+ /* I2C_MST_DLY is set according to sample rate,
+ slow down the power*/
+ data = COMPASS_RATE_SCALE *
+ st->chip_config.fifo_rate / ONE_K_HZ;
+ if (data > 0)
+ data -= 1;
+ result = inv_i2c_single_write(st, REG_I2C_SLV4_CTRL,
+ data);
+ if (result)
+ return result;
+ }
+ /* enable sensor output to FIFO */
+ val = 0;
+ if (st->chip_config.gyro_fifo_enable)
+ val |= BITS_GYRO_OUT;
+ if (st->chip_config.accl_fifo_enable)
+ val |= BIT_ACCEL_OUT;
+ result = inv_i2c_single_write(st, reg->fifo_en, val);
+ if (result)
+ goto reset_fifo_fail;
+ }
+ st->chip_config.normal_compass_measure = 0;
+ result = inv_lpa_mode(st, st->chip_config.lpa_mode);
+ if (result)
+ goto reset_fifo_fail;
+
+ return 0;
+
+reset_fifo_fail:
+ if (st->chip_config.dmp_on)
+ val = BIT_DMP_INT_EN;
+ else
+ val = BIT_DATA_RDY_EN;
+ inv_i2c_single_write(st, reg->int_enable, val);
+ pr_err("reset fifo failed\n");
+
+ return result;
+}
+
+/**
+ * inv_clear_kfifo() - clear time stamp fifo
+ * @st: Device driver instance.
+ */
+static void inv_clear_kfifo(struct inv_mpu_iio_s *st)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&st->time_stamp_lock, flags);
+ kfifo_reset(&st->timestamps);
+ spin_unlock_irqrestore(&st->time_stamp_lock, flags);
+}
+
+/**
+ * inv_reset_fifo() - Reset FIFO related registers.
+ * @st: Device driver instance.
+ */
+static int inv_reset_fifo(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+
+ inv_clear_kfifo(st);
+ if (INV_MPU3050 == st->chip_type)
+ return reset_fifo_mpu3050(indio_dev);
+ else
+ return reset_fifo_itg(indio_dev);
+}
+
+/**
+ * set_inv_enable() - Reset FIFO related registers.
+ * This also powers on the chip if needed.
+ * @st: Device driver instance.
+ * @fifo_enable: enable/disable
+ */
+int set_inv_enable(struct iio_dev *indio_dev,
+ bool enable) {
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct inv_reg_map_s *reg;
+ int result;
+
+ if (st->chip_config.is_asleep)
+ return -EINVAL;
+ reg = &st->reg;
+ if (enable) {
+ result = inv_reset_fifo(indio_dev);
+ if (result)
+ return result;
+ } else {
+ if ((INV_MPU3050 != st->chip_type)
+ && st->chip_config.lpa_mode) {
+ result = inv_lpa_mode(st, 0);
+ if (result)
+ return result;
+ }
+ result = inv_i2c_single_write(st, reg->fifo_en, 0);
+ if (result)
+ return result;
+ /* disable fifo reading */
+ if (INV_MPU3050 != st->chip_type) {
+ result = inv_i2c_single_write(st, reg->int_enable, 0);
+ if (result)
+ return result;
+ result = inv_i2c_single_write(st, reg->user_ctrl, 0);
+ } else {
+ result = inv_i2c_single_write(st, reg->int_enable,
+ st->plat_data.int_config);
+ }
+ if (result)
+ return result;
+ }
+ st->chip_config.enable = !!enable;
+
+ return 0;
+}
+
+/**
+ * inv_irq_handler() - Cache a timestamp at each data ready interrupt.
+ */
+static irqreturn_t inv_irq_handler(int irq, void *dev_id)
+{
+ struct inv_mpu_iio_s *st;
+ u64 timestamp;
+ int catch_up;
+ u64 time_since_last_irq;
+
+ st = (struct inv_mpu_iio_s *)dev_id;
+ timestamp = get_time_ns();
+ time_since_last_irq = timestamp - st->last_isr_time;
+ spin_lock(&st->time_stamp_lock);
+ catch_up = 0;
+ while ((time_since_last_irq > st->irq_dur_ns * 2) &&
+ (catch_up < MAX_CATCH_UP) &&
+ (!st->chip_config.lpa_mode) &&
+ (!st->chip_config.dmp_on)) {
+ st->last_isr_time += st->irq_dur_ns;
+ kfifo_in(&st->timestamps,
+ &st->last_isr_time, 1);
+ time_since_last_irq = timestamp - st->last_isr_time;
+ catch_up++;
+ }
+ kfifo_in(&st->timestamps, &timestamp, 1);
+ st->last_isr_time = timestamp;
+ spin_unlock(&st->time_stamp_lock);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int put_scan_to_buf(struct iio_dev *indio_dev, u8 *d,
+ short *s, int scan_index, int d_ind) {
+ struct iio_buffer *ring = indio_dev->buffer;
+ int st;
+ int i;
+ for (i = 0; i < 3; i++) {
+ st = iio_scan_mask_query(indio_dev, ring, scan_index + i);
+ if (st) {
+ memcpy(&d[d_ind], &s[i], sizeof(s[i]));
+ d_ind += sizeof(s[i]);
+ }
+ }
+ return d_ind;
+}
+
+static int put_scan_to_buf_q(struct iio_dev *indio_dev, u8 *d,
+ int *s, int scan_index, int d_ind) {
+ struct iio_buffer *ring = indio_dev->buffer;
+ int st;
+ int i;
+ for (i = 0; i < 4; i++) {
+ st = iio_scan_mask_query(indio_dev, ring, scan_index + i);
+ if (st) {
+ memcpy(&d[d_ind], &s[i], sizeof(s[i]));
+ d_ind += sizeof(s[i]);
+ }
+ }
+ return d_ind;
+}
+
+static void inv_report_data_3050(struct iio_dev *indio_dev, s64 t,
+ int has_footer, u8 *data)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int ind, i, d_ind;
+ struct inv_chip_config_s *conf;
+ short g[THREE_AXIS], a[THREE_AXIS];
+ s64 buf[8];
+ u8 *tmp;
+ int bytes_per_datum, scan_count;
+ conf = &st->chip_config;
+
+ scan_count = bitmap_weight(indio_dev->active_scan_mask,
+ indio_dev->masklength);
+ bytes_per_datum = scan_count * 2;
+
+ ind = 0;
+ if (has_footer)
+ ind += 2;
+ tmp = (u8 *)buf;
+ d_ind = 0;
+
+ if (conf->gyro_fifo_enable) {
+ for (i = 0; i < ARRAY_SIZE(g); i++) {
+ g[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2]));
+ st->raw_gyro[i] = g[i];
+ }
+ ind += BYTES_PER_SENSOR;
+ d_ind = put_scan_to_buf(indio_dev, tmp, g,
+ INV_MPU_SCAN_GYRO_X, d_ind);
+ }
+ if (conf->accl_fifo_enable) {
+ st->mpu_slave->combine_data(&data[ind], a);
+ for (i = 0; i < ARRAY_SIZE(a); i++)
+ st->raw_accel[i] = a[i];
+
+ ind += BYTES_PER_SENSOR;
+ d_ind = put_scan_to_buf(indio_dev, tmp, a,
+ INV_MPU_SCAN_ACCL_X, d_ind);
+ }
+
+ i = (bytes_per_datum + 7) / 8;
+ if (ring->scan_timestamp)
+ buf[i] = t;
+ ring->access->store_to(indio_dev->buffer, (u8 *)buf);
+}
+
+/**
+ * inv_read_fifo_mpu3050() - Transfer data from FIFO to ring buffer for
+ * mpu3050.
+ */
+irqreturn_t inv_read_fifo_mpu3050(int irq, void *dev_id)
+{
+
+ struct inv_mpu_iio_s *st = (struct inv_mpu_iio_s *)dev_id;
+ struct iio_dev *indio_dev = iio_priv_to_dev(st);
+ int bytes_per_datum;
+ u8 data[64];
+ int result;
+ short fifo_count, byte_read;
+ u32 copied;
+ s64 timestamp;
+ struct inv_reg_map_s *reg;
+ reg = &st->reg;
+ /* It is impossible that chip is asleep or enable is
+ zero when interrupt is on
+ * because interrupt is now connected with enable */
+ if (st->chip_config.dmp_on)
+ bytes_per_datum = BYTES_FOR_DMP;
+ else
+ bytes_per_datum = (st->chip_config.accl_fifo_enable +
+ st->chip_config.gyro_fifo_enable)*BYTES_PER_SENSOR;
+ if (st->chip_config.has_footer)
+ byte_read = bytes_per_datum + MPU3050_FOOTER_SIZE;
+ else
+ byte_read = bytes_per_datum;
+
+ fifo_count = 0;
+ if (byte_read != 0) {
+ result = inv_i2c_read(st, reg->fifo_count_h,
+ FIFO_COUNT_BYTE, data);
+ if (result)
+ goto end_session;
+ fifo_count = be16_to_cpup((__be16 *)(&data[0]));
+ if (fifo_count < byte_read)
+ goto end_session;
+ if (fifo_count & 1)
+ goto flush_fifo;
+ if (fifo_count > FIFO_THRESHOLD)
+ goto flush_fifo;
+ /* Timestamp mismatch. */
+ if (kfifo_len(&st->timestamps) <
+ fifo_count / byte_read)
+ goto flush_fifo;
+ if (kfifo_len(&st->timestamps) >
+ fifo_count / byte_read + TIME_STAMP_TOR) {
+ if (st->chip_config.dmp_on) {
+ result = kfifo_to_user(&st->timestamps,
+ &timestamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ } else {
+ goto flush_fifo;
+ }
+ }
+ }
+ while ((bytes_per_datum != 0) && (fifo_count >= byte_read)) {
+ result = inv_i2c_read(st, reg->fifo_r_w, byte_read, data);
+ if (result)
+ goto flush_fifo;
+
+ result = kfifo_to_user(&st->timestamps,
+ &timestamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ inv_report_data_3050(indio_dev, timestamp,
+ st->chip_config.has_footer, data);
+ fifo_count -= byte_read;
+ if (st->chip_config.has_footer == 0) {
+ st->chip_config.has_footer = 1;
+ byte_read = bytes_per_datum + MPU3050_FOOTER_SIZE;
+ }
+ }
+
+end_session:
+ return IRQ_HANDLED;
+
+flush_fifo:
+ /* Flush HW and SW FIFOs. */
+ inv_reset_fifo(indio_dev);
+ inv_clear_kfifo(st);
+ return IRQ_HANDLED;
+}
+
+static int inv_report_gyro_accl_compass(struct iio_dev *indio_dev,
+ u8 *data, s64 t)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ short g[THREE_AXIS], a[THREE_AXIS], c[THREE_AXIS];
+ int q[4];
+ int result, ind, d_ind;
+ s64 buf[8];
+ u32 word;
+ u8 d[8], compass_divider;
+ u8 *tmp;
+ int source, i;
+ struct inv_chip_config_s *conf;
+
+ conf = &st->chip_config;
+ ind = 0;
+ if (conf->quaternion_on & conf->dmp_on) {
+ for (i = 0; i < ARRAY_SIZE(q); i++) {
+ q[i] = be32_to_cpup((__be32 *)(&data[ind + i * 4]));
+ st->raw_quaternion[i] = q[i];
+ }
+ ind += QUATERNION_BYTES;
+ }
+ if (conf->accl_fifo_enable) {
+ for (i = 0; i < ARRAY_SIZE(a); i++) {
+ a[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2]));
+ a[i] *= st->chip_info.multi;
+ st->raw_accel[i] = a[i];
+ }
+ ind += BYTES_PER_SENSOR;
+ }
+ if (conf->gyro_fifo_enable) {
+ for (i = 0; i < ARRAY_SIZE(g); i++) {
+ g[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2]));
+ st->raw_gyro[i] = g[i];
+ }
+ ind += BYTES_PER_SENSOR;
+ }
+ if (conf->dmp_on && (conf->tap_on || conf->display_orient_on)) {
+ word = (u32)(be32_to_cpup((u32 *)&data[ind]));
+ source = ((word >> 16) & 0xff);
+ if (source) {
+ st->tap_data = (DMP_MASK_TAP & (word & 0xff));
+ st->display_orient_data =
+ ((DMP_MASK_DIS_ORIEN & (word & 0xff)) >>
+ DMP_DIS_ORIEN_SHIFT);
+ }
+
+ /* report tap information */
+ if (source & INT_SRC_TAP)
+ sysfs_notify(&indio_dev->dev.kobj, NULL, "event_tap");
+ /* report orientation information */
+ if (source & INT_SRC_DISPLAY_ORIENT)
+ sysfs_notify(&indio_dev->dev.kobj, NULL,
+ "event_display_orientation");
+ }
+ /*divider and counter is used to decrease the speed of read in
+ high frequency sample rate*/
+ if (conf->compass_fifo_enable) {
+ c[0] = 0;
+ c[1] = 0;
+ c[2] = 0;
+ if (conf->dmp_on)
+ compass_divider = st->compass_dmp_divider;
+ else
+ compass_divider = st->compass_divider;
+ if (compass_divider == st->compass_counter) {
+ /*read from external sensor data register */
+ result = inv_i2c_read(st, REG_EXT_SENS_DATA_00,
+ NUM_BYTES_COMPASS_SLAVE, d);
+ /* d[7] is status 2 register */
+ /*for AKM8975, bit 2 and 3 should be all be zero*/
+ /* for AMK8963, bit 3 should be zero*/
+ if ((DATA_AKM_DRDY == d[0]) &&
+ (0 == (d[7] & DATA_AKM_STAT_MASK)) &&
+ (!result)) {
+ u8 *sens;
+ sens = st->chip_info.compass_sens;
+ c[0] = (short)((d[2] << 8) | d[1]);
+ c[1] = (short)((d[4] << 8) | d[3]);
+ c[2] = (short)((d[6] << 8) | d[5]);
+ c[0] = (short)(((int)c[0] *
+ (sens[0] + 128)) >> 8);
+ c[1] = (short)(((int)c[1] *
+ (sens[1] + 128)) >> 8);
+ c[2] = (short)(((int)c[2] *
+ (sens[2] + 128)) >> 8);
+ st->raw_compass[0] = c[0];
+ st->raw_compass[1] = c[1];
+ st->raw_compass[2] = c[2];
+ }
+ st->compass_counter = 0;
+ } else if (compass_divider != 0) {
+ st->compass_counter++;
+ }
+ if (!conf->normal_compass_measure) {
+ c[0] = 0;
+ c[1] = 0;
+ c[2] = 0;
+ conf->normal_compass_measure = 1;
+ }
+ }
+
+ tmp = (u8 *)buf;
+ d_ind = 0;
+ if (conf->quaternion_on & conf->dmp_on)
+ d_ind = put_scan_to_buf_q(indio_dev, tmp, q,
+ INV_MPU_SCAN_QUAT_R, d_ind);
+ if (conf->gyro_fifo_enable)
+ d_ind = put_scan_to_buf(indio_dev, tmp, g,
+ INV_MPU_SCAN_GYRO_X, d_ind);
+ if (conf->accl_fifo_enable)
+ d_ind = put_scan_to_buf(indio_dev, tmp, a,
+ INV_MPU_SCAN_ACCL_X, d_ind);
+ if (conf->compass_fifo_enable)
+ d_ind = put_scan_to_buf(indio_dev, tmp, c,
+ INV_MPU_SCAN_MAGN_X, d_ind);
+ if (ring->scan_timestamp)
+ buf[(d_ind + 7) / 8] = t;
+ /* if nothing is enabled, send nothing */
+ if (d_ind)
+ ring->access->store_to(indio_dev->buffer, (u8 *)buf);
+
+ return 0;
+}
+
+static void inv_process_motion(struct inv_mpu_iio_s *st)
+{
+ struct iio_dev *indio_dev = iio_priv_to_dev(st);
+ s32 diff, true_motion;
+ s64 timestamp;
+ int result;
+ u8 data[1];
+
+ /* motion interrupt */
+ result = inv_i2c_read(st, REG_INT_STATUS, 1, data);
+ if (result)
+ return;
+
+ if (data[0] & BIT_MOT_INT) {
+ timestamp = get_time_ns();
+ diff = (int)(((timestamp - st->mpu6500_last_motion_time) >>
+ NS_PER_MS_SHIFT));
+ if (diff > st->mot_int.mot_dur) {
+ st->mpu6500_last_motion_time = timestamp;
+ true_motion = 1;
+ } else {
+ true_motion = 0;
+ }
+ if (true_motion)
+ sysfs_notify(&indio_dev->dev.kobj, NULL,
+ "event_accel_motion");
+ }
+}
+
+static int get_bytes_per_datum(struct inv_mpu_iio_s *st)
+{
+ int bytes_per_datum;
+
+ bytes_per_datum = 0;
+ if (st->chip_config.dmp_on) {
+ if (st->chip_config.quaternion_on)
+ bytes_per_datum += QUATERNION_BYTES;
+ if (st->chip_config.tap_on ||
+ st->chip_config.display_orient_on)
+ bytes_per_datum += BYTES_FOR_EVENTS;
+ }
+ if (st->chip_config.accl_fifo_enable)
+ bytes_per_datum += BYTES_PER_SENSOR;
+ if (st->chip_config.gyro_fifo_enable)
+ bytes_per_datum += BYTES_PER_SENSOR;
+
+ return bytes_per_datum;
+}
+
+/**
+ * inv_read_fifo() - Transfer data from FIFO to ring buffer.
+ */
+irqreturn_t inv_read_fifo(int irq, void *dev_id)
+{
+
+ struct inv_mpu_iio_s *st = (struct inv_mpu_iio_s *)dev_id;
+ struct iio_dev *indio_dev = iio_priv_to_dev(st);
+ size_t bytes_per_datum;
+ int result;
+ u8 data[BYTES_FOR_DMP + QUATERNION_BYTES];
+ u16 fifo_count;
+ u32 copied;
+ s64 timestamp;
+ struct inv_reg_map_s *reg;
+ s64 buf[8];
+ s8 *tmp;
+
+ mutex_lock(&indio_dev->mlock);
+ if (!(iio_buffer_enabled(indio_dev)))
+ goto end_session;
+
+ reg = &st->reg;
+ if (!(st->chip_config.accl_fifo_enable |
+ st->chip_config.gyro_fifo_enable |
+ st->chip_config.dmp_on |
+ st->chip_config.compass_fifo_enable |
+ st->mot_int.mot_on))
+ goto end_session;
+ if (st->mot_int.mot_on)
+ inv_process_motion(st);
+ if (st->chip_config.lpa_mode) {
+ result = inv_i2c_read(st, reg->raw_accl,
+ BYTES_PER_SENSOR, data);
+ if (result)
+ goto end_session;
+ inv_report_gyro_accl_compass(indio_dev, data,
+ get_time_ns());
+ goto end_session;
+ }
+ bytes_per_datum = get_bytes_per_datum(st);
+ fifo_count = 0;
+ if (bytes_per_datum != 0) {
+ result = inv_i2c_read(st, reg->fifo_count_h,
+ FIFO_COUNT_BYTE, data);
+ if (result)
+ 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 (fifo_count & 1)
+ goto flush_fifo;
+ if (fifo_count > FIFO_THRESHOLD)
+ goto flush_fifo;
+ /* timestamp mismatch. */
+ if (kfifo_len(&st->timestamps) <
+ fifo_count / bytes_per_datum)
+ goto flush_fifo;
+ if (kfifo_len(&st->timestamps) >
+ fifo_count / bytes_per_datum + TIME_STAMP_TOR) {
+ if (st->chip_config.dmp_on) {
+ result = kfifo_to_user(&st->timestamps,
+ &timestamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ } else {
+ goto flush_fifo;
+ }
+ }
+ } else {
+ result = kfifo_to_user(&st->timestamps,
+ &timestamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ }
+ tmp = (s8 *)buf;
+ while ((bytes_per_datum != 0) && (fifo_count >= bytes_per_datum)) {
+ result = inv_i2c_read(st, reg->fifo_r_w, bytes_per_datum,
+ data);
+ if (result)
+ goto flush_fifo;
+
+ result = kfifo_to_user(&st->timestamps,
+ &timestamp, sizeof(timestamp), &copied);
+ if (result)
+ goto flush_fifo;
+ inv_report_gyro_accl_compass(indio_dev, data, timestamp);
+ fifo_count -= bytes_per_datum;
+ }
+ if (bytes_per_datum == 0 && st->chip_config.compass_fifo_enable)
+ inv_report_gyro_accl_compass(indio_dev, data, timestamp);
+
+end_session:
+ mutex_unlock(&indio_dev->mlock);
+
+ return IRQ_HANDLED;
+
+flush_fifo:
+ /* Flush HW and SW FIFOs. */
+ inv_reset_fifo(indio_dev);
+ inv_clear_kfifo(st);
+ mutex_unlock(&indio_dev->mlock);
+
+ return IRQ_HANDLED;
+}
+
+void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ free_irq(gpio_to_irq(st->client->irq), st);
+ iio_kfifo_free(indio_dev->buffer);
+};
+
+static int inv_postenable(struct iio_dev *indio_dev)
+{
+ return set_inv_enable(indio_dev, true);
+}
+
+static int inv_predisable(struct iio_dev *indio_dev)
+{
+ return set_inv_enable(indio_dev, false);
+}
+
+static void inv_scan_query(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int result;
+
+ if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_Z))
+ st->chip_config.gyro_fifo_enable = 1;
+ else
+ st->chip_config.gyro_fifo_enable = 0;
+
+ if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_Z))
+ st->chip_config.accl_fifo_enable = 1;
+ else
+ st->chip_config.accl_fifo_enable = 0;
+
+ if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_Z))
+ st->chip_config.compass_fifo_enable = 1;
+ else
+ st->chip_config.compass_fifo_enable = 0;
+
+ /* check to make sure engine is turned on if fifo is turned on */
+ if (st->chip_config.gyro_fifo_enable &&
+ (!st->chip_config.gyro_enable)) {
+ result = st->switch_gyro_engine(st, true);
+ if (result)
+ return;
+ st->chip_config.gyro_enable = true;
+ }
+ if (st->chip_config.accl_fifo_enable &&
+ (!st->chip_config.accl_enable)) {
+ result = st->switch_accl_engine(st, true);
+ if (result)
+ return;
+ st->chip_config.accl_enable = true;
+ }
+}
+
+static int inv_check_quaternion(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int result;
+
+ if (st->chip_config.dmp_on) {
+ if (
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_QUAT_R) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_QUAT_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_QUAT_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_QUAT_Z))
+ st->chip_config.quaternion_on = 1;
+ else
+ st->chip_config.quaternion_on = 0;
+
+ result = inv_send_quaternion(st,
+ st->chip_config.quaternion_on);
+ if (result)
+ return result;
+ } else {
+ st->chip_config.quaternion_on = 0;
+ clear_bit(INV_MPU_SCAN_QUAT_R, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_QUAT_X, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_QUAT_Y, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_QUAT_Z, ring->scan_mask);
+ }
+
+ return 0;
+}
+
+static int inv_check_conflict_sysfs(struct iio_dev *indio_dev)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int result;
+
+ if (st->chip_config.lpa_mode) {
+ /* dmp cannot run with low power mode on */
+ st->chip_config.dmp_on = 0;
+ result = st->gyro_en(st, ring, false);
+ if (result)
+ return result;
+ result = st->compass_en(st, ring, false);
+ if (result)
+ return result;
+ result = st->quaternion_en(st, ring, false);
+ if (result)
+ return result;
+
+ result = st->accl_en(st, ring, true);
+ if (result)
+ return result;
+ }
+ result = inv_check_quaternion(indio_dev);
+ if (result)
+ return result;
+
+ return result;
+}
+
+static int inv_preenable(struct iio_dev *indio_dev)
+{
+ int result;
+
+ result = inv_check_conflict_sysfs(indio_dev);
+ if (result)
+ return result;
+ inv_scan_query(indio_dev);
+ result = iio_sw_buffer_preenable(indio_dev);
+
+ return result;
+}
+
+static const struct iio_buffer_setup_ops inv_mpu_ring_setup_ops = {
+ .preenable = &inv_preenable,
+ .postenable = &inv_postenable,
+ .predisable = &inv_predisable,
+};
+
+int inv_mpu_configure_ring(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring;
+
+ ring = iio_kfifo_allocate(indio_dev);
+ if (!ring)
+ return -ENOMEM;
+ indio_dev->buffer = ring;
+ /* setup ring buffer */
+ ring->scan_timestamp = true;
+ indio_dev->setup_ops = &inv_mpu_ring_setup_ops;
+ /*scan count double count timestamp. should subtract 1. but
+ number of channels still includes timestamp*/
+ if (INV_MPU3050 == st->chip_type)
+ ret = request_threaded_irq(gpio_to_irq(st->client->irq), inv_irq_handler,
+ inv_read_fifo_mpu3050,
+ IRQF_TRIGGER_RISING | IRQF_SHARED, "inv_irq", st);
+ else
+ ret = request_threaded_irq(gpio_to_irq(st->client->irq), inv_irq_handler,
+ inv_read_fifo,
+ IRQF_TRIGGER_RISING | IRQF_SHARED, "inv_irq", st);
+ if (ret)
+ goto error_iio_sw_rb_free;
+
+ indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
+ return 0;
+error_iio_sw_rb_free:
+ iio_kfifo_free(indio_dev->buffer);
+ return ret;
+}
+
+/**
+ * @}
+ */
+