summaryrefslogtreecommitdiff
path: root/drivers/iio/imu/inv_mpu/inv_mpu_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/imu/inv_mpu/inv_mpu_core.c')
-rw-r--r--drivers/iio/imu/inv_mpu/inv_mpu_core.c2078
1 files changed, 2078 insertions, 0 deletions
diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_core.c b/drivers/iio/imu/inv_mpu/inv_mpu_core.c
new file mode 100644
index 00000000..e547ab77
--- /dev/null
+++ b/drivers/iio/imu/inv_mpu/inv_mpu_core.c
@@ -0,0 +1,2078 @@
+/*
+* 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_core.c
+ * @brief A sysfs device driver for Invensense devices
+ * @details This driver currently works for the
+ * MPU3050/MPU6050/MPU9150/MPU6500/MPU9250 devices.
+ */
+
+#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/spinlock.h>
+#include <linux/iio/sysfs.h>
+
+#include "inv_mpu_iio.h"
+#include "inv_counters.h"
+
+#include <linux/regulator/consumer.h>
+#include <mach/regulator.h>
+#include <linux/gpio.h>
+#include <linux/input.h>
+s64 get_time_ns(void)
+{
+ struct timespec ts;
+ ktime_get_ts(&ts);
+ return timespec_to_ns(&ts);
+}
+
+static const short AKM8975_ST_Lower[3] = {-100, -100, -1000};
+static const short AKM8975_ST_Upper[3] = {100, 100, -300};
+
+static const short AKM8972_ST_Lower[3] = {-50, -50, -500};
+static const short AKM8972_ST_Upper[3] = {50, 50, -100};
+
+static const short AKM8963_ST_Lower[3] = {-200, -200, -3200};
+static const short AKM8963_ST_Upper[3] = {200, 200, -800};
+
+static const struct inv_hw_s hw_info[INV_NUM_PARTS] = {
+ {119, "ITG3500"},
+ { 63, "MPU3050"},
+ {117, "MPU6050"},
+ {118, "MPU9150"},
+ {119, "MPU6500"},
+ {118, "MPU9250"},
+};
+
+static void inv_setup_reg(struct inv_reg_map_s *reg)
+{
+ reg->sample_rate_div = REG_SAMPLE_RATE_DIV;
+ reg->lpf = REG_CONFIG;
+ reg->bank_sel = REG_BANK_SEL;
+ reg->user_ctrl = REG_USER_CTRL;
+ reg->fifo_en = REG_FIFO_EN;
+ reg->gyro_config = REG_GYRO_CONFIG;
+ reg->accl_config = REG_ACCEL_CONFIG;
+ reg->fifo_count_h = REG_FIFO_COUNT_H;
+ reg->fifo_r_w = REG_FIFO_R_W;
+ reg->raw_gyro = REG_RAW_GYRO;
+ reg->raw_accl = REG_RAW_ACCEL;
+ reg->temperature = REG_TEMPERATURE;
+ reg->int_enable = REG_INT_ENABLE;
+ reg->int_status = REG_INT_STATUS;
+ reg->pwr_mgmt_1 = REG_PWR_MGMT_1;
+ reg->pwr_mgmt_2 = REG_PWR_MGMT_2;
+ reg->mem_start_addr = REG_MEM_START_ADDR;
+ reg->mem_r_w = REG_MEM_RW;
+ reg->prgm_strt_addrh = REG_PRGM_STRT_ADDRH;
+};
+
+/**
+ * inv_i2c_read() - Read one or more bytes from the device registers.
+ * @st: Device driver instance.
+ * @reg: First device register to be read from.
+ * @length: Number of bytes to read.
+ * @data: Data read from device.
+ * NOTE:This is not re-implementation of i2c_smbus_read because i2c
+ * address could be specified in this case. We could have two different
+ * i2c address due to secondary i2c interface.
+ */
+int inv_i2c_read_base(struct inv_mpu_iio_s *st, u16 i2c_addr,
+ u8 reg, u16 length, u8 *data)
+{
+ struct i2c_msg msgs[2];
+ int res;
+
+ if (!data)
+ return -EINVAL;
+
+ msgs[0].addr = i2c_addr;
+ msgs[0].flags = 0; /* write */
+ msgs[0].buf = &reg;
+ msgs[0].len = 1;
+
+ msgs[1].addr = i2c_addr;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].buf = data;
+ msgs[1].len = length;
+
+ res = i2c_transfer(st->sl_handle, msgs, 2);
+
+ if (res < 2) {
+ if (res >= 0)
+ res = -EIO;
+ } else
+ res = 0;
+
+ INV_I2C_INC_MPUWRITE(3);
+ INV_I2C_INC_MPUREAD(length);
+#if CONFIG_DYNAMIC_DEBUG
+ {
+ char *read = 0;
+ pr_debug("%s RD%02X%02X%02X -> %s%s\n", st->hw->name,
+ i2c_addr, reg, length,
+ wr_pr_debug_begin(data, length, read),
+ wr_pr_debug_end(read));
+ }
+#endif
+ return res;
+}
+
+/**
+ * inv_i2c_single_write() - Write a byte to a device register.
+ * @st: Device driver instance.
+ * @reg: Device register to be written to.
+ * @data: Byte to write to device.
+ * NOTE:This is not re-implementation of i2c_smbus_write because i2c
+ * address could be specified in this case. We could have two different
+ * i2c address due to secondary i2c interface.
+ */
+int inv_i2c_single_write_base(struct inv_mpu_iio_s *st,
+ u16 i2c_addr, u8 reg, u8 data)
+{
+ u8 tmp[2];
+ struct i2c_msg msg;
+ int res;
+
+ tmp[0] = reg;
+ tmp[1] = data;
+
+ msg.addr = i2c_addr;
+ msg.flags = 0; /* write */
+ msg.buf = tmp;
+ msg.len = 2;
+
+ pr_debug("%s WR%02X%02X%02X\n", st->hw->name, i2c_addr, reg, data);
+ INV_I2C_INC_MPUWRITE(3);
+
+ res = i2c_transfer(st->sl_handle, &msg, 1);
+ if (res < 1) {
+ if (res == 0)
+ res = -EIO;
+ return res;
+ } else
+ return 0;
+}
+
+static int inv_switch_engine(struct inv_mpu_iio_s *st, bool en, u32 mask)
+{
+ struct inv_reg_map_s *reg;
+ u8 data, mgmt_1;
+ int result;
+ reg = &st->reg;
+ /* switch clock needs to be careful. Only when gyro is on, can
+ clock source be switched to gyro. Otherwise, it must be set to
+ internal clock */
+ if (BIT_PWR_GYRO_STBY == mask) {
+ result = inv_i2c_read(st, reg->pwr_mgmt_1, 1, &mgmt_1);
+ if (result)
+ return result;
+
+ mgmt_1 &= ~BIT_CLK_MASK;
+ }
+
+ if ((BIT_PWR_GYRO_STBY == mask) && (!en)) {
+ /* turning off gyro requires switch to internal clock first.
+ Then turn off gyro engine */
+ mgmt_1 |= INV_CLK_INTERNAL;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1,
+ mgmt_1);
+ if (result)
+ return result;
+ }
+
+ result = inv_i2c_read(st, reg->pwr_mgmt_2, 1, &data);
+ if (result)
+ return result;
+ if (en)
+ data &= (~mask);
+ else
+ data |= mask;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_2, data);
+ if (result)
+ return result;
+
+ if ((BIT_PWR_GYRO_STBY == mask) && en) {
+ /* only gyro on needs sensor up time */
+ msleep(SENSOR_UP_TIME);
+ /* after gyro is on & stable, switch internal clock to PLL */
+ mgmt_1 |= INV_CLK_PLL;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1,
+ mgmt_1);
+ if (result)
+ return result;
+ }
+
+ return 0;
+}
+
+/**
+ * inv_lpa_freq() - store current low power frequency setting.
+ */
+static int inv_lpa_freq(struct inv_mpu_iio_s *st, int lpa_freq)
+{
+ unsigned long result;
+ u8 d;
+ struct inv_reg_map_s *reg;
+ /* this mapping makes 6500 and 6050 setting close */
+ /* 2, 4, 6, 7 corresponds to 0.98, 3.91, 15.63, 31.25 */
+ const u8 mpu6500_lpa_mapping[] = {2, 4, 6, 7};
+
+ if (lpa_freq > MAX_LPA_FREQ_PARAM)
+ return -EINVAL;
+
+ if (INV_MPU6500 == st->chip_type) {
+ d = mpu6500_lpa_mapping[lpa_freq];
+ result = inv_i2c_single_write(st, REG_6500_LP_ACCEL_ODR, d);
+ if (result)
+ return result;
+ } else {
+ reg = &st->reg;
+ result = inv_i2c_read(st, reg->pwr_mgmt_2, 1, &d);
+ if (result)
+ return result;
+ d &= ~BIT_LPA_FREQ;
+ d |= (u8)(lpa_freq << LPA_FREQ_SHIFT);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_2, d);
+ if (result)
+ return result;
+ }
+ st->chip_config.lpa_freq = lpa_freq;
+
+ return 0;
+}
+
+static int set_power_itg(struct inv_mpu_iio_s *st, bool power_on)
+{
+ struct inv_reg_map_s *reg;
+ u8 data;
+ int result;
+
+ reg = &st->reg;
+ if (power_on)
+ data = 0;
+ else
+ data = BIT_SLEEP;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data);
+ if (result)
+ return result;
+
+ if (power_on) {
+ msleep(POWER_UP_TIME);
+ result = inv_switch_engine(st, st->chip_config.gyro_enable,
+ BIT_PWR_GYRO_STBY);
+ if (result)
+ return result;
+ result = inv_switch_engine(st, st->chip_config.accl_enable,
+ BIT_PWR_ACCL_STBY);
+ if (result)
+ return result;
+ result = inv_lpa_freq(st, st->chip_config.lpa_freq);
+ if (result)
+ return result;
+ }
+ st->chip_config.is_asleep = !power_on;
+
+ return 0;
+}
+
+/**
+ * inv_init_config() - Initialize hardware, disable FIFO.
+ * @indio_dev: Device driver instance.
+ * Initial configuration:
+ * FSR: +/- 2000DPS
+ * DLPF: 42Hz
+ * FIFO rate: 50Hz
+ */
+static int inv_init_config(struct iio_dev *indio_dev)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+
+ if (st->chip_config.is_asleep)
+ return -EPERM;
+
+ reg = &st->reg;
+ result = set_inv_enable(indio_dev, false);
+ if (result)
+ return result;
+
+ result = inv_i2c_single_write(st, reg->gyro_config,
+ INV_FSR_2000DPS << GYRO_CONFIG_FSR_SHIFT);
+ if (result)
+ return result;
+
+ st->chip_config.fsr = INV_FSR_2000DPS;
+
+ result = inv_i2c_single_write(st, reg->lpf, INV_FILTER_42HZ);
+ if (result)
+ return result;
+ st->chip_config.lpf = INV_FILTER_42HZ;
+
+ result = inv_i2c_single_write(st, reg->sample_rate_div,
+ ONE_K_HZ / INIT_FIFO_RATE - 1);
+ if (result)
+ return result;
+ st->chip_config.fifo_rate = INIT_FIFO_RATE;
+ st->irq_dur_ns = INIT_DUR_TIME;
+ st->chip_config.prog_start_addr = DMP_START_ADDR;
+ st->chip_config.gyro_enable = 1;
+ st->chip_config.gyro_fifo_enable = 1;
+ st->chip_config.dmp_output_rate = INIT_DMP_OUTPUT_RATE;
+ if (INV_ITG3500 != st->chip_type) {
+ st->chip_config.accl_enable = 1;
+ st->chip_config.accl_fifo_enable = 1;
+ st->chip_config.accl_fs = INV_FS_02G;
+ result = inv_i2c_single_write(st, reg->accl_config,
+ (INV_FS_02G << ACCL_CONFIG_FSR_SHIFT));
+ if (result)
+ return result;
+ st->tap.time = INIT_TAP_TIME;
+ st->tap.thresh = INIT_TAP_THRESHOLD;
+ st->tap.min_count = INIT_TAP_MIN_COUNT;
+
+ result = inv_i2c_single_write(st, REG_ACCEL_MOT_DUR,
+ INIT_MOT_DUR);
+ if (result)
+ return result;
+ st->mot_int.mot_dur = INIT_MOT_DUR;
+
+ result = inv_i2c_single_write(st, REG_ACCEL_MOT_THR,
+ INIT_MOT_THR);
+ if (result)
+ return result;
+ st->mot_int.mot_thr = INIT_MOT_THR;
+ }
+
+ return 0;
+}
+
+/**
+ * inv_compass_scale_show() - show compass scale.
+ */
+static int inv_compass_scale_show(struct inv_mpu_iio_s *st, int *scale)
+{
+ if (COMPASS_ID_AK8975 == st->plat_data.sec_slave_id)
+ *scale = DATA_AKM8975_SCALE;
+ else if (COMPASS_ID_AK8972 == st->plat_data.sec_slave_id)
+ *scale = DATA_AKM8972_SCALE;
+ else if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id)
+ if (st->compass_scale)
+ *scale = DATA_AKM8963_SCALE1;
+ else
+ *scale = DATA_AKM8963_SCALE0;
+ else
+ return -EINVAL;
+
+ return IIO_VAL_INT;
+}
+
+/**
+ * mpu_read_raw() - read raw method.
+ */
+static int mpu_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ int result;
+ if (st->chip_config.is_asleep)
+ return -EINVAL;
+ switch (mask) {
+ case 0:
+ if (!st->chip_config.enable)
+ return -EPERM;
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ if (!st->chip_config.gyro_enable)
+ return -EPERM;
+ *val = st->raw_gyro[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ case IIO_ACCEL:
+ if (!st->chip_config.accl_enable)
+ return -EPERM;
+ *val = st->raw_accel[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ case IIO_MAGN:
+ if (!st->chip_config.compass_enable)
+ return -EPERM;
+ *val = st->raw_compass[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ case IIO_QUATERNION:
+ if (!(st->chip_config.dmp_on
+ && st->chip_config.quaternion_on))
+ return -EPERM;
+ if (IIO_MOD_R == chan->channel2)
+ *val = st->raw_quaternion[0];
+ else
+ *val = st->raw_quaternion[chan->channel2 -
+ IIO_MOD_X + 1];
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ return -EINVAL;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ {
+ const s16 gyro_scale[] = {250, 500, 1000, 2000};
+
+ *val = gyro_scale[st->chip_config.fsr];
+
+ return IIO_VAL_INT;
+ }
+ case IIO_ACCEL:
+ {
+ const s16 accel_scale[] = {2, 4, 8, 16};
+ *val = accel_scale[st->chip_config.accl_fs];
+ return IIO_VAL_INT;
+ }
+ case IIO_MAGN:
+ return inv_compass_scale_show(st, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_CALIBBIAS:
+ /* return bias=0 for both accel and gyro for MPU6500;
+ self test not supported yet */
+ if (INV_MPU6500 == st->chip_type) {
+ *val = 0;
+ return IIO_VAL_INT;
+ }
+ if (st->chip_config.self_test_run_once == 0) {
+ result = inv_do_test(st, 0, st->gyro_bias,
+ st->accel_bias);
+ /* Reset Accel and Gyro full scale range
+ back to default value */
+ inv_recover_setting(st);
+ if (result)
+ return result;
+ st->chip_config.self_test_run_once = 1;
+ }
+
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ *val = st->gyro_bias[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ case IIO_ACCEL:
+ *val = st->accel_bias[chan->channel2 - IIO_MOD_X] *
+ st->chip_info.multi;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ *val = st->input_accel_bias[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * inv_write_fsr() - Configure the gyro's scale range.
+ */
+static int inv_write_fsr(struct inv_mpu_iio_s *st, int fsr)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ reg = &st->reg;
+ if ((fsr < 0) || (fsr > MAX_GYRO_FS_PARAM))
+ return -EINVAL;
+ if (fsr == st->chip_config.fsr)
+ return 0;
+
+ if (INV_MPU3050 == st->chip_type)
+ result = inv_i2c_single_write(st, reg->lpf,
+ (fsr << GYRO_CONFIG_FSR_SHIFT) | st->chip_config.lpf);
+ else
+ result = inv_i2c_single_write(st, reg->gyro_config,
+ fsr << GYRO_CONFIG_FSR_SHIFT);
+
+ if (result)
+ return result;
+ st->chip_config.fsr = fsr;
+
+ return 0;
+}
+
+/**
+ * inv_write_accel_fs() - Configure the accelerometer's scale range.
+ */
+static int inv_write_accel_fs(struct inv_mpu_iio_s *st, int fs)
+{
+ int result;
+ struct inv_reg_map_s *reg;
+ reg = &st->reg;
+
+ if (fs < 0 || fs > MAX_ACCL_FS_PARAM)
+ return -EINVAL;
+ if (fs == st->chip_config.accl_fs)
+ return 0;
+ if (INV_MPU3050 == st->chip_type)
+ result = st->mpu_slave->set_fs(st, fs);
+ else
+ result = inv_i2c_single_write(st, reg->accl_config,
+ (fs << ACCL_CONFIG_FSR_SHIFT));
+ if (result)
+ return result;
+
+ st->chip_config.accl_fs = fs;
+
+ return 0;
+}
+
+/**
+ * inv_write_compass_scale() - Configure the compass's scale range.
+ */
+static int inv_write_compass_scale(struct inv_mpu_iio_s *st, int data)
+{
+ char d, en;
+ int result;
+ if (COMPASS_ID_AK8963 != st->plat_data.sec_slave_id)
+ return 0;
+ en = !!data;
+ if (st->compass_scale == en)
+ return 0;
+ d = (DATA_AKM_MODE_SM | (st->compass_scale << AKM8963_SCALE_SHIFT));
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_DO, d);
+ if (result)
+ return result;
+ st->compass_scale = en;
+
+ return 0;
+}
+
+static inline int check_enable(struct inv_mpu_iio_s *st)
+{
+ return st->chip_config.is_asleep | st->chip_config.enable;
+}
+
+static inline int check_dmp_on(struct inv_mpu_iio_s *st)
+{
+ return (!st->chip_config.is_asleep) &&
+ st->chip_config.enable && st->chip_config.dmp_on;
+}
+
+/**
+ * mpu_write_raw() - write raw method.
+ */
+static int mpu_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask) {
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ int result;
+ if (check_enable(st))
+ return -EPERM;
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ return inv_write_fsr(st, val);
+ case IIO_ACCEL:
+ return inv_write_accel_fs(st, val);
+ case IIO_MAGN:
+ return inv_write_compass_scale(st, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ if (!st->chip_config.firmware_loaded)
+ return -EPERM;
+ result = inv_set_accel_bias_dmp(st);
+ if (result)
+ return result;
+ st->input_accel_bias[chan->channel2 - IIO_MOD_X] = val;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * inv_set_lpf() - set low pass filer based on fifo rate.
+ */
+static int inv_set_lpf(struct inv_mpu_iio_s *st, int rate)
+{
+ const short hz[] = {188, 98, 42, 20, 10, 5};
+ const int d[] = {INV_FILTER_188HZ, INV_FILTER_98HZ,
+ INV_FILTER_42HZ, INV_FILTER_20HZ,
+ INV_FILTER_10HZ, INV_FILTER_5HZ};
+ int i, h, data, result;
+ struct inv_reg_map_s *reg;
+ reg = &st->reg;
+ h = (rate >> 1);
+ i = 0;
+ while ((h < hz[i]) && (i < ARRAY_SIZE(d) - 1))
+ i++;
+ data = d[i];
+ if (INV_MPU3050 == st->chip_type) {
+ if (st->mpu_slave != NULL) {
+ result = st->mpu_slave->set_lpf(st, rate);
+ if (result)
+ return result;
+ }
+ result = inv_i2c_single_write(st, reg->lpf, data |
+ (st->chip_config.fsr << GYRO_CONFIG_FSR_SHIFT));
+ } else {
+ result = inv_i2c_single_write(st, reg->lpf, data);
+ }
+ if (result)
+ return result;
+ st->chip_config.lpf = data;
+
+ return 0;
+}
+
+/**
+ * inv_fifo_rate_store() - Set fifo rate.
+ */
+static ssize_t inv_fifo_rate_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ u32 fifo_rate;
+ u8 data;
+ int result;
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ struct inv_reg_map_s *reg;
+ reg = &st->reg;
+
+ if (check_enable(st))
+ return -EPERM;
+ if (kstrtouint(buf, 10, &fifo_rate))
+ return -EINVAL;
+ if ((fifo_rate < MIN_FIFO_RATE) || (fifo_rate > MAX_FIFO_RATE))
+ return -EINVAL;
+ if (fifo_rate == st->chip_config.fifo_rate)
+ return count;
+ if (st->chip_config.has_compass) {
+ st->compass_divider = COMPASS_RATE_SCALE * fifo_rate /
+ ONE_K_HZ;
+ if (st->compass_divider > 0)
+ st->compass_divider -= 1;
+ st->compass_counter = 0;
+ }
+ data = ONE_K_HZ / fifo_rate - 1;
+ result = inv_i2c_single_write(st, reg->sample_rate_div, data);
+ if (result)
+ return result;
+ st->chip_config.fifo_rate = fifo_rate;
+ result = inv_set_lpf(st, fifo_rate);
+ if (result)
+ return result;
+ st->irq_dur_ns = (data + 1) * NSEC_PER_MSEC;
+
+ return count;
+}
+
+/**
+ * inv_fifo_rate_show() - Get the current sampling rate.
+ */
+static ssize_t inv_fifo_rate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+
+ return sprintf(buf, "%d\n", st->chip_config.fifo_rate);
+}
+
+/**
+ * inv_power_state_store() - Turn device on/off.
+ */
+static ssize_t inv_power_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int result;
+ u32 power_state;
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ if (kstrtouint(buf, 10, &power_state))
+ return -EINVAL;
+ if ((!power_state) == st->chip_config.is_asleep)
+ return count;
+ result = st->set_power_state(st, power_state);
+
+ return count;
+}
+
+/**
+ * inv_reg_dump_show() - Register dump for testing.
+ */
+static ssize_t inv_reg_dump_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ii;
+ char data;
+ ssize_t bytes_printed = 0;
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+
+ for (ii = 0; ii < st->hw->num_reg; ii++) {
+ /* don't read fifo r/w register */
+ if (ii == st->reg.fifo_r_w)
+ data = 0;
+ else
+ inv_i2c_read(st, ii, 1, &data);
+ bytes_printed += sprintf(buf + bytes_printed, "%#2x: %#2x\n",
+ ii, data);
+ }
+
+ return bytes_printed;
+}
+
+int write_be32_key_to_mem(struct inv_mpu_iio_s *st,
+ u32 data, int key)
+{
+ cpu_to_be32s(&data);
+ return mem_w_key(key, sizeof(data), (u8 *)&data);
+}
+
+/**
+ * inv_dmp_attr_store() - calling this function will store current
+ * dmp parameter settings
+ */
+static ssize_t inv_dmp_attr_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ int result, data;
+ mutex_lock(&indio_dev->mlock);
+ if (st->chip_config.is_asleep | (!st->chip_config.firmware_loaded) |
+ st->chip_config.enable) {
+ result = -EINVAL;
+ goto dmp_attr_store_fail;
+ }
+ result = kstrtoint(buf, 10, &data);
+ if (result)
+ goto dmp_attr_store_fail;
+ switch (this_attr->address) {
+ case ATTR_DMP_PEDOMETER_STEPS:
+ result = write_be32_key_to_mem(st, data, KEY_D_PEDSTD_STEPCTR);
+ if (result)
+ goto dmp_attr_store_fail;
+ break;
+ case ATTR_DMP_PEDOMETER_TIME:
+ result = write_be32_key_to_mem(st, data, KEY_D_PEDSTD_TIMECTR);
+ if (result)
+ goto dmp_attr_store_fail;
+ break;
+ case ATTR_DMP_TAP_THRESHOLD: {
+ const char ax[] = {INV_TAP_AXIS_X, INV_TAP_AXIS_Y,
+ INV_TAP_AXIS_Z};
+ int i;
+ if (data < 0 || data > USHRT_MAX) {
+ result = -EINVAL;
+ goto dmp_attr_store_fail;
+ }
+ for (i = 0; i < ARRAY_SIZE(ax); i++) {
+ result = inv_set_tap_threshold_dmp(st, ax[i], data);
+ if (result)
+ goto dmp_attr_store_fail;
+ }
+ st->tap.thresh = data;
+ break;
+ }
+ case ATTR_DMP_TAP_MIN_COUNT:
+ if (data < 0 || data > USHRT_MAX) {
+ result = -EINVAL;
+ goto dmp_attr_store_fail;
+ }
+ result = inv_set_min_taps_dmp(st, data);
+ if (result)
+ goto dmp_attr_store_fail;
+ st->tap.min_count = data;
+ break;
+ case ATTR_DMP_TAP_ON:
+ result = inv_enable_tap_dmp(st, !!data);
+ if (result)
+ goto dmp_attr_store_fail;
+ st->chip_config.tap_on = !!data;
+ break;
+ case ATTR_DMP_TAP_TIME:
+ if (data < 0 || data > USHRT_MAX) {
+ result = -EINVAL;
+ goto dmp_attr_store_fail;
+ }
+ result = inv_set_tap_time_dmp(st, data);
+ if (result)
+ goto dmp_attr_store_fail;
+ st->tap.time = data;
+ break;
+ case ATTR_DMP_ON:
+ st->chip_config.dmp_on = !!data;
+ break;
+ case ATTR_DMP_INT_ON:
+ st->chip_config.dmp_int_on = !!data;
+ break;
+ case ATTR_DMP_EVENT_INT_ON:
+ result = inv_set_interrupt_on_gesture_event(st, !!data);
+ if (result)
+ goto dmp_attr_store_fail;
+ st->chip_config.dmp_event_int_on = !!data;
+ break;
+ case ATTR_DMP_OUTPUT_RATE:
+ if (data <= 0 || data > USHRT_MAX) {
+ result = -EINVAL;
+ goto dmp_attr_store_fail;
+ }
+ result = inv_set_fifo_rate(st, data);
+ if (result)
+ goto dmp_attr_store_fail;
+ if (st->chip_config.has_compass) {
+ st->compass_dmp_divider = COMPASS_RATE_SCALE * data /
+ ONE_K_HZ;
+ if (st->compass_dmp_divider > 0)
+ st->compass_dmp_divider -= 1;
+ st->compass_counter = 0;
+ }
+ st->chip_config.dmp_output_rate = data;
+ break;
+ case ATTR_DMP_DISPLAY_ORIENTATION_ON:
+ result = inv_set_display_orient_interrupt_dmp(st, !!data);
+ if (result)
+ goto dmp_attr_store_fail;
+ st->chip_config.display_orient_on = !!data;
+ break;
+ default:
+ result = -EINVAL;
+ goto dmp_attr_store_fail;
+ }
+ result = count;
+
+dmp_attr_store_fail:
+ mutex_unlock(&indio_dev->mlock);
+
+ return result;
+}
+
+/**
+ * inv_attr_show() - calling this function will show current
+ * dmp parameters.
+ */
+static ssize_t inv_attr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ s8 d[4];
+ int result, data;
+ s8 *m;
+
+ switch (this_attr->address) {
+ case ATTR_DMP_PEDOMETER_STEPS:
+ if (!check_dmp_on(st))
+ return -EPERM;
+ result = mpu_memory_read(st, st->i2c_addr,
+ inv_dmp_get_address(KEY_D_PEDSTD_STEPCTR), 4, d);
+ if (result)
+ return result;
+ data = be32_to_cpup((int *)d);
+ return sprintf(buf, "%d\n", data);
+ case ATTR_DMP_PEDOMETER_TIME:
+ if (!check_dmp_on(st))
+ return -EPERM;
+ result = mpu_memory_read(st, st->i2c_addr,
+ inv_dmp_get_address(KEY_D_PEDSTD_TIMECTR), 4, d);
+ if (result)
+ return result;
+ data = be32_to_cpup((int *)d);
+ return sprintf(buf, "%d\n", data * MS_PER_DMP_TICK);
+ case ATTR_DMP_TAP_THRESHOLD:
+ return sprintf(buf, "%d\n", st->tap.thresh);
+ case ATTR_DMP_TAP_MIN_COUNT:
+ return sprintf(buf, "%d\n", st->tap.min_count);
+ case ATTR_DMP_TAP_ON:
+ return sprintf(buf, "%d\n", st->chip_config.tap_on);
+ case ATTR_DMP_TAP_TIME:
+ return sprintf(buf, "%d\n", st->tap.time);
+ case ATTR_DMP_ON:
+ return sprintf(buf, "%d\n", st->chip_config.dmp_on);
+ case ATTR_DMP_INT_ON:
+ return sprintf(buf, "%d\n", st->chip_config.dmp_int_on);
+ case ATTR_DMP_EVENT_INT_ON:
+ return sprintf(buf, "%d\n", st->chip_config.dmp_event_int_on);
+ case ATTR_DMP_OUTPUT_RATE:
+ return sprintf(buf, "%d\n", st->chip_config.dmp_output_rate);
+ case ATTR_DMP_QUATERNION_ON:
+ return sprintf(buf, "%d\n", st->chip_config.quaternion_on);
+ case ATTR_DMP_DISPLAY_ORIENTATION_ON:
+ return sprintf(buf, "%d\n",
+ st->chip_config.display_orient_on);
+ case ATTR_LPA_FREQ:{
+ const char *f[] = {"1.25", "5", "20", "40"};
+ return sprintf(buf, "%s\n", f[st->chip_config.lpa_freq]);
+ }
+ case ATTR_SELF_TEST:
+ if (INV_MPU3050 == st->chip_type)
+ result = 1;
+ else if (INV_MPU6500 == st->chip_type)
+ result = inv_hw_self_test_6500(st);
+ else
+ result = inv_hw_self_test(st);
+ return sprintf(buf, "%d\n", result);
+ case ATTR_GYRO_MATRIX:
+ m = st->plat_data.orientation;
+ return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+ case ATTR_ACCL_MATRIX:
+ if (st->plat_data.sec_slave_type == SECONDARY_SLAVE_TYPE_ACCEL)
+ m = st->plat_data.secondary_orientation;
+ else
+ m = st->plat_data.orientation;
+ return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+ case ATTR_COMPASS_MATRIX:
+ if (st->plat_data.sec_slave_type ==
+ SECONDARY_SLAVE_TYPE_COMPASS)
+ m = st->plat_data.secondary_orientation;
+ else
+ return -ENODEV;
+ return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+ case ATTR_GYRO_ENABLE:
+ return sprintf(buf, "%d\n", st->chip_config.gyro_enable);
+ case ATTR_ACCL_ENABLE:
+ return sprintf(buf, "%d\n", st->chip_config.accl_enable);
+ case ATTR_COMPASS_ENABLE:
+ return sprintf(buf, "%d\n", st->chip_config.compass_enable);
+ case ATTR_POWER_STATE:
+ return sprintf(buf, "%d\n", !st->chip_config.is_asleep);
+ case ATTR_FIRMWARE_LOADED:
+ return sprintf(buf, "%d\n", st->chip_config.firmware_loaded);
+ case ATTR_MOTION_ON:
+ return sprintf(buf, "%d\n", st->mot_int.mot_on);
+ case ATTR_MOTION_DURATION:
+ return sprintf(buf, "%d\n", st->mot_int.mot_dur);
+ case ATTR_MOTION_THRESHOLD:
+ return sprintf(buf, "%d\n", st->mot_int.mot_thr);
+#ifdef CONFIG_INV_TESTING
+ case ATTR_REG_WRITE:
+ return sprintf(buf, "1\n");
+#endif
+ default:
+ return -EPERM;
+ }
+}
+
+/**
+ * inv_dmp_display_orient_show() - calling this function will
+ * show orientation This event must use poll.
+ */
+static ssize_t inv_dmp_display_orient_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ return sprintf(buf, "%d\n", st->display_orient_data);
+}
+
+/**
+ * inv_accel_motion_show() - calling this function showes motion interrupt.
+ * This event must use poll.
+ */
+static ssize_t inv_accel_motion_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "1\n");
+}
+
+/**
+ * inv_dmp_tap_show() - calling this function will show tap
+ * This event must use poll.
+ */
+static ssize_t inv_dmp_tap_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ return sprintf(buf, "%d\n", st->tap_data);
+}
+
+/**
+ * inv_temperature_show() - Read temperature data directly from registers.
+ */
+static ssize_t inv_temperature_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev));
+ struct inv_reg_map_s *reg;
+ int result;
+ short temp;
+ long scale_t;
+ u8 data[2];
+ reg = &st->reg;
+
+ if (st->chip_config.is_asleep)
+ return -EPERM;
+ result = inv_i2c_read(st, reg->temperature, 2, data);
+ if (result) {
+ pr_err("Could not read temperature register.\n");
+ return result;
+ }
+ temp = (signed short)(be16_to_cpup((short *)&data[0]));
+
+ if (INV_MPU3050 == st->chip_type)
+ scale_t = MPU3050_TEMP_OFFSET +
+ inv_q30_mult((int)temp << MPU_TEMP_SHIFT,
+ MPU3050_TEMP_SCALE);
+ else
+ scale_t = MPU6050_TEMP_OFFSET +
+ inv_q30_mult((int)temp << MPU_TEMP_SHIFT,
+ MPU6050_TEMP_SCALE);
+
+ INV_I2C_INC_TEMPREAD(1);
+
+ return sprintf(buf, "%ld %lld\n", scale_t, get_time_ns());
+}
+
+/**
+ * inv_firmware_loaded() - calling this function will change
+ * firmware load
+ */
+static int inv_firmware_loaded(struct inv_mpu_iio_s *st, int data)
+{
+ if (data)
+ return -EINVAL;
+ st->chip_config.firmware_loaded = 0;
+ st->chip_config.dmp_on = 0;
+ st->chip_config.quaternion_on = 0;
+
+ return 0;
+}
+
+/**
+ * inv_quaternion_on() - calling this function will store
+ * current quaternion on
+ */
+static int inv_quaternion_on(struct inv_mpu_iio_s *st,
+ struct iio_buffer *ring, bool en)
+{
+ st->chip_config.quaternion_on = en;
+ if (!en) {
+ 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_switch_gyro_engine(struct inv_mpu_iio_s *st, bool en)
+{
+ return inv_switch_engine(st, en, BIT_PWR_GYRO_STBY);
+}
+
+static int inv_switch_accl_engine(struct inv_mpu_iio_s *st, bool en)
+{
+ return inv_switch_engine(st, en, BIT_PWR_ACCL_STBY);
+}
+
+/**
+ * inv_gyro_enable() - Enable/disable gyro.
+ */
+static int inv_gyro_enable(struct inv_mpu_iio_s *st,
+ struct iio_buffer *ring, bool en)
+{
+ int result;
+ if (en == st->chip_config.gyro_enable)
+ return 0;
+ result = st->switch_gyro_engine(st, en);
+ if (result)
+ return result;
+
+ if (!en) {
+ st->chip_config.gyro_fifo_enable = 0;
+ clear_bit(INV_MPU_SCAN_GYRO_X, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_GYRO_Y, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_GYRO_Z, ring->scan_mask);
+ }
+ st->chip_config.gyro_enable = en;
+
+ return 0;
+}
+
+/**
+ * inv_accl_enable() - Enable/disable accl.
+ */
+static ssize_t inv_accl_enable(struct inv_mpu_iio_s *st,
+ struct iio_buffer *ring, bool en)
+{
+ int result;
+ if (en == st->chip_config.accl_enable)
+ return 0;
+ result = st->switch_accl_engine(st, en);
+ if (result)
+ return result;
+ st->chip_config.accl_enable = en;
+ if (!en) {
+ st->chip_config.accl_fifo_enable = 0;
+ clear_bit(INV_MPU_SCAN_ACCL_X, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_ACCL_Y, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_ACCL_Z, ring->scan_mask);
+ }
+
+ return 0;
+}
+
+/**
+ * inv_compass_enable() - calling this function will store compass
+ * enable
+ */
+static ssize_t inv_compass_enable(struct inv_mpu_iio_s *st,
+ struct iio_buffer *ring, bool en)
+{
+ if (en == st->chip_config.compass_enable)
+ return 0;
+ st->chip_config.compass_enable = en;
+ if (!en) {
+ st->chip_config.compass_fifo_enable = 0;
+ clear_bit(INV_MPU_SCAN_MAGN_X, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_MAGN_Y, ring->scan_mask);
+ clear_bit(INV_MPU_SCAN_MAGN_Z, ring->scan_mask);
+ }
+
+ return 0;
+}
+
+/**
+ * inv_attr_store() - calling this function will store current
+ * non-dmp parameter settings
+ */
+static ssize_t inv_attr_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ int data;
+ u8 d;
+ int result;
+
+ mutex_lock(&indio_dev->mlock);
+ if (check_enable(st)) {
+ result = -EINVAL;
+ goto attr_store_fail;
+ }
+
+ result = kstrtoint(buf, 10, &data);
+ if (result)
+ goto attr_store_fail;
+ switch (this_attr->address) {
+ case ATTR_GYRO_ENABLE:
+ result = inv_gyro_enable(st, ring, !!data);
+ break;
+ case ATTR_ACCL_ENABLE:
+ result = inv_accl_enable(st, ring, !!data);
+ break;
+ case ATTR_COMPASS_ENABLE:
+ result = inv_compass_enable(st, ring, !!data);
+ break;
+ case ATTR_DMP_QUATERNION_ON:
+ result = inv_quaternion_on(st, ring, !!data);
+ break;
+ case ATTR_LPA_FREQ:
+ result = inv_lpa_freq(st, data);
+ break;
+ case ATTR_FIRMWARE_LOADED:
+ result = inv_firmware_loaded(st, data);
+ break;
+ case ATTR_MOTION_ON:
+ if (INV_MPU6500 == st->chip_type) {
+ if (data)
+ /* enable and put in MPU6500 mode */
+ d = BIT_ACCEL_INTEL_ENABLE
+ | BIT_ACCEL_INTEL_MODE;
+ else
+ d = 0;
+ result = inv_i2c_single_write(st,
+ REG_6500_ACCEL_INTEL_CTRL, d);
+ if (result)
+ goto attr_store_fail;
+ }
+ st->mot_int.mot_on = !!data;
+ st->chip_config.lpa_mode = !!data;
+ break;
+ case ATTR_MOTION_DURATION:
+ if (INV_MPU6500 != st->chip_type) {
+ result = inv_i2c_single_write(st, REG_ACCEL_MOT_DUR,
+ MPU6050_MOTION_DUR_DEFAULT);
+ if (result)
+ goto attr_store_fail;
+ }
+ st->mot_int.mot_dur = data;
+ break;
+ case ATTR_MOTION_THRESHOLD:
+ if ((data > MPU6XXX_MAX_MOTION_THRESH) || (data < 0)) {
+ result = -EINVAL;
+ goto attr_store_fail;
+ }
+ d = (u8)(data >> MPU6XXX_MOTION_THRESH_SHIFT);
+ data = (d << MPU6XXX_MOTION_THRESH_SHIFT);
+ result = inv_i2c_single_write(st, REG_ACCEL_MOT_THR, d);
+ if (result)
+ goto attr_store_fail;
+ st->mot_int.mot_thr = data;
+ break;
+ default:
+ result = -EINVAL;
+ goto attr_store_fail;
+ };
+ if (result)
+ goto attr_store_fail;
+ result = count;
+
+attr_store_fail:
+ mutex_unlock(&indio_dev->mlock);
+
+ return result;
+}
+
+#ifdef CONFIG_INV_TESTING
+/**
+ * inv_reg_write_store() - register write command for testing.
+ * Format: WSRRDD, where RR is the register in hex,
+ * and DD is the data in hex.
+ */
+static ssize_t inv_reg_write_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ u32 result;
+ u8 wreg, wval;
+ int temp;
+ char local_buf[10];
+
+ if ((buf[0] != 'W' && buf[0] != 'w') ||
+ (buf[1] != 'S' && buf[1] != 's'))
+ return -EINVAL;
+ if (strlen(buf) < 6)
+ return -EINVAL;
+
+ strncpy(local_buf, buf, 7);
+ local_buf[6] = 0;
+ result = sscanf(&local_buf[4], "%x", &temp);
+ if (result == 0)
+ return -EINVAL;
+ wval = temp;
+ local_buf[4] = 0;
+ sscanf(&local_buf[2], "%x", &temp);
+ if (result == 0)
+ return -EINVAL;
+ wreg = temp;
+
+ result = inv_i2c_single_write(st, wreg, wval);
+ if (result)
+ return result;
+
+ return count;
+}
+#endif /* CONFIG_INV_TESTING */
+
+#define INV_MPU_CHAN(_type, _channel2, _index) \
+ { \
+ .type = _type, \
+ .modified = 1, \
+ .channel2 = _channel2, \
+ .info_mask = (IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \
+ IIO_CHAN_INFO_SCALE_SHARED_BIT), \
+ .scan_index = _index, \
+ .scan_type = IIO_ST('s', 16, 16, 0) \
+ }
+
+#define INV_ACCL_CHAN(_type, _channel2, _index) \
+ { \
+ .type = _type, \
+ .modified = 1, \
+ .channel2 = _channel2, \
+ .info_mask = (IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \
+ IIO_CHAN_INFO_SCALE_SHARED_BIT | \
+ IIO_CHAN_INFO_OFFSET_SEPARATE_BIT), \
+ .scan_index = _index, \
+ .scan_type = IIO_ST('s', 16, 16, 0) \
+ }
+
+#define INV_MPU_QUATERNION_CHAN(_channel2, _index) \
+ { \
+ .type = IIO_QUATERNION, \
+ .modified = 1, \
+ .channel2 = _channel2, \
+ .scan_index = _index, \
+ .scan_type = IIO_ST('s', 32, 32, 0) \
+ }
+
+#define INV_MPU_MAGN_CHAN(_channel2, _index) \
+ { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel2 = _channel2, \
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \
+ .scan_index = _index, \
+ .scan_type = IIO_ST('s', 16, 16, 0) \
+ }
+
+static const struct iio_chan_spec inv_mpu_channels[] = {
+ IIO_CHAN_SOFT_TIMESTAMP(INV_MPU_SCAN_TIMESTAMP),
+
+ INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU_SCAN_GYRO_X),
+ INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU_SCAN_GYRO_Y),
+ INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU_SCAN_GYRO_Z),
+
+ INV_ACCL_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU_SCAN_ACCL_X),
+ INV_ACCL_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU_SCAN_ACCL_Y),
+ INV_ACCL_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU_SCAN_ACCL_Z),
+
+ INV_MPU_QUATERNION_CHAN(IIO_MOD_R, INV_MPU_SCAN_QUAT_R),
+ INV_MPU_QUATERNION_CHAN(IIO_MOD_X, INV_MPU_SCAN_QUAT_X),
+ INV_MPU_QUATERNION_CHAN(IIO_MOD_Y, INV_MPU_SCAN_QUAT_Y),
+ INV_MPU_QUATERNION_CHAN(IIO_MOD_Z, INV_MPU_SCAN_QUAT_Z),
+
+ INV_MPU_MAGN_CHAN(IIO_MOD_X, INV_MPU_SCAN_MAGN_X),
+ INV_MPU_MAGN_CHAN(IIO_MOD_Y, INV_MPU_SCAN_MAGN_Y),
+ INV_MPU_MAGN_CHAN(IIO_MOD_Z, INV_MPU_SCAN_MAGN_Z),
+};
+
+/*constant IIO attribute */
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 20 50 100 200 500");
+static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, inv_fifo_rate_show,
+ inv_fifo_rate_store);
+static DEVICE_ATTR(temperature, S_IRUGO, inv_temperature_show, NULL);
+static IIO_DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_power_state_store, ATTR_POWER_STATE);
+static IIO_DEVICE_ATTR(firmware_loaded, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_FIRMWARE_LOADED);
+static IIO_DEVICE_ATTR(motion_lpa_freq, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_LPA_FREQ);
+static IIO_DEVICE_ATTR(motion_lpa_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_MOTION_ON);
+static IIO_DEVICE_ATTR(motion_lpa_duration, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_MOTION_DURATION);
+static IIO_DEVICE_ATTR(motion_lpa_threshold, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_MOTION_THRESHOLD);
+static DEVICE_ATTR(reg_dump, S_IRUGO, inv_reg_dump_show, NULL);
+static IIO_DEVICE_ATTR(self_test, S_IRUGO, inv_attr_show, NULL,
+ ATTR_SELF_TEST);
+static IIO_DEVICE_ATTR(gyro_matrix, S_IRUGO, inv_attr_show, NULL,
+ ATTR_GYRO_MATRIX);
+static IIO_DEVICE_ATTR(accl_matrix, S_IRUGO, inv_attr_show, NULL,
+ ATTR_ACCL_MATRIX);
+static IIO_DEVICE_ATTR(compass_matrix, S_IRUGO, inv_attr_show, NULL,
+ ATTR_COMPASS_MATRIX);
+static IIO_DEVICE_ATTR(dmp_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_ON);
+static IIO_DEVICE_ATTR(dmp_int_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_INT_ON);
+static IIO_DEVICE_ATTR(dmp_event_int_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_EVENT_INT_ON);
+static IIO_DEVICE_ATTR(dmp_output_rate, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_OUTPUT_RATE);
+static IIO_DEVICE_ATTR(quaternion_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_DMP_QUATERNION_ON);
+static IIO_DEVICE_ATTR(display_orientation_on, S_IRUGO | S_IWUSR,
+ inv_attr_show, inv_dmp_attr_store, ATTR_DMP_DISPLAY_ORIENTATION_ON);
+static IIO_DEVICE_ATTR(tap_on, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_TAP_ON);
+static IIO_DEVICE_ATTR(tap_time, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_TAP_TIME);
+static IIO_DEVICE_ATTR(tap_min_count, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_TAP_MIN_COUNT);
+static IIO_DEVICE_ATTR(tap_threshold, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_TAP_THRESHOLD);
+static IIO_DEVICE_ATTR(pedometer_time, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_PEDOMETER_TIME);
+static IIO_DEVICE_ATTR(pedometer_steps, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_dmp_attr_store, ATTR_DMP_PEDOMETER_STEPS);
+static DEVICE_ATTR(event_tap, S_IRUGO, inv_dmp_tap_show, NULL);
+static DEVICE_ATTR(event_display_orientation, S_IRUGO,
+ inv_dmp_display_orient_show, NULL);
+static DEVICE_ATTR(event_accel_motion, S_IRUGO, inv_accel_motion_show, NULL);
+static IIO_DEVICE_ATTR(gyro_enable, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_GYRO_ENABLE);
+static IIO_DEVICE_ATTR(accl_enable, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_ACCL_ENABLE);
+static IIO_DEVICE_ATTR(compass_enable, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_attr_store, ATTR_COMPASS_ENABLE);
+#ifdef CONFIG_INV_TESTING
+static IIO_DEVICE_ATTR(reg_write, S_IRUGO | S_IWUSR, inv_attr_show,
+ inv_reg_write_store, ATTR_REG_WRITE);
+#endif
+
+static const struct attribute *inv_gyro_attributes[] = {
+ &iio_dev_attr_gyro_enable.dev_attr.attr,
+ &dev_attr_temperature.attr,
+ &iio_dev_attr_power_state.dev_attr.attr,
+ &dev_attr_reg_dump.attr,
+ &iio_dev_attr_self_test.dev_attr.attr,
+ &iio_dev_attr_gyro_matrix.dev_attr.attr,
+#ifdef CONFIG_INV_TESTING
+ &iio_dev_attr_reg_write.dev_attr.attr,
+#endif
+ &iio_dev_attr_sampling_frequency.dev_attr.attr,
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+};
+
+static const struct attribute *inv_mpu6050_attributes[] = {
+ &iio_dev_attr_accl_enable.dev_attr.attr,
+ &iio_dev_attr_accl_matrix.dev_attr.attr,
+ &iio_dev_attr_firmware_loaded.dev_attr.attr,
+ &iio_dev_attr_motion_lpa_freq.dev_attr.attr,
+ &iio_dev_attr_motion_lpa_on.dev_attr.attr,
+ &iio_dev_attr_motion_lpa_duration.dev_attr.attr,
+ &iio_dev_attr_motion_lpa_threshold.dev_attr.attr,
+ &iio_dev_attr_dmp_on.dev_attr.attr,
+ &iio_dev_attr_dmp_int_on.dev_attr.attr,
+ &iio_dev_attr_dmp_event_int_on.dev_attr.attr,
+ &iio_dev_attr_dmp_output_rate.dev_attr.attr,
+ &iio_dev_attr_quaternion_on.dev_attr.attr,
+ &iio_dev_attr_display_orientation_on.dev_attr.attr,
+ &iio_dev_attr_tap_on.dev_attr.attr,
+ &iio_dev_attr_tap_time.dev_attr.attr,
+ &iio_dev_attr_tap_min_count.dev_attr.attr,
+ &iio_dev_attr_tap_threshold.dev_attr.attr,
+ &iio_dev_attr_pedometer_time.dev_attr.attr,
+ &iio_dev_attr_pedometer_steps.dev_attr.attr,
+ &dev_attr_event_display_orientation.attr,
+ &dev_attr_event_tap.attr,
+ &dev_attr_event_accel_motion.attr,
+};
+
+static const struct attribute *inv_compass_attributes[] = {
+ &iio_dev_attr_compass_matrix.dev_attr.attr,
+ &iio_dev_attr_compass_enable.dev_attr.attr,
+};
+
+static const struct attribute *inv_mpu3050_attributes[] = {
+ &iio_dev_attr_accl_matrix.dev_attr.attr,
+ &iio_dev_attr_accl_enable.dev_attr.attr,
+};
+
+static struct attribute *inv_attributes[ARRAY_SIZE(inv_gyro_attributes) +
+ ARRAY_SIZE(inv_mpu6050_attributes) +
+ ARRAY_SIZE(inv_compass_attributes) + 1];
+
+static const struct attribute_group inv_attribute_group = {
+ .name = "mpu",
+ .attrs = inv_attributes
+};
+
+static const struct iio_info mpu_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = &mpu_read_raw,
+ .write_raw = &mpu_write_raw,
+ .attrs = &inv_attribute_group,
+};
+
+/**
+ * inv_setup_compass() - Configure compass.
+ */
+static int inv_setup_compass(struct inv_mpu_iio_s *st)
+{
+ int result;
+ u8 data[4];
+
+ result = inv_i2c_read(st, REG_YGOFFS_TC, 1, data);
+ if (result)
+ return result;
+ data[0] &= ~BIT_I2C_MST_VDDIO;
+ if (st->plat_data.level_shifter)
+ data[0] |= BIT_I2C_MST_VDDIO;
+ /*set up VDDIO register */
+ result = inv_i2c_single_write(st, REG_YGOFFS_TC, data[0]);
+ if (result)
+ return result;
+ /* set to bypass mode */
+ result = inv_i2c_single_write(st, REG_INT_PIN_CFG,
+ st->plat_data.int_config | BIT_BYPASS_EN);
+ if (result)
+ return result;
+ /*read secondary i2c ID register */
+ result = inv_secondary_read(REG_AKM_ID, 1, data);
+ if (result)
+ return result;
+ if (data[0] != DATA_AKM_ID)
+ return -ENXIO;
+ /*set AKM to Fuse ROM access mode */
+ result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_FR);
+ if (result)
+ return result;
+ result = inv_secondary_read(REG_AKM_SENSITIVITY, THREE_AXIS,
+ st->chip_info.compass_sens);
+ if (result)
+ return result;
+ /*revert to power down mode */
+ result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD);
+ if (result)
+ return result;
+ pr_debug("%s senx=%d, seny=%d, senz=%d\n",
+ st->hw->name,
+ st->chip_info.compass_sens[0],
+ st->chip_info.compass_sens[1],
+ st->chip_info.compass_sens[2]);
+ /*restore to non-bypass mode */
+ result = inv_i2c_single_write(st, REG_INT_PIN_CFG,
+ st->plat_data.int_config);
+ if (result)
+ return result;
+
+ /*setup master mode and master clock and ES bit*/
+ result = inv_i2c_single_write(st, REG_I2C_MST_CTRL, BIT_WAIT_FOR_ES);
+ if (result)
+ return result;
+ /* slave 0 is used to read data from compass */
+ /*read mode */
+ result = inv_i2c_single_write(st, REG_I2C_SLV0_ADDR, BIT_I2C_READ|
+ st->plat_data.secondary_i2c_addr);
+ if (result)
+ return result;
+ /* AKM status register address is 2 */
+ result = inv_i2c_single_write(st, REG_I2C_SLV0_REG, REG_AKM_STATUS);
+ if (result)
+ return result;
+ /* slave 0 is enabled at the beginning, read 8 bytes from here */
+ result = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, BIT_SLV_EN |
+ NUM_BYTES_COMPASS_SLAVE);
+ if (result)
+ return result;
+ /*slave 1 is used for AKM mode change only*/
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_ADDR,
+ st->plat_data.secondary_i2c_addr);
+ if (result)
+ return result;
+ /* AKM mode register address is 0x0A */
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_REG, REG_AKM_MODE);
+ if (result)
+ return result;
+ /* slave 1 is enabled, byte length is 1 */
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_CTRL, BIT_SLV_EN | 1);
+ if (result)
+ return result;
+ /* output data for slave 1 is fixed, single measure mode*/
+ st->compass_scale = 1;
+ if (COMPASS_ID_AK8975 == st->plat_data.sec_slave_id) {
+ st->compass_st_upper = AKM8975_ST_Upper;
+ st->compass_st_lower = AKM8975_ST_Lower;
+ data[0] = DATA_AKM_MODE_SM;
+ } else if (COMPASS_ID_AK8972 == st->plat_data.sec_slave_id) {
+ st->compass_st_upper = AKM8972_ST_Upper;
+ st->compass_st_lower = AKM8972_ST_Lower;
+ data[0] = DATA_AKM_MODE_SM;
+ } else if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) {
+ st->compass_st_upper = AKM8963_ST_Upper;
+ st->compass_st_lower = AKM8963_ST_Lower;
+ data[0] = DATA_AKM_MODE_SM |
+ (st->compass_scale << AKM8963_SCALE_SHIFT);
+ }
+ result = inv_i2c_single_write(st, REG_I2C_SLV1_DO, data[0]);
+ if (result)
+ return result;
+ /* slave 0 and 1 timer action is enabled every sample*/
+ result = inv_i2c_single_write(st, REG_I2C_MST_DELAY_CTRL,
+ BIT_SLV0_DLY_EN | BIT_SLV1_DLY_EN);
+ return result;
+}
+
+static void inv_setup_func_ptr(struct inv_mpu_iio_s *st)
+{
+ if (st->chip_type == INV_MPU3050) {
+ st->set_power_state = set_power_mpu3050;
+ st->switch_gyro_engine = inv_switch_3050_gyro_engine;
+ st->switch_accl_engine = inv_switch_3050_accl_engine;
+ st->init_config = inv_init_config_mpu3050;
+ st->setup_reg = inv_setup_reg_mpu3050;
+ } else {
+ st->set_power_state = set_power_itg;
+ st->switch_gyro_engine = inv_switch_gyro_engine;
+ st->switch_accl_engine = inv_switch_accl_engine;
+ st->init_config = inv_init_config;
+ st->setup_reg = inv_setup_reg;
+ /*MPU6XXX special functions */
+ st->compass_en = inv_compass_enable;
+ st->quaternion_en = inv_quaternion_on;
+ st->gyro_en = inv_gyro_enable;
+ st->accl_en = inv_accl_enable;
+ }
+}
+
+static int inv_detect_6xxx(struct inv_mpu_iio_s *st)
+{
+ int result;
+ u8 d;
+
+ result = inv_i2c_read(st, REG_WHOAMI, 1, &d);
+ if (result)
+ return result;
+ if (d == MPU6500_ID) {
+ st->chip_type = INV_MPU6500;
+ strcpy(st->name, "mpu6500");
+ } else {
+ strcpy(st->name, "mpu6050");
+ }
+
+ return 0;
+}
+
+/**
+ * inv_check_chip_type() - check and setup chip type.
+ */
+static int inv_check_chip_type(struct inv_mpu_iio_s *st,
+ const struct i2c_device_id *id)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ int t_ind;
+
+ if (!strcmp(id->name, "itg3500"))
+ st->chip_type = INV_ITG3500;
+ else if (!strcmp(id->name, "mpu3050"))
+ st->chip_type = INV_MPU3050;
+ else if (!strcmp(id->name, "mpu6050"))
+ st->chip_type = INV_MPU6050;
+ else if (!strcmp(id->name, "mpu9150"))
+ st->chip_type = INV_MPU6050;
+ else if (!strcmp(id->name, "mpu6500"))
+ st->chip_type = INV_MPU6500;
+ else if (!strcmp(id->name, "mpu9250"))
+ st->chip_type = INV_MPU6500;
+ else if (!strcmp(id->name, "mpu6xxx"))
+ st->chip_type = INV_MPU6050;
+ else
+ return -EPERM;
+ inv_setup_func_ptr(st);
+ st->hw = &hw_info[st->chip_type];
+ st->mpu_slave = NULL;
+ reg = &st->reg;
+ st->setup_reg(reg);
+ st->chip_config.gyro_enable = 1;
+ /* reset to make sure previous state are not there */
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_H_RESET);
+ if (result)
+ return result;
+ msleep(POWER_UP_TIME);
+
+ /* turn off and turn on power to ensure gyro engine is on */
+ result = st->set_power_state(st, false);
+ if (result)
+ return result;
+ result = st->set_power_state(st, true);
+ if (result)
+ return result;
+
+ if (!strcmp(id->name, "mpu6xxx")) {
+ result = inv_detect_6xxx(st);
+ if (result)
+ return result;
+ }
+
+ switch (st->chip_type) {
+ case INV_ITG3500:
+ st->num_channels = INV_CHANNEL_NUM_GYRO;
+ break;
+ case INV_MPU6050:
+ case INV_MPU6500:
+ if (SECONDARY_SLAVE_TYPE_COMPASS ==
+ st->plat_data.sec_slave_type) {
+ st->chip_config.has_compass = 1;
+ st->num_channels =
+ INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION_MAGN;
+ } else {
+ st->chip_config.has_compass = 0;
+ st->num_channels =
+ INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION;
+ }
+ break;
+ case INV_MPU3050:
+ if (SECONDARY_SLAVE_TYPE_ACCEL ==
+ st->plat_data.sec_slave_type) {
+ if (ACCEL_ID_BMA250 == st->plat_data.sec_slave_id)
+ inv_register_mpu3050_slave(st);
+ st->num_channels = INV_CHANNEL_NUM_GYRO_ACCL;
+ } else {
+ st->num_channels = INV_CHANNEL_NUM_GYRO;
+ }
+ break;
+ default:
+ result = st->set_power_state(st, false);
+ return -ENODEV;
+ }
+
+ switch (st->chip_type) {
+ case INV_MPU6050:
+ result = inv_get_silicon_rev_mpu6050(st);
+ break;
+ case INV_MPU6500:
+ result = inv_get_silicon_rev_mpu6500(st);
+ break;
+ default:
+ result = 0;
+ break;
+ }
+ if (result) {
+ pr_err("read silicon rev error\n");
+ st->set_power_state(st, false);
+ return result;
+ }
+
+//wlg bypass i2c slave
+ result = inv_i2c_single_write(st, REG_INT_PIN_CFG,
+ st->plat_data.int_config | BIT_BYPASS_EN);
+ if (result)
+ return result;
+
+ if (st->chip_config.has_compass) {
+ result = inv_setup_compass(st);
+ if (result) {
+ pr_err("compass setup failed\n");
+ st->set_power_state(st, false);
+ return result;
+ }
+ }
+
+ t_ind = 0;
+ memcpy(&inv_attributes[t_ind], inv_gyro_attributes,
+ sizeof(inv_gyro_attributes));
+ t_ind += ARRAY_SIZE(inv_gyro_attributes);
+
+ if (INV_MPU3050 == st->chip_type && st->mpu_slave != NULL) {
+ memcpy(&inv_attributes[t_ind], inv_mpu3050_attributes,
+ sizeof(inv_mpu3050_attributes));
+ t_ind += ARRAY_SIZE(inv_mpu3050_attributes);
+ inv_attributes[t_ind] = NULL;
+ return 0;
+ }
+
+ if ((INV_MPU6050 == st->chip_type) || (INV_MPU6500 == st->chip_type)) {
+ memcpy(&inv_attributes[t_ind], inv_mpu6050_attributes,
+ sizeof(inv_mpu6050_attributes));
+ t_ind += ARRAY_SIZE(inv_mpu6050_attributes);
+ }
+
+ if (st->chip_config.has_compass) {
+ memcpy(&inv_attributes[t_ind], inv_compass_attributes,
+ sizeof(inv_compass_attributes));
+ t_ind += ARRAY_SIZE(inv_compass_attributes);
+ }
+ inv_attributes[t_ind] = NULL;
+
+ return 0;
+}
+
+/**
+ * inv_create_dmp_sysfs() - create binary sysfs dmp entry.
+ */
+static const struct bin_attribute dmp_firmware = {
+ .attr = {
+ .name = "dmp_firmware",
+ .mode = S_IRUGO | S_IWUSR
+ },
+ .size = 4096,
+ .read = inv_dmp_firmware_read,
+ .write = inv_dmp_firmware_write,
+};
+
+static int inv_create_dmp_sysfs(struct iio_dev *ind)
+{
+ int result;
+ result = sysfs_create_bin_file(&ind->dev.kobj, &dmp_firmware);
+
+ return result;
+}
+static void inv_mpu_hw_init(struct i2c_client *client)
+{
+ struct regulator *reg_vdd;
+ struct mpu_platform_data *pdata = client->dev.platform_data;
+ reg_vdd = regulator_get(&client->dev, pdata->vdd_name);
+ if (!WARN(IS_ERR(reg_vdd), "inv_mpu regulator: failed to get %s.\n", pdata->vdd_name)) {
+ regulator_set_voltage(reg_vdd, 1800000, 1800000);
+ regulator_enable(reg_vdd);
+ }
+ msleep(100);
+}
+
+/**
+ * inv_mpu_probe() - probe function.
+ */
+static int inv_mpu_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct inv_mpu_iio_s *st;
+ struct iio_dev *indio_dev;
+ int result;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ result = -ENOSYS;
+ pr_err("I2c function error\n");
+ goto out_no_free;
+ }
+ indio_dev = iio_device_alloc(sizeof(*st));
+ if (indio_dev == NULL) {
+ pr_err("memory allocation failed\n");
+ result = -ENOMEM;
+ goto out_no_free;
+ }
+ st = iio_priv(indio_dev);
+ st->client = client;
+ st->sl_handle = client->adapter;
+ st->i2c_addr = client->addr;
+ st->plat_data =
+ *(struct mpu_platform_data *)dev_get_platdata(&client->dev);
+ inv_mpu_hw_init(client);
+
+ /* power is turned on inside check chip type*/
+ result = inv_check_chip_type(st, id);
+ if (result)
+ goto out_free;
+
+ result = st->init_config(indio_dev);
+ if (result) {
+ dev_err(&client->adapter->dev,
+ "Could not initialize device.\n");
+ goto out_free;
+ }
+ result = st->set_power_state(st, false);
+ if (result) {
+ dev_err(&client->adapter->dev,
+ "%s could not be turned off.\n", st->hw->name);
+ goto out_free;
+ }
+ /* Make state variables available to all _show and _store functions. */
+ i2c_set_clientdata(client, indio_dev);
+ indio_dev->dev.parent = &client->dev;
+ if (!strcmp(id->name, "mpu6xxx"))
+ indio_dev->name = st->name;
+ else
+ indio_dev->name = id->name;
+ indio_dev->channels = inv_mpu_channels;
+ indio_dev->num_channels = st->num_channels;
+
+ indio_dev->info = &mpu_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->currentmode = INDIO_DIRECT_MODE;
+
+ result = inv_mpu_configure_ring(indio_dev);
+ if (result) {
+ pr_err("configure ring buffer fail\n");
+ goto out_free;
+ }
+ result = iio_buffer_register(indio_dev, indio_dev->channels,
+ indio_dev->num_channels);
+ if (result) {
+ pr_err("ring buffer register fail\n");
+ goto out_unreg_ring;
+ }
+ st->irq = gpio_to_irq(client->irq);
+ result = inv_mpu_probe_trigger(indio_dev);
+ if (result) {
+ pr_err("trigger probe fail\n");
+ goto out_remove_ring;
+ }
+
+ /* Tell the i2c counter, we have an IRQ */
+ INV_I2C_SETIRQ(MPU, gpio_to_irq(client->irq));
+
+ result = iio_device_register(indio_dev);
+ if (result) {
+ pr_err("IIO device register fail\n");
+ goto out_remove_trigger;
+ }
+
+ if (INV_MPU6050 == st->chip_type ||
+ INV_MPU6500 == st->chip_type) {
+ result = inv_create_dmp_sysfs(indio_dev);
+ if (result) {
+ pr_err("create dmp sysfs failed\n");
+ goto out_unreg_iio;
+ }
+ }
+
+ INIT_KFIFO(st->timestamps);
+ spin_lock_init(&st->time_stamp_lock);
+ dev_info(&client->adapter->dev, "%s is ready to go!\n",
+ indio_dev->name);
+
+ return 0;
+out_unreg_iio:
+ iio_device_unregister(indio_dev);
+out_remove_trigger:
+ if (indio_dev->modes & INDIO_BUFFER_TRIGGERED)
+ inv_mpu_remove_trigger(indio_dev);
+out_remove_ring:
+ iio_buffer_unregister(indio_dev);
+out_unreg_ring:
+ inv_mpu_unconfigure_ring(indio_dev);
+out_free:
+ iio_device_free(indio_dev);
+out_no_free:
+ dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+
+ return -EIO;
+}
+
+static void inv_mpu_shutdown(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ struct inv_reg_map_s *reg;
+ int result;
+
+ reg = &st->reg;
+ dev_dbg(&client->adapter->dev, "Shutting down %s...\n", st->hw->name);
+
+ /* reset to make sure previous state are not there */
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_H_RESET);
+ if (result)
+ dev_err(&client->adapter->dev, "Failed to reset %s\n",
+ st->hw->name);
+ msleep(POWER_UP_TIME);
+ /* turn off power to ensure gyro engine is off */
+ result = st->set_power_state(st, false);
+ if (result)
+ dev_err(&client->adapter->dev, "Failed to turn off %s\n",
+ st->hw->name);
+}
+
+/**
+ * inv_mpu_remove() - remove function.
+ */
+static int inv_mpu_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+ kfifo_free(&st->timestamps);
+ iio_device_unregister(indio_dev);
+ if (indio_dev->modes & INDIO_BUFFER_TRIGGERED)
+ inv_mpu_remove_trigger(indio_dev);
+ iio_buffer_unregister(indio_dev);
+ inv_mpu_unconfigure_ring(indio_dev);
+ iio_device_free(indio_dev);
+
+ dev_info(&client->adapter->dev, "inv-mpu-iio module removed.\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int inv_mpu_resume(struct device *dev)
+{
+ struct inv_mpu_iio_s *st =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+ pr_debug("%s inv_mpu_resume\n", st->hw->name);
+ return st->set_power_state(st, true);
+}
+
+static int inv_mpu_suspend(struct device *dev)
+{
+ struct inv_mpu_iio_s *st =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+ pr_debug("%s inv_mpu_suspend\n", st->hw->name);
+ return st->set_power_state(st, false);
+}
+static const struct dev_pm_ops inv_mpu_pmops = {
+ SET_SYSTEM_SLEEP_PM_OPS(inv_mpu_suspend, inv_mpu_resume)
+};
+#define INV_MPU_PMOPS (&inv_mpu_pmops)
+#else
+#define INV_MPU_PMOPS NULL
+#endif /* CONFIG_PM */
+
+static const u16 normal_i2c[] = { I2C_CLIENT_END };
+/* device id table is used to identify what device can be
+ * supported by this driver
+ */
+static const struct i2c_device_id inv_mpu_id[] = {
+ {"itg3500", INV_ITG3500},
+ {"mpu3050", INV_MPU3050},
+ {"mpu6050", INV_MPU6050},
+ {"mpu9150", INV_MPU9150},
+ {"mpu6500", INV_MPU6500},
+ {"mpu9250", INV_MPU9250},
+ {"mpu6xxx", INV_MPU6XXX},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, inv_mpu_id);
+
+static struct i2c_driver inv_mpu_driver = {
+ .class = I2C_CLASS_HWMON,
+ .probe = inv_mpu_probe,
+ .remove = inv_mpu_remove,
+ .shutdown = inv_mpu_shutdown,
+ .id_table = inv_mpu_id,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "inv-mpu-iio",
+ .pm = INV_MPU_PMOPS,
+ },
+ .address_list = normal_i2c,
+};
+
+static int __init inv_mpu_init(void)
+{
+ int result = i2c_add_driver(&inv_mpu_driver);
+ if (result) {
+ pr_err("failed\n");
+ return result;
+ }
+ return 0;
+}
+
+static void __exit inv_mpu_exit(void)
+{
+ i2c_del_driver(&inv_mpu_driver);
+}
+
+module_init(inv_mpu_init);
+module_exit(inv_mpu_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Invensense device driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("inv-mpu-iio");
+
+/**
+ * @}
+ */