summaryrefslogtreecommitdiff
path: root/drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c')
-rw-r--r--drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c292
1 files changed, 292 insertions, 0 deletions
diff --git a/drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c b/drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c
new file mode 100644
index 00000000..920c9dde
--- /dev/null
+++ b/drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c
@@ -0,0 +1,292 @@
+/*
+* 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_mpu3050_iio.c
+ * @brief A sysfs device driver for Invensense devices
+ * @details This file is part of invensense mpu driver code
+ */
+
+#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 "inv_mpu_iio.h"
+#define MPU3050_NACK_MIN_TIME (2 * 1000)
+#define MPU3050_NACK_MAX_TIME (3 * 1000)
+
+#define MPU3050_ONE_MPU_TIME 20
+#define MPU3050_BOGUS_ADDR 0x7F
+int __attribute__((weak)) inv_register_mpu3050_slave(struct inv_mpu_iio_s *st)
+{
+ return 0;
+}
+
+int set_3050_bypass(struct inv_mpu_iio_s *st, bool enable)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ u8 b;
+
+ reg = &st->reg;
+ result = inv_i2c_read(st, reg->user_ctrl, 1, &b);
+ if (result)
+ return result;
+ if (((b & BIT_3050_AUX_IF_EN) == 0) && enable)
+ return 0;
+ if ((b & BIT_3050_AUX_IF_EN) && (enable == 0))
+ return 0;
+ b &= ~BIT_3050_AUX_IF_EN;
+ if (!enable) {
+ b |= BIT_3050_AUX_IF_EN;
+ result = inv_i2c_single_write(st, reg->user_ctrl, b);
+ return result;
+ } else {
+ /* Coming out of I2C is tricky due to several erratta. Do not
+ * modify this algorithm
+ */
+ /*
+ * 1) wait for the right time and send the command to change
+ * the aux i2c slave address to an invalid address that will
+ * get nack'ed
+ *
+ * 0x00 is broadcast. 0x7F is unlikely to be used by any aux.
+ */
+ result = inv_i2c_single_write(st, REG_3050_SLAVE_ADDR,
+ MPU3050_BOGUS_ADDR);
+ if (result)
+ return result;
+ /*
+ * 2) wait enough time for a nack to occur, then go into
+ * bypass mode:
+ */
+ usleep_range(MPU3050_NACK_MIN_TIME, MPU3050_NACK_MAX_TIME);
+ result = inv_i2c_single_write(st, reg->user_ctrl, b);
+ if (result)
+ return result;
+ /*
+ * 3) wait for up to one MPU cycle then restore the slave
+ * address
+ */
+ msleep(MPU3050_ONE_MPU_TIME);
+
+ result = inv_i2c_single_write(st, REG_3050_SLAVE_ADDR,
+ st->plat_data.secondary_i2c_addr);
+ if (result)
+ return result;
+ result = inv_i2c_single_write(st, reg->user_ctrl, b);
+ if (result)
+ return result;
+ usleep_range(MPU3050_NACK_MIN_TIME, MPU3050_NACK_MAX_TIME);
+ }
+ return 0;
+}
+
+void inv_setup_reg_mpu3050(struct inv_reg_map_s *reg)
+{
+ reg->fifo_en = REG_3050_FIFO_EN;
+ reg->sample_rate_div = REG_3050_SAMPLE_RATE_DIV;
+ reg->lpf = REG_3050_LPF;
+ reg->fifo_count_h = REG_3050_FIFO_COUNT_H;
+ reg->fifo_r_w = REG_3050_FIFO_R_W;
+ reg->user_ctrl = REG_3050_USER_CTRL;
+ reg->pwr_mgmt_1 = REG_3050_PWR_MGMT_1;
+ reg->raw_gyro = REG_3050_RAW_GYRO;
+ reg->raw_accl = REG_3050_AUX_XOUT_H;
+ reg->temperature = REG_3050_TEMPERATURE;
+ reg->int_enable = REG_3050_INT_ENABLE;
+ reg->int_status = REG_3050_INT_STATUS;
+}
+
+int inv_switch_3050_gyro_engine(struct inv_mpu_iio_s *st, bool en)
+{
+ struct inv_reg_map_s *reg;
+ u8 data, p;
+ int result;
+ reg = &st->reg;
+ if (en) {
+ data = INV_CLK_PLL;
+ p = (BITS_3050_POWER1 | data);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p);
+ if (result)
+ return result;
+ p = (BITS_3050_POWER2 | data);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p);
+ if (result)
+ return result;
+ p = data;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p);
+ msleep(SENSOR_UP_TIME);
+ } else {
+ p = BITS_3050_GYRO_STANDBY;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p);
+ }
+
+ return result;
+}
+
+int inv_switch_3050_accl_engine(struct inv_mpu_iio_s *st, bool en)
+{
+ int result;
+ if (NULL == st->mpu_slave)
+ return -EPERM;
+ if (en)
+ result = st->mpu_slave->resume(st);
+ else
+ result = st->mpu_slave->suspend(st);
+
+ return result;
+}
+
+/**
+ * inv_init_config_mpu3050() - Initialize hardware, disable FIFO.
+ * @st: Device driver instance.
+ * Initial configuration:
+ * FSR: +/- 2000DPS
+ * DLPF: 42Hz
+ * FIFO rate: 50Hz
+ * Clock source: Gyro PLL
+ */
+int inv_init_config_mpu3050(struct iio_dev *indio_dev)
+{
+ struct inv_reg_map_s *reg;
+ int result;
+ u8 data;
+ struct inv_mpu_iio_s *st = iio_priv(indio_dev);
+
+ if (st->chip_config.is_asleep)
+ return -EPERM;
+ /*reading AUX VDDIO register */
+ result = inv_i2c_read(st, REG_3050_AUX_VDDIO, 1, &data);
+ if (result)
+ return result;
+ data &= ~BIT_3050_VDDIO;
+ if (st->plat_data.level_shifter)
+ data |= BIT_3050_VDDIO;
+ result = inv_i2c_single_write(st, REG_3050_AUX_VDDIO, data);
+ if (result)
+ return result;
+
+ reg = &st->reg;
+ result = set_inv_enable(indio_dev, false);
+ if (result)
+ return result;
+ /*2000dps full scale range*/
+ result = inv_i2c_single_write(st, reg->lpf,
+ (INV_FSR_2000DPS << GYRO_CONFIG_FSR_SHIFT)
+ | INV_FILTER_42HZ);
+ if (result)
+ return result;
+ st->chip_config.fsr = INV_FSR_2000DPS;
+ 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;
+ if ((SECONDARY_SLAVE_TYPE_ACCEL == st->plat_data.sec_slave_type) &&
+ st->mpu_slave) {
+ result = st->mpu_slave->setup(st);
+ if (result)
+ return result;
+ result = st->mpu_slave->set_fs(st, INV_FS_02G);
+ if (result)
+ return result;
+ result = st->mpu_slave->set_lpf(st, INIT_FIFO_RATE);
+ if (result)
+ return result;
+ st->chip_config.accl_enable = 1;
+ st->chip_config.accl_fifo_enable = 1;
+ }
+
+ return 0;
+}
+
+/**
+ * set_power_mpu3050() - set power of mpu3050.
+ * @st: Device driver instance.
+ * @power_on: on/off
+ */
+int set_power_mpu3050(struct inv_mpu_iio_s *st, bool power_on)
+{
+ struct inv_reg_map_s *reg;
+ u8 data, p;
+ int result;
+ reg = &st->reg;
+ if (power_on) {
+ data = 0;
+ } else {
+ if (st->mpu_slave) {
+ result = st->mpu_slave->suspend(st);
+ if (result)
+ return result;
+ }
+ data = BIT_SLEEP;
+ }
+ if (st->chip_config.gyro_enable) {
+ p = (BITS_3050_POWER1 | INV_CLK_PLL);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p);
+ if (result)
+ return result;
+
+ p = (BITS_3050_POWER2 | INV_CLK_PLL);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p);
+ if (result)
+ return result;
+
+ p = INV_CLK_PLL;
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p);
+ if (result)
+ return result;
+ } else {
+ data |= (BITS_3050_GYRO_STANDBY | INV_CLK_INTERNAL);
+ result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data);
+ if (result)
+ return result;
+ }
+ if (power_on) {
+ msleep(POWER_UP_TIME);
+ if (st->mpu_slave) {
+ result = st->mpu_slave->resume(st);
+ if (result)
+ return result;
+ }
+ }
+ st->chip_config.is_asleep = !power_on;
+
+ return 0;
+}
+/**
+ * @}
+ */
+