diff options
author | Robert Collins <rcollins@nvidia.com> | 2011-12-01 16:18:19 -0800 |
---|---|---|
committer | Simone Willett <swillett@nvidia.com> | 2012-02-14 09:46:41 -0800 |
commit | 2f3c7d00ed29fdb6ef107c4b304c1464153114d6 (patch) | |
tree | 2941e994734aabddee03925477834c4cf2ea4af0 | |
parent | 8f4d3050455e55864262a5d5a1e249eadca79dd0 (diff) |
mpu6050: Motion Libraries: Add kernel files for Invensense MPU6050.
Invensense kernel files for MPU6050. MPL version 4.1.1.
Bug 825602
Signed-off-by: Robert Collins <rcollins@nvidia.com>
Reviewed-on: http://git-master/r/70372
(cherry picked from commit 8852d46c35632f960aa1753097bb9d1c448d3602)
Change-Id: I1660a52debaaae39af94199a08e1f3eb88f08b6e
Signed-off-by: Pritesh Raithatha <praithatha@nvidia.com>
Reviewed-on: http://git-master/r/82716
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Varun Wadekar <vwadekar@nvidia.com>
Tested-by: Varun Wadekar <vwadekar@nvidia.com>
Reviewed-by: Robert Collins <rcollins@nvidia.com>
23 files changed, 5517 insertions, 58 deletions
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index a8f6ab7fb89d..5b3728ebcaee 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -54,7 +54,7 @@ obj-$(CONFIG_APANIC) += apanic.o obj-$(CONFIG_SENSORS_AK8975) += akm8975.o obj-$(CONFIG_SENSORS_NCT1008) += nct1008.o obj-$(CONFIG_BCM4329_RFKILL) += bcm4329_rfkill.o -obj-$(CONFIG_MPU_SENSORS_MPU3050) += inv_mpu/ +obj-$(CONFIG_INV_SENSORS) += inv_mpu/ obj-$(CONFIG_TEGRA_CRYPTO_DEV) += tegra-cryptodev.o obj-$(CONFIG_TEGRA_BB_SUPPORT) += tegra-baseband/ obj-$(CONFIG_MAX1749_VIBRATOR) += max1749.o diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig index 53c7c200d453..4c231b576f68 100644 --- a/drivers/misc/inv_mpu/Kconfig +++ b/drivers/misc/inv_mpu/Kconfig @@ -6,7 +6,7 @@ config MPU_SENSORS_TIMERIRQ or timer threads. Reading from this device returns the same type of information as reading from the MPU and slave IRQ's. -menuconfig: INV_SENSORS +menuconfig INV_SENSORS tristate "Motion Processing Unit" depends on I2C default y @@ -14,12 +14,12 @@ menuconfig: INV_SENSORS if INV_SENSORS choice - prompt "MPU Master" + tristate "MPU Master" depends on I2C && INV_SENSORS default MPU_SENSORS_MPU3050 config MPU_SENSORS_MPU3050 - bool "MPU3050" + tristate "MPU3050" depends on I2C select MPU_SENSORS_MPU3050_GYRO help @@ -28,7 +28,7 @@ config MPU_SENSORS_MPU3050 will be called mpu3050. config MPU_SENSORS_MPU6050A2 - bool "MPU6050A2" + tristate "MPU6050A2" depends on I2C select MPU_SENSORS_MPU6050_GYRO help @@ -37,9 +37,9 @@ config MPU_SENSORS_MPU6050A2 will be called mpu6050a2. config MPU_SENSORS_MPU6050B1 - bool "MPU6050B1" - select MPU_SENSORS_MPU6050_GYRO + tristate "MPU6050B1" depends on I2C + select MPU_SENSORS_MPU6050_GYRO help If you say yes here you get support for the MPU6050 Gyroscope driver This driver can also be built as a module. If so, the module @@ -47,11 +47,6 @@ config MPU_SENSORS_MPU6050B1 endchoice -choice - prompt "Gyroscope Type" - depends on I2C && INV_SENSORS - default MPU_SENSORS_MPU3050_GYRO - config MPU_SENSORS_MPU3050_GYRO bool "MPU3050 built in gyroscope" depends on MPU_SENSORS_MPU3050 @@ -60,8 +55,6 @@ config MPU_SENSORS_MPU6050_GYRO bool "MPU6050 built in gyroscope" depends on MPU_SENSORS_MPU6050B1 || MPU_SENSORS_MPU6050A2 -endchoice - source "drivers/misc/inv_mpu/accel/Kconfig" source "drivers/misc/inv_mpu/compass/Kconfig" source "drivers/misc/inv_mpu/pressure/Kconfig" diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile index 248648f6567c..f10c6844a087 100644 --- a/drivers/misc/inv_mpu/Makefile +++ b/drivers/misc/inv_mpu/Makefile @@ -3,43 +3,16 @@ # # -# MPU -ifdef CONFIG_MPU_SENSORS_MPU3050 -INV_MODULE_NAME := mpu3050 -endif - -ifdef CONFIG_MPU_SENSORS_MPU6050A2 -INV_MODULE_NAME := mpu6050 -endif - -ifdef CONFIG_MPU_SENSORS_MPU6050B1 -INV_MODULE_NAME := mpu6050 -endif - -obj-$(CONFIG_INV_SENSORS) += $(INV_MODULE_NAME).o - -$(INV_MODULE_NAME)-objs += mpuirq.o -$(INV_MODULE_NAME)-objs += slaveirq.o -$(INV_MODULE_NAME)-objs += mpu-dev.o -$(INV_MODULE_NAME)-objs += mlsl-kernel.o -$(INV_MODULE_NAME)-objs += mldl_cfg.o -$(INV_MODULE_NAME)-objs += mldl_print_cfg.o - -ifdef CONFIG_MPU_SENSORS_MPU6050A2 -$(INV_MODULE_NAME)-objs += accel/mpu6050.o -endif - -ifdef CONFIG_MPU_SENSORS_MPU6050B1 -$(INV_MODULE_NAME)-objs += accel/mpu6050.o -endif - EXTRA_CFLAGS += -Idrivers/misc/inv_mpu EXTRA_CFLAGS += -D__C99_DESIGNATED_INITIALIZER EXTRA_CFLAGS += -DINV_CACHE_DMP=1 obj-$(CONFIG_MPU_SENSORS_TIMERIRQ)+= timerirq.o +obj-$(CONFIG_INV_SENSORS) += mpuirq.o slaveirq.o -obj-y += accel/ -obj-y += compass/ -obj-y += pressure/ +obj-$(CONFIG_MPU_SENSORS_MPU3050) += mpu3050/ +obj-$(CONFIG_MPU_SENSORS_MPU6050B1) += mpu6050/ +obj-y += accel/ +obj-y += compass/ +obj-y += pressure/ diff --git a/drivers/misc/inv_mpu/accel/mpu6050.c b/drivers/misc/inv_mpu/accel/mpu6050.c new file mode 100644 index 000000000000..c5bb6784a411 --- /dev/null +++ b/drivers/misc/inv_mpu/accel/mpu6050.c @@ -0,0 +1,695 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, All Rights Reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup ACCELDL + * @brief Provides the interface to setup and handle an accelerometer. + * + * @{ + * @file mpu6050.c + * @brief Accelerometer setup and handling methods for Invensense MPU6050 + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mpu6050b1.h" +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* -------------------------------------------------------------------------- */ + +struct mpu6050_config { + unsigned int odr; /**< output data rate 1/1000 Hz */ + unsigned int fsr; /**< full scale range mg */ + unsigned int ths; /**< mot/no-mot thseshold mg */ + unsigned int dur; /**< mot/no-mot duration ms */ + unsigned int irq_type; /**< irq type */ +}; + +struct mpu6050_private_data { + struct mpu6050_config suspend; + struct mpu6050_config resume; + struct mldl_cfg *mldl_cfg_ref; +}; + +/* -------------------------------------------------------------------------- */ + +static int mpu6050_set_mldl_cfg_ref(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mldl_cfg *mldl_cfg_ref) +{ + struct mpu6050_private_data *private_data = + (struct mpu6050_private_data *)pdata->private_data; + private_data->mldl_cfg_ref = mldl_cfg_ref; + return 0; +} +static int mpu6050_set_lp_mode(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + unsigned char lpa_freq) +{ + unsigned char b = 0; + /* Reducing the duration setting for lp mode */ + b = 1; + inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_MOT_DUR, b); + /* Setting the cycle bit and LPA wake up freq */ + inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_1, 1, + &b); + b |= BIT_CYCLE | BIT_PD_PTAT; + inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_1, + b); + inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, 1, &b); + b |= lpa_freq & BITS_LPA_WAKE_CTRL; + inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, b); + + return INV_SUCCESS; +} + +static int mpu6050_set_fp_mode(void *mlsl_handle, + struct ext_slave_platform_data *pdata) +{ + unsigned char b; + struct mpu6050_private_data *private_data = + (struct mpu6050_private_data *)pdata->private_data; + /* Resetting the cycle bit and LPA wake up freq */ + inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_1, 1, &b); + b &= ~BIT_CYCLE & ~BIT_PD_PTAT; + inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_1, b); + inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, 1, &b); + b &= ~BITS_LPA_WAKE_CTRL; + inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, b); + /* Resetting the duration setting for fp mode */ + b = (unsigned char)private_data->suspend.ths / ACCEL_MOT_DUR_LSB; + inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_MOT_DUR, b); + + return INV_SUCCESS; +} +/** + * Record the odr for use in computing duration values. + * + * @param config Config to set, suspend or resume structure + * @param odr output data rate in 1/1000 hz + */ +static int mpu6050_set_odr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mpu6050_config *config, long apply, long odr) +{ + int result; + unsigned char b; + unsigned char lpa_freq = 1; /* Default value */ + long base; + int total_divider; + struct mpu6050_private_data *private_data = + (struct mpu6050_private_data *)pdata->private_data; + struct mldl_cfg *mldl_cfg_ref = + (struct mldl_cfg *)private_data->mldl_cfg_ref; + + if (mldl_cfg_ref) { + base = 1000 * + inv_mpu_get_sampling_rate_hz(mldl_cfg_ref->mpu_gyro_cfg) + * (mldl_cfg_ref->mpu_gyro_cfg->divider + 1); + } else { + /* have no reference to mldl_cfg => assume base rate is 1000 */ + base = 1000000L; + } + + if (odr != 0) { + total_divider = (base / odr) - 1; + /* final odr MAY be different from requested odr due to + integer truncation */ + config->odr = base / (total_divider + 1); + } else { + config->odr = 0; + return 0; + } + + /* if the DMP and/or gyros are on, don't set the ODR => + the DMP/gyro mldl_cfg->divider setting will handle it */ + if (apply + && (mldl_cfg_ref && + !(mldl_cfg_ref->inv_mpu_cfg->requested_sensors & + INV_DMP_PROCESSOR))) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_SMPLRT_DIV, + (unsigned char)total_divider); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGI("ODR : %d mHz\n", config->odr); + } + /* Decide whether to put accel in LP mode or pull out of LP mode + based on the odr. */ + switch (odr) { + case 1000: + lpa_freq = BITS_LPA_WAKE_1HZ; + break; + case 2000: + lpa_freq = BITS_LPA_WAKE_2HZ; + break; + case 10000: + lpa_freq = BITS_LPA_WAKE_10HZ; + break; + case 40000: + lpa_freq = BITS_LPA_WAKE_40HZ; + break; + default: + inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_1, 1, &b); + b &= BIT_CYCLE; + if (b == BIT_CYCLE) { + MPL_LOGI(" Accel LP - > FP mode. \n "); + mpu6050_set_fp_mode(mlsl_handle, pdata); + } + } + /* If lpa_freq default value was changed, set into LP mode */ + if (lpa_freq != 1) { + MPL_LOGI(" Accel FP - > LP mode. \n "); + mpu6050_set_lp_mode(mlsl_handle, pdata, lpa_freq); + } + return 0; +} + +static int mpu6050_set_fsr(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mpu6050_config *config, long apply, long fsr) +{ + unsigned char fsr_mask; + int result; + + if (fsr <= 2000) { + config->fsr = 2000; + fsr_mask = 0x00; + } else if (fsr <= 4000) { + config->fsr = 4000; + fsr_mask = 0x08; + } else if (fsr <= 8000) { + config->fsr = 8000; + fsr_mask = 0x10; + } else { /* fsr = [8001, oo) */ + config->fsr = 16000; + fsr_mask = 0x18; + } + + if (apply) { + unsigned char reg; + result = inv_serial_read(mlsl_handle, pdata->address, + MPUREG_ACCEL_CONFIG, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_CONFIG, + reg | fsr_mask); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("FSR: %d\n", config->fsr); + } + return 0; +} + +static int mpu6050_set_irq(void *mlsl_handle, + struct ext_slave_platform_data *pdata, + struct mpu6050_config *config, long apply, + long irq_type) +{ + int result; + unsigned char reg_int_cfg; + + switch (irq_type) { + case MPU_SLAVE_IRQ_TYPE_DATA_READY: + config->irq_type = irq_type; + reg_int_cfg = BIT_RAW_RDY_EN; + break; + /* todo: add MOTION, NO_MOTION, and FREEFALL */ + case MPU_SLAVE_IRQ_TYPE_NONE: + /* Do nothing, not even set the interrupt because it is + shared with the gyro */ + config->irq_type = irq_type; + return 0; + default: + return INV_ERROR_INVALID_PARAMETER; + } + + if (apply) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_INT_ENABLE, + reg_int_cfg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGV("irq_type: %d\n", config->irq_type); + } + + return 0; +} + +static int mpu6050_set_ths(void *mlsl_handle, + struct ext_slave_platform_data *slave, + struct mpu6050_config *config, long apply, long ths) +{ + if (ths < 0) + ths = 0; + + config->ths = ths; + MPL_LOGV("THS: %d\n", config->ths); + return 0; +} + +static int mpu6050_set_dur(void *mlsl_handle, + struct ext_slave_platform_data *slave, + struct mpu6050_config *config, long apply, long dur) +{ + if (dur < 0) + dur = 0; + + config->dur = dur; + MPL_LOGV("DUR: %d\n", config->dur); + return 0; +} + + +static int mpu6050_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + struct mpu6050_private_data *private_data; + + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + pdata->private_data = private_data; + + result = mpu6050_set_odr(mlsl_handle, pdata, &private_data->suspend, + false, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_odr(mlsl_handle, pdata, &private_data->resume, + false, 200000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_fsr(mlsl_handle, pdata, &private_data->suspend, + false, 2000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_fsr(mlsl_handle, pdata, &private_data->resume, + false, 2000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = mpu6050_set_irq(mlsl_handle, pdata, &private_data->suspend, + false, MPU_SLAVE_IRQ_TYPE_NONE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_irq(mlsl_handle, pdata, &private_data->resume, + false, MPU_SLAVE_IRQ_TYPE_NONE); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = mpu6050_set_ths(mlsl_handle, pdata, &private_data->suspend, + false, 80); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_ths(mlsl_handle, pdata, &private_data->resume, + false, 40); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_dur(mlsl_handle, pdata, &private_data->suspend, + false, 1000); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_dur(mlsl_handle, pdata, &private_data->resume, + false, 2540); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return 0; +} + +static int mpu6050_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + pdata->private_data = NULL; + return 0; +} + +static int mpu6050_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + unsigned char reg; + int result; + struct mpu6050_private_data *private_data = + (struct mpu6050_private_data *)pdata->private_data; + + result = mpu6050_set_odr(mlsl_handle, pdata, &private_data->suspend, + true, private_data->suspend.odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = mpu6050_set_irq(mlsl_handle, pdata, &private_data->suspend, + true, private_data->suspend.irq_type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg |= (BIT_STBY_XA | BIT_STBY_YA | BIT_STBY_ZA); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + return 0; +} + +static int mpu6050_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char reg; + struct mpu6050_private_data *private_data = + (struct mpu6050_private_data *)pdata->private_data; + + result = inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_1, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (reg & BIT_SLEEP) { + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_1, reg & ~BIT_SLEEP); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + msleep(2); + + result = inv_serial_read(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg &= ~(BIT_STBY_XA | BIT_STBY_YA | BIT_STBY_ZA); + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_PWR_MGMT_2, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* settings */ + + result = mpu6050_set_fsr(mlsl_handle, pdata, &private_data->resume, + true, private_data->resume.fsr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_odr(mlsl_handle, pdata, &private_data->resume, + true, private_data->resume.odr); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu6050_set_irq(mlsl_handle, pdata, &private_data->resume, + true, private_data->resume.irq_type); + + /* motion, no_motion */ + /* TODO : port these in their respective _set_thrs and _set_dur + functions and use the APPLY paremeter to apply just like + _set_odr, _set_irq, and _set_fsr. */ + reg = (unsigned char)private_data->suspend.ths / ACCEL_MOT_THR_LSB; + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_MOT_THR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg = (unsigned char) + ACCEL_ZRMOT_THR_LSB_CONVERSION(private_data->resume.ths); + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_ZRMOT_THR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg = (unsigned char)private_data->suspend.ths / ACCEL_MOT_DUR_LSB; + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_MOT_DUR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + reg = (unsigned char)private_data->resume.ths / ACCEL_ZRMOT_DUR_LSB; + result = inv_serial_single_write(mlsl_handle, pdata->address, + MPUREG_ACCEL_ZRMOT_DUR, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return 0; +} + +static int mpu6050_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + result = inv_serial_read(mlsl_handle, pdata->address, + slave->read_reg, slave->read_len, data); + return result; +} + +static int mpu6050_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct mpu6050_private_data *private_data = + (struct mpu6050_private_data *)pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + return mpu6050_set_odr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_ODR_RESUME: + return mpu6050_set_odr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + return mpu6050_set_fsr(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_FSR_RESUME: + return mpu6050_set_fsr(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_THS: + return mpu6050_set_ths(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_THS: + return mpu6050_set_ths(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_MOT_DUR: + return mpu6050_set_dur(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_NMOT_DUR: + return mpu6050_set_dur(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + return mpu6050_set_irq(mlsl_handle, pdata, + &private_data->suspend, + data->apply, *((long *)data->data)); + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + return mpu6050_set_irq(mlsl_handle, pdata, + &private_data->resume, + data->apply, *((long *)data->data)); + case MPU_SLAVE_CONFIG_INTERNAL_REFERENCE: + return mpu6050_set_mldl_cfg_ref(mlsl_handle, pdata, + (struct mldl_cfg *)data->data); + break; + + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return 0; +} + +static int mpu6050_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct mpu6050_private_data *private_data = + (struct mpu6050_private_data *)pdata->private_data; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.odr; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.odr; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.fsr; + break; + case MPU_SLAVE_CONFIG_FSR_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.fsr; + break; + case MPU_SLAVE_CONFIG_MOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.ths; + break; + case MPU_SLAVE_CONFIG_NMOT_THS: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.ths; + break; + case MPU_SLAVE_CONFIG_MOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.dur; + break; + case MPU_SLAVE_CONFIG_NMOT_DUR: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.dur; + break; + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + (*(unsigned long *)data->data) = + (unsigned long)private_data->suspend.irq_type; + break; + case MPU_SLAVE_CONFIG_IRQ_RESUME: + (*(unsigned long *)data->data) = + (unsigned long)private_data->resume.irq_type; + break; + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return 0; +} + +static struct ext_slave_descr mpu6050_descr = { + .init = mpu6050_init, + .exit = mpu6050_exit, + .suspend = mpu6050_suspend, + .resume = mpu6050_resume, + .read = mpu6050_read, + .config = mpu6050_config, + .get_config = mpu6050_get_config, + .name = "mpu6050", + .type = EXT_SLAVE_TYPE_ACCEL, + .id = ACCEL_ID_MPU6050, + .read_reg = 0x3B, + .read_len = 6, + .endian = EXT_SLAVE_BIG_ENDIAN, + .range = {2, 0}, + .trigger = NULL, +}; + +struct ext_slave_descr *mpu6050_get_slave_descr(void) +{ + return &mpu6050_descr; +} + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/compass/Kconfig b/drivers/misc/inv_mpu/compass/Kconfig index 6c4c13a6e19a..7e6bac87d31e 100644 --- a/drivers/misc/inv_mpu/compass/Kconfig +++ b/drivers/misc/inv_mpu/compass/Kconfig @@ -9,6 +9,15 @@ menuconfig INV_SENSORS_COMPASS if INV_SENSORS_COMPASS +config MPU_SENSORS_AK8963 + tristate "AKM ak8963" + help + This enables support for the AKM ak8963 compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + config MPU_SENSORS_AK8975 tristate "AKM ak8975" help diff --git a/drivers/misc/inv_mpu/compass/Makefile b/drivers/misc/inv_mpu/compass/Makefile index aa8aa6a2657b..d76ce1b06f31 100644 --- a/drivers/misc/inv_mpu/compass/Makefile +++ b/drivers/misc/inv_mpu/compass/Makefile @@ -28,6 +28,9 @@ inv_mpu_hscdtd002b-objs += hscdtd002b.o obj-$(CONFIG_MPU_SENSORS_HSCDTD004A) += inv_mpu_hscdtd004a.o inv_mpu_hscdtd004a-objs += hscdtd004a.o +obj-$(CONFIG_MPU_SENSORS_AK8963) += inv_mpu_ak89xx.o +inv_mpu_ak89xx-objs += ak89xx.o + obj-$(CONFIG_MPU_SENSORS_AK8975) += inv_mpu_ak8975.o inv_mpu_ak8975-objs += ak8975.o diff --git a/drivers/misc/inv_mpu/compass/ak89xx.c b/drivers/misc/inv_mpu/compass/ak89xx.c new file mode 100644 index 000000000000..d15b0b8dbe68 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/ak89xx.c @@ -0,0 +1,522 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, All Rights Reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup COMPASSDL + * + * @{ + * @file ak89xx.c + * @brief Magnetometer setup and handling methods for the AKM + * compass devices. + */ + +/* -------------------------------------------------------------------------- */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "mpu-dev.h" + +#include <log.h> +#include <linux/mpu.h> +#include "mlsl.h" +#include "mldl_cfg.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +/* -------------------------------------------------------------------------- */ +#define AK89XX_REG_ST1 (0x02) +#define AK89XX_REG_HXL (0x03) +#define AK89XX_REG_ST2 (0x09) + +#define AK89XX_REG_CNTL (0x0A) +#define AK89XX_REG_ASAX (0x10) +#define AK89XX_REG_ASAY (0x11) +#define AK89XX_REG_ASAZ (0x12) + +#define AK89XX_CNTL_MODE_POWER_DOWN (0x00) +#define AK89XX_CNTL_MODE_SINGLE_MEASUREMENT (0x01) +#define AK89XX_CNTL_MODE_FUSE_ROM_ACCESS (0x0f) + +/* -------------------------------------------------------------------------- */ +struct ak89xx_config { + char asa[COMPASS_NUM_AXES]; /* axis sensitivity adjustment */ +}; + +struct ak89xx_private_data { + struct ak89xx_config init; +}; + +/* -------------------------------------------------------------------------- */ +static int ak89xx_init(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + unsigned char serial_data[COMPASS_NUM_AXES]; + + struct ak89xx_private_data *private_data; + private_data = (struct ak89xx_private_data *) + kzalloc(sizeof(struct ak89xx_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + AK89XX_REG_CNTL, + AK89XX_CNTL_MODE_POWER_DOWN); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Wait at least 100us */ + udelay(100); + + result = inv_serial_single_write(mlsl_handle, pdata->address, + AK89XX_REG_CNTL, + AK89XX_CNTL_MODE_FUSE_ROM_ACCESS); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Wait at least 200us */ + udelay(200); + + result = inv_serial_read(mlsl_handle, pdata->address, + AK89XX_REG_ASAX, + COMPASS_NUM_AXES, serial_data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + pdata->private_data = private_data; + + private_data->init.asa[0] = serial_data[0]; + private_data->init.asa[1] = serial_data[1]; + private_data->init.asa[2] = serial_data[2]; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + AK89XX_REG_CNTL, + AK89XX_CNTL_MODE_POWER_DOWN); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + udelay(100); + return INV_SUCCESS; +} + +static int ak89xx_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int ak89xx_suspend(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AK89XX_REG_CNTL, + AK89XX_CNTL_MODE_POWER_DOWN); + msleep(1); /* wait at least 100us */ + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int ak89xx_resume(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = INV_SUCCESS; + result = + inv_serial_single_write(mlsl_handle, pdata->address, + AK89XX_REG_CNTL, + AK89XX_CNTL_MODE_SINGLE_MEASUREMENT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int ak89xx_read(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, unsigned char *data) +{ + unsigned char regs[8]; + unsigned char *stat = ®s[0]; + unsigned char *stat2 = ®s[7]; + int result = INV_SUCCESS; + int status = INV_SUCCESS; + + result = + inv_serial_read(mlsl_handle, pdata->address, AK89XX_REG_ST1, + 8, regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Always return the data and the status registers */ + memcpy(data, ®s[1], 6); + data[6] = regs[0]; + data[7] = regs[7]; + + /* + * ST : data ready - + * Measurement has been completed and data is ready to be read. + */ + if (*stat & 0x01) + status = INV_SUCCESS; + + /* + * ST2 : data error - + * occurs when data read is started outside of a readable period; + * data read would not be correct. + * Valid in continuous measurement mode only. + * In single measurement mode this error should not occour but we + * stil account for it and return an error, since the data would be + * corrupted. + * DERR bit is self-clearing when ST2 register is read. + * + * This bit is always zero on the AK8963. + */ + if (*stat2 & 0x04) + status = INV_ERROR_COMPASS_DATA_ERROR; + /* + * ST2 : overflow - + * the sum of the absolute values of all axis |X|+|Y|+|Z| < 2400uT. + * This is likely to happen in presence of an external magnetic + * disturbance; it indicates, the sensor data is incorrect and should + * be ignored. + * An error is returned. + * HOFL bit clears when a new measurement starts. + */ + if (*stat2 & 0x08) + status = INV_ERROR_COMPASS_DATA_OVERFLOW; + /* + * ST : overrun - + * the previous sample was not fetched and lost. + * Valid in continuous measurement mode only. + * In single measurement mode this error should not occour and we + * don't consider this condition an error. + * DOR bit is self-clearing when ST2 or any meas. data register is + * read. + */ + if (*stat & 0x02) { + /* status = INV_ERROR_COMPASS_DATA_UNDERFLOW; */ + status = INV_SUCCESS; + } + + /* + * trigger next measurement if: + * - stat is non zero; + * - if stat is zero and stat2 is non zero. + * Won't trigger if data is not ready and there was no error. + */ + if (*stat != 0x00 || *stat2 != 0x00) { + result = inv_serial_single_write( + mlsl_handle, pdata->address, + AK89XX_REG_CNTL, AK89XX_CNTL_MODE_SINGLE_MEASUREMENT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return status; +} + +static int ak89xx_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + int result; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_WRITE_REGISTERS: + result = inv_serial_write(mlsl_handle, pdata->address, + data->len, + (unsigned char *)data->data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + break; + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + case MPU_SLAVE_CONFIG_ODR_RESUME: + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + case MPU_SLAVE_CONFIG_FSR_RESUME: + case MPU_SLAVE_CONFIG_MOT_THS: + case MPU_SLAVE_CONFIG_NMOT_THS: + case MPU_SLAVE_CONFIG_MOT_DUR: + case MPU_SLAVE_CONFIG_NMOT_DUR: + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static int ak89xx_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct ak89xx_private_data *private_data = pdata->private_data; + int result; + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_READ_REGISTERS: + { + unsigned char *serial_data = + (unsigned char *)data->data; + result = + inv_serial_read(mlsl_handle, pdata->address, + serial_data[0], data->len - 1, + &serial_data[1]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + break; + } + case MPU_SLAVE_READ_SCALE: + { + unsigned char *serial_data = + (unsigned char *)data->data; + serial_data[0] = private_data->init.asa[0]; + serial_data[1] = private_data->init.asa[1]; + serial_data[2] = private_data->init.asa[2]; + result = INV_SUCCESS; + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + break; + } + case MPU_SLAVE_CONFIG_ODR_SUSPEND: + (*(unsigned long *)data->data) = 0; + break; + case MPU_SLAVE_CONFIG_ODR_RESUME: + (*(unsigned long *)data->data) = 8000; + break; + case MPU_SLAVE_CONFIG_FSR_SUSPEND: + case MPU_SLAVE_CONFIG_FSR_RESUME: + case MPU_SLAVE_CONFIG_MOT_THS: + case MPU_SLAVE_CONFIG_NMOT_THS: + case MPU_SLAVE_CONFIG_MOT_DUR: + case MPU_SLAVE_CONFIG_NMOT_DUR: + case MPU_SLAVE_CONFIG_IRQ_SUSPEND: + case MPU_SLAVE_CONFIG_IRQ_RESUME: + default: + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + +static struct ext_slave_read_trigger ak89xx_read_trigger = { + /*.reg = */ 0x0A, + /*.value = */ 0x01 +}; + +static struct ext_slave_descr ak89xx_descr = { + .init = ak89xx_init, + .exit = ak89xx_exit, + .suspend = ak89xx_suspend, + .resume = ak89xx_resume, + .read = ak89xx_read, + .config = ak89xx_config, + .get_config = ak89xx_get_config, + .name = "ak89xx", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_AK8963, + .read_reg = 0x01, + .read_len = 10, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {9830, 4000}, + .trigger = &ak89xx_read_trigger, +}; + +static +struct ext_slave_descr *ak89xx_get_slave_descr(void) +{ + return &ak89xx_descr; +} + +/* -------------------------------------------------------------------------- */ +struct ak89xx_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int ak89xx_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct ak89xx_mod_private_data *private_data; + int result = 0; + + dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); + + /* Override default values based on model. */ + ak89xx_descr.id = devid->driver_data; + /* ak89xx_descr.name = devid->name; */ + switch (ak89xx_descr.id) { + case COMPASS_ID_AK8963: + break; + case COMPASS_ID_AK8972: + ak89xx_descr.read_len = 9; + ak89xx_descr.range.mantissa = 39321; + ak89xx_descr.range.fraction = 6000; + break; + case COMPASS_ID_AK8975: + break; + default: + result = -EFAULT; + goto out_no_free; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->adapter->dev, + "Missing platform data for slave %s\n", devid->name); + result = -EFAULT; + goto out_no_free; + } + + private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); + if (!private_data) { + result = -ENOMEM; + goto out_no_free; + } + + i2c_set_clientdata(client, private_data); + private_data->client = client; + private_data->pdata = pdata; + + result = inv_mpu_register_slave(THIS_MODULE, client, pdata, + ak89xx_get_slave_descr); + if (result) { + dev_err(&client->adapter->dev, + "Slave registration failed: %s, %d\n", + devid->name, result); + goto out_free_memory; + } + + return result; + +out_free_memory: + kfree(private_data); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return result; + +} + +static int ak89xx_mod_remove(struct i2c_client *client) +{ + struct ak89xx_mod_private_data *private_data = + i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + inv_mpu_unregister_slave(client, private_data->pdata, + ak89xx_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id ak89xx_mod_id[] = { + { "ak8963", COMPASS_ID_AK8963 }, + { "ak8972", COMPASS_ID_AK8972 }, + { "ak8975", COMPASS_ID_AK8975 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ak89xx_mod_id); + +static struct i2c_driver ak89xx_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = ak89xx_mod_probe, + .remove = ak89xx_mod_remove, + .id_table = ak89xx_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "ak89xx_mod", + }, + .address_list = normal_i2c, +}; + +static int __init ak89xx_mod_init(void) +{ + int res = i2c_add_driver(&ak89xx_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "ak89xx_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit ak89xx_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&ak89xx_mod_driver); +} + +module_init(ak89xx_mod_init); +module_exit(ak89xx_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate AKM AK89XX sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ak89xx_mod"); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/mldl_cfg.h b/drivers/misc/inv_mpu/mldl_cfg.h index 1c23878940b7..2b81b89179d5 100644 --- a/drivers/misc/inv_mpu/mldl_cfg.h +++ b/drivers/misc/inv_mpu/mldl_cfg.h @@ -31,7 +31,11 @@ #include "mltypes.h" #include "mlsl.h" #include <linux/mpu.h> +#ifdef MPU_CURRENT_BUILD_MPU6050B1 +#include "mpu6050b1.h" +#elif defined(MPU_CURRENT_BUILD_MPU3050) #include "mpu3050.h" +#endif #include "log.h" diff --git a/drivers/misc/inv_mpu/mpu3050/Makefile b/drivers/misc/inv_mpu/mpu3050/Makefile new file mode 100644 index 000000000000..9e5739302386 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu3050/Makefile @@ -0,0 +1,17 @@ + +# Kernel makefile for motions sensors +# +# + +obj-$(CONFIG_MPU_SENSORS_MPU3050) += mpu3050.o + +ccflags-y := -DMPU_CURRENT_BUILD_MPU3050 + +mpu3050-objs += mldl_cfg.o +mpu3050-objs += mldl_print_cfg.o +mpu3050-objs += mlsl-kernel.o +mpu3050-objs += mpu-dev.o + +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu +EXTRA_CFLAGS += -D__C99_DESIGNATED_INITIALIZER +EXTRA_CFLAGS += -DINV_CACHE_DMP=1 diff --git a/drivers/misc/inv_mpu/mldl_cfg.c b/drivers/misc/inv_mpu/mpu3050/mldl_cfg.c index ccacc8ec0b56..ccacc8ec0b56 100644 --- a/drivers/misc/inv_mpu/mldl_cfg.c +++ b/drivers/misc/inv_mpu/mpu3050/mldl_cfg.c diff --git a/drivers/misc/inv_mpu/mldl_print_cfg.c b/drivers/misc/inv_mpu/mpu3050/mldl_print_cfg.c index e2b8d30cebaa..e2b8d30cebaa 100644 --- a/drivers/misc/inv_mpu/mldl_print_cfg.c +++ b/drivers/misc/inv_mpu/mpu3050/mldl_print_cfg.c diff --git a/drivers/misc/inv_mpu/mlsl-kernel.c b/drivers/misc/inv_mpu/mpu3050/mlsl-kernel.c index 19adf5182c00..19adf5182c00 100644 --- a/drivers/misc/inv_mpu/mlsl-kernel.c +++ b/drivers/misc/inv_mpu/mpu3050/mlsl-kernel.c diff --git a/drivers/misc/inv_mpu/mpu-dev.c b/drivers/misc/inv_mpu/mpu3050/mpu-dev.c index ddaf0864a440..22a837b35c57 100644 --- a/drivers/misc/inv_mpu/mpu-dev.c +++ b/drivers/misc/inv_mpu/mpu3050/mpu-dev.c @@ -1059,6 +1059,7 @@ int mpu_probe(struct i2c_client *client, const struct i2c_device_id *devid) struct mldl_cfg *mldl_cfg; int res = 0; int ii = 0; + unsigned long irq_flags; dev_info(&client->adapter->dev, "%s: %d\n", __func__, ii++); @@ -1138,7 +1139,12 @@ int mpu_probe(struct i2c_client *client, const struct i2c_device_id *devid) if (client->irq) { dev_info(&client->adapter->dev, "Installing irq using %d\n", client->irq); - res = mpuirq_init(client, mldl_cfg); + if (BIT_ACTL_LOW == ((mldl_cfg->pdata->int_config) & BIT_ACTL)) + irq_flags = IRQF_TRIGGER_FALLING; + else + irq_flags = IRQF_TRIGGER_RISING; + res = mpuirq_init(client, mldl_cfg, irq_flags); + if (res) goto out_mpuirq_failed; } else { diff --git a/drivers/misc/inv_mpu/mpu6050/Makefile b/drivers/misc/inv_mpu/mpu6050/Makefile new file mode 100644 index 000000000000..a93aa97a6997 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu6050/Makefile @@ -0,0 +1,18 @@ + +# Kernel makefile for motions sensors +# +# + +obj-$(CONFIG_MPU_SENSORS_MPU6050B1) += mpu6050b1.o + +ccflags-y := -DMPU_CURRENT_BUILD_MPU6050B1 + +mpu6050b1-objs += mldl_cfg.o +mpu6050b1-objs += mldl_print_cfg.o +mpu6050b1-objs += mlsl-kernel.o +mpu6050b1-objs += mpu-dev.o +mpu6050b1-objs += ../accel/mpu6050.o + +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu +EXTRA_CFLAGS += -D__C99_DESIGNATED_INITIALIZER +EXTRA_CFLAGS += -DINV_CACHE_DMP=1 diff --git a/drivers/misc/inv_mpu/mpu6050/mldl_cfg.c b/drivers/misc/inv_mpu/mpu6050/mldl_cfg.c new file mode 100644 index 000000000000..22af0c200985 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu6050/mldl_cfg.c @@ -0,0 +1,1916 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, All Rights Reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup MLDL + * + * @{ + * @file mldl_cfg.c + * @brief The Motion Library Driver Layer. + */ + +/* -------------------------------------------------------------------------- */ +#include <linux/delay.h> +#include <linux/slab.h> + +#include <stddef.h> + +#include "mldl_cfg.h" +#include <linux/mpu.h> +#include "mpu6050b1.h" + +#include "mlsl.h" +#include "mldl_print_cfg.h" +#include "log.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "mldl_cfg:" + +/* -------------------------------------------------------------------------- */ + +#define SLEEP 0 +#define WAKE_UP 7 +#define RESET 1 +#define STANDBY 1 + +/* -------------------------------------------------------------------------- */ + +/** + * @brief Stop the DMP running + * + * @return INV_SUCCESS or non-zero error code + */ +static int dmp_stop(struct mldl_cfg *mldl_cfg, void *gyro_handle) +{ + unsigned char user_ctrl_reg; + int result; + + if (mldl_cfg->inv_mpu_state->status & MPU_DMP_IS_SUSPENDED) + return INV_SUCCESS; + + result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, 1, &user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + user_ctrl_reg = (user_ctrl_reg & (~BIT_FIFO_EN)) | BIT_FIFO_RST; + user_ctrl_reg = (user_ctrl_reg & (~BIT_DMP_EN)) | BIT_DMP_RST; + + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->inv_mpu_state->status |= MPU_DMP_IS_SUSPENDED; + + return result; +} + +/** + * @brief Starts the DMP running + * + * @return INV_SUCCESS or non-zero error code + */ +static int dmp_start(struct mldl_cfg *mldl_cfg, void *mlsl_handle) +{ + unsigned char user_ctrl_reg; + int result; + + if ((!(mldl_cfg->inv_mpu_state->status & MPU_DMP_IS_SUSPENDED) && + mldl_cfg->mpu_gyro_cfg->dmp_enable) + || + ((mldl_cfg->inv_mpu_state->status & MPU_DMP_IS_SUSPENDED) && + !mldl_cfg->mpu_gyro_cfg->dmp_enable)) + return INV_SUCCESS; + + result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, 1, &user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, + ((user_ctrl_reg & (~BIT_FIFO_EN)) + | BIT_FIFO_RST)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, 1, &user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + user_ctrl_reg |= BIT_DMP_EN; + + if (mldl_cfg->mpu_gyro_cfg->fifo_enable) + user_ctrl_reg |= BIT_FIFO_EN; + else + user_ctrl_reg &= ~BIT_FIFO_EN; + + user_ctrl_reg |= BIT_DMP_RST; + + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, user_ctrl_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->inv_mpu_state->status &= ~MPU_DMP_IS_SUSPENDED; + + return result; +} + +/** + * @brief enables/disables the I2C bypass to an external device + * connected to MPU's secondary I2C bus. + * @param enable + * Non-zero to enable pass through. + * @return INV_SUCCESS if successful, a non-zero error code otherwise. + */ +static int mpu6050b1_set_i2c_bypass(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, unsigned char enable) +{ + unsigned char reg; + int result; + unsigned char status = mldl_cfg->inv_mpu_state->status; + if ((status & MPU_GYRO_IS_BYPASSED && enable) || + (!(status & MPU_GYRO_IS_BYPASSED) && !enable)) + return INV_SUCCESS; + + /*---- get current 'USER_CTRL' into b ----*/ + result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (!enable) { + /* setting int_config with the property flag BIT_BYPASS_EN + should be done by the setup functions */ + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_INT_PIN_CFG, + (mldl_cfg->pdata->int_config & ~(BIT_BYPASS_EN))); + if (!(reg & BIT_I2C_MST_EN)) { + result = + inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, + (reg | BIT_I2C_MST_EN)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + } else if (enable) { + if (reg & BIT_AUX_IF_EN) { + result = + inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_USER_CTRL, + (reg & (~BIT_I2C_MST_EN))); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /***************************************** + * To avoid hanging the bus we must sleep until all + * slave transactions have been completed. + * 24 bytes max slave reads + * +1 byte possible extra write + * +4 max slave address + * --- + * 33 Maximum bytes + * x9 Approximate bits per byte + * --- + * 297 bits. + * 2.97 ms minimum @ 100kbps + * 0.75 ms minimum @ 400kbps. + *****************************************/ + msleep(3); + } + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_INT_PIN_CFG, + (mldl_cfg->pdata->int_config | BIT_BYPASS_EN)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + if (enable) + mldl_cfg->inv_mpu_state->status |= MPU_GYRO_IS_BYPASSED; + else + mldl_cfg->inv_mpu_state->status &= ~MPU_GYRO_IS_BYPASSED; + + return result; +} + + + + +/** + * @brief enables/disables the I2C bypass to an external device + * connected to MPU's secondary I2C bus. + * @param enable + * Non-zero to enable pass through. + * @return INV_SUCCESS if successful, a non-zero error code otherwise. + */ +static int mpu_set_i2c_bypass(struct mldl_cfg *mldl_cfg, void *mlsl_handle, + unsigned char enable) +{ + return mpu6050b1_set_i2c_bypass(mldl_cfg, mlsl_handle, enable); +} + + +#define NUM_OF_PROD_REVS (ARRAY_SIZE(prod_rev_map)) + +/* NOTE : when not indicated, product revision + is considered an 'npp'; non production part */ + +/* produces an unique identifier for each device based on the + combination of product version and product revision */ +struct prod_rev_map_t { + unsigned short mpl_product_key; + unsigned char silicon_rev; + unsigned short gyro_trim; + unsigned short accel_trim; +}; + +/* NOTE: product entries are in chronological order */ +static struct prod_rev_map_t prod_rev_map[] = { + /* prod_ver = 0 */ + {MPL_PROD_KEY(0, 1), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 2), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 3), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 4), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 5), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 6), MPU_SILICON_REV_A2, 131, 16384}, /* (A2/C2-1) */ + /* prod_ver = 1, forced to 0 for MPU6050 A2 */ + {MPL_PROD_KEY(0, 7), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 8), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 9), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 10), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 11), MPU_SILICON_REV_A2, 131, 16384}, /* (A2/D2-1) */ + {MPL_PROD_KEY(0, 12), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 13), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 14), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 15), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 27), MPU_SILICON_REV_A2, 131, 16384}, /* (A2/D4) */ + /* prod_ver = 1 */ + {MPL_PROD_KEY(1, 16), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-1) */ + {MPL_PROD_KEY(1, 17), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-2) */ + {MPL_PROD_KEY(1, 18), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-3) */ + {MPL_PROD_KEY(1, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-4) */ + {MPL_PROD_KEY(1, 20), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D2-5) */ + {MPL_PROD_KEY(1, 28), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/D4) */ + {MPL_PROD_KEY(1, 1), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-1) */ + {MPL_PROD_KEY(1, 2), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-2) */ + {MPL_PROD_KEY(1, 3), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-3) */ + {MPL_PROD_KEY(1, 4), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-4) */ + {MPL_PROD_KEY(1, 5), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-5) */ + {MPL_PROD_KEY(1, 6), MPU_SILICON_REV_B1, 131, 16384}, /* (B1/E1-6) */ + /* prod_ver = 2 */ + {MPL_PROD_KEY(2, 7), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-1) */ + {MPL_PROD_KEY(2, 8), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-2) */ + {MPL_PROD_KEY(2, 9), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-3) */ + {MPL_PROD_KEY(2, 10), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-4) */ + {MPL_PROD_KEY(2, 11), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-5) */ + {MPL_PROD_KEY(2, 12), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E1-6) */ + {MPL_PROD_KEY(2, 29), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/D4) */ + /* prod_ver = 3 */ + {MPL_PROD_KEY(3, 30), MPU_SILICON_REV_B1, 131, 16384}, /* (B2/E2) */ + /* prod_ver = 4 */ + {MPL_PROD_KEY(4, 31), MPU_SILICON_REV_B1, 131, 8192}, /* (B2/F1) */ + {MPL_PROD_KEY(4, 1), MPU_SILICON_REV_B1, 131, 8192}, /* (B3/F1) */ + {MPL_PROD_KEY(4, 3), MPU_SILICON_REV_B1, 131, 8192}, /* (B4/F1) */ + /* prod_ver = 5 */ + {MPL_PROD_KEY(6, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B5/E2) */ + /* prod_ver = 7 */ + {MPL_PROD_KEY(7, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B5/E2) */ + /* prod_ver = 8 */ + {MPL_PROD_KEY(8, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B5/E2) */ + /* prod_ver = 9 */ + {MPL_PROD_KEY(9, 19), MPU_SILICON_REV_B1, 131, 16384}, /* (B5/E2) */ + /* prod_ver = 10 */ + {MPL_PROD_KEY(10, 19), MPU_SILICON_REV_B1, 131, 16384} /* (B5/E2) */ +}; + +/** + * @internal + * @brief Inverse lookup of the index of an MPL product key . + * @param key + * the MPL product indentifier also referred to as 'key'. + * @return the index position of the key in the array, -1 if not found. + */ +short index_of_key(unsigned short key) +{ + int i; + for (i = 0; i < NUM_OF_PROD_REVS; i++) + if (prod_rev_map[i].mpl_product_key == key) + return (short)i; + return -1; +} + +/** + * @internal + * @brief Get the product revision and version for MPU6050 and + * extract all per-part specific information. + * The product version number is read from the PRODUCT_ID register in + * user space register map. + * The product revision number is in read from OTP bank 0, ADDR6[7:2]. + * These 2 numbers, combined, provide an unique key to be used to + * retrieve some per-device information such as the silicon revision + * and the gyro and accel sensitivity trim values. + * + * @param mldl_cfg + * a pointer to the mldl config data structure. + * @param mlsl_handle + * an file handle to the serial communication device the + * device is connected to. + * + * @return 0 on success, a non-zero error code otherwise. + */ +static int inv_get_silicon_rev_mpu6050( + struct mldl_cfg *mldl_cfg, void *mlsl_handle) +{ + int result; + unsigned char prod_ver = 0x00, prod_rev = 0x00; + unsigned char bank = + (BIT_PRFTCH_EN | BIT_CFG_USER_BANK | MPU_MEM_OTP_BANK_0); + unsigned short memAddr = ((bank << 8) | 0x06); + unsigned short key; + short index; + struct mpu_chip_info *mpu_chip_info = mldl_cfg->mpu_chip_info; + + result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_PRODUCT_ID, 1, &prod_ver); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + prod_ver &= 0xF; + + result = inv_serial_read_mem(mlsl_handle, mldl_cfg->mpu_chip_info->addr, + memAddr, 1, &prod_rev); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + prod_rev >>= 2; + + /* clean the prefetch and cfg user bank bits */ + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_BANK_SEL, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + key = MPL_PROD_KEY(prod_ver, prod_rev); + if (key == 0) { + MPL_LOGE("Product id read as 0 " + "indicates device is either " + "incompatible or an MPU3050\n"); + return INV_ERROR_INVALID_MODULE; + } + index = index_of_key(key); + if (index == -1 || index >= NUM_OF_PROD_REVS) { + MPL_LOGE("Unsupported product key %d in MPL\n", key); + return INV_ERROR_INVALID_MODULE; + } + /* check MPL is compiled for this device */ + if (prod_rev_map[index].silicon_rev != MPU_SILICON_REV_B1) { + MPL_LOGE("MPL compiled for MPU6050B1 support " + "but device is not MPU6050B1 (%d)\n", key); + return INV_ERROR_INVALID_MODULE; + } + + mpu_chip_info->product_id = prod_ver; + mpu_chip_info->product_revision = prod_rev; + mpu_chip_info->silicon_revision = prod_rev_map[index].silicon_rev; + mpu_chip_info->gyro_sens_trim = prod_rev_map[index].gyro_trim; + mpu_chip_info->accel_sens_trim = prod_rev_map[index].accel_trim; + + return result; +} +#define inv_get_silicon_rev inv_get_silicon_rev_mpu6050 + + +/** + * @brief Enable / Disable the use MPU's secondary I2C interface level + * shifters. + * When enabled the secondary I2C interface to which the external + * device is connected runs at VDD voltage (main supply). + * When disabled the 2nd interface runs at VDDIO voltage. + * See the device specification for more details. + * + * @note using this API may produce unpredictable results, depending on how + * the MPU and slave device are setup on the target platform. + * Use of this API should entirely be restricted to system + * integrators. Once the correct value is found, there should be no + * need to change the level shifter at runtime. + * + * @pre Must be called after inv_serial_start(). + * @note Typically called before inv_dmp_open(). + * + * @param[in] enable: + * 0 to run at VDDIO (default), + * 1 to run at VDD. + * + * @return INV_SUCCESS if successfull, a non-zero error code otherwise. + */ +static int inv_mpu_set_level_shifter_bit(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, unsigned char enable) +{ + int result; + unsigned char regval; + + result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_YG_OFFS_TC, 1, ®val); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (enable) + regval |= BIT_I2C_MST_VDDIO; + + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_YG_OFFS_TC, regval); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return INV_SUCCESS; +} + + +/** + * @internal + * @brief MPU6050 B1 power management functions. + * @param mldl_cfg + * a pointer to the internal mldl_cfg data structure. + * @param mlsl_handle + * a file handle to the serial device used to communicate + * with the MPU6050 B1 device. + * @param reset + * 1 to reset hardware. + * @param sensors + * Bitfield of sensors to leave on + * + * @return 0 on success, a non-zero error code on error. + */ +static int mpu60xx_pwr_mgmt(struct mldl_cfg *mldl_cfg, + void *mlsl_handle, + unsigned int reset, unsigned long sensors) +{ + unsigned char pwr_mgmt[2]; + unsigned char pwr_mgmt_prev[2]; + int result; + int sleep = !(sensors & (INV_THREE_AXIS_GYRO | INV_THREE_AXIS_ACCEL + | INV_DMP_PROCESSOR)); + + if (reset) { + MPL_LOGI("Reset MPU6050 B1\n"); + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_PWR_MGMT_1, BIT_H_RESET); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->inv_mpu_state->status &= ~MPU_GYRO_IS_BYPASSED; + msleep(15); + } + + /* NOTE : reading both PWR_MGMT_1 and PWR_MGMT_2 for efficiency because + they are accessible even when the device is powered off */ + result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_PWR_MGMT_1, 2, pwr_mgmt_prev); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + pwr_mgmt[0] = pwr_mgmt_prev[0]; + pwr_mgmt[1] = pwr_mgmt_prev[1]; + + if (sleep) { + mldl_cfg->inv_mpu_state->status |= MPU_DEVICE_IS_SUSPENDED; + pwr_mgmt[0] |= BIT_SLEEP; + } else { + mldl_cfg->inv_mpu_state->status &= ~MPU_DEVICE_IS_SUSPENDED; + pwr_mgmt[0] &= ~BIT_SLEEP; + } + if (pwr_mgmt[0] != pwr_mgmt_prev[0]) { + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_PWR_MGMT_1, pwr_mgmt[0]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + pwr_mgmt[1] &= ~(BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG); + if (!(sensors & INV_X_GYRO)) + pwr_mgmt[1] |= BIT_STBY_XG; + if (!(sensors & INV_Y_GYRO)) + pwr_mgmt[1] |= BIT_STBY_YG; + if (!(sensors & INV_Z_GYRO)) + pwr_mgmt[1] |= BIT_STBY_ZG; + + if (pwr_mgmt[1] != pwr_mgmt_prev[1]) { + result = inv_serial_single_write( + mlsl_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_PWR_MGMT_2, pwr_mgmt[1]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + if ((pwr_mgmt[1] & (BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG)) == + (BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG)) { + mldl_cfg->inv_mpu_state->status |= MPU_GYRO_IS_SUSPENDED; + } else { + mldl_cfg->inv_mpu_state->status &= ~MPU_GYRO_IS_SUSPENDED; + } + + return INV_SUCCESS; +} + + +/** + * @brief sets the clock source for the gyros. + * @param mldl_cfg + * a pointer to the struct mldl_cfg data structure. + * @param gyro_handle + * an handle to the serial device the gyro is assigned to. + * @return ML_SUCCESS if successful, a non-zero error code otherwise. + */ +static int mpu_set_clock_source(void *gyro_handle, struct mldl_cfg *mldl_cfg) +{ + int result; + unsigned char cur_clk_src; + unsigned char reg; + + /* clock source selection */ + result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_PWR_MGM, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + cur_clk_src = reg & BITS_CLKSEL; + reg &= ~BITS_CLKSEL; + + + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_PWR_MGM, mldl_cfg->mpu_gyro_cfg->clk_src | reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* ERRATA: + workaroud to switch from any MPU_CLK_SEL_PLLGYROx to + MPU_CLK_SEL_INTERNAL and XGyro is powered up: + 1) Select INT_OSC + 2) PD XGyro + 3) PU XGyro + */ + if ((cur_clk_src == MPU_CLK_SEL_PLLGYROX + || cur_clk_src == MPU_CLK_SEL_PLLGYROY + || cur_clk_src == MPU_CLK_SEL_PLLGYROZ) + && mldl_cfg->mpu_gyro_cfg->clk_src == MPU_CLK_SEL_INTERNAL + && mldl_cfg->inv_mpu_cfg->requested_sensors & INV_X_GYRO) { + unsigned char first_result = INV_SUCCESS; + mldl_cfg->inv_mpu_cfg->requested_sensors &= ~INV_X_GYRO; + result = mpu60xx_pwr_mgmt( + mldl_cfg, gyro_handle, + false, mldl_cfg->inv_mpu_cfg->requested_sensors); + ERROR_CHECK_FIRST(first_result, result); + mldl_cfg->inv_mpu_cfg->requested_sensors |= INV_X_GYRO; + result = mpu60xx_pwr_mgmt( + mldl_cfg, gyro_handle, + false, mldl_cfg->inv_mpu_cfg->requested_sensors); + ERROR_CHECK_FIRST(first_result, result); + result = first_result; + } + return result; +} + +/** + * Configures the MPU I2C Master + * + * @mldl_cfg Handle to the configuration data + * @gyro_handle handle to the gyro communictation interface + * @slave Can be Null if turning off the slave + * @slave_pdata Can be null if turning off the slave + * @slave_id enum ext_slave_type to determine which index to use + * + * + * This fucntion configures the slaves by: + * 1) Setting up the read + * a) Read Register + * b) Read Length + * 2) Set up the data trigger (MPU6050 only) + * a) Set trigger write register + * b) Set Trigger write value + * 3) Set up the divider (MPU6050 only) + * 4) Set the slave bypass mode depending on slave + * + * returns INV_SUCCESS or non-zero error code + */ + +static int mpu_set_slave_mpu60xx(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *slave_pdata, + int slave_id) +{ + int result; + unsigned char reg; + /* Slave values */ + unsigned char slave_reg; + unsigned char slave_len; + unsigned char slave_endian; + unsigned char slave_address; + /* Which MPU6050 registers to use */ + unsigned char addr_reg; + unsigned char reg_reg; + unsigned char ctrl_reg; + /* Which MPU6050 registers to use for the trigger */ + unsigned char addr_trig_reg; + unsigned char reg_trig_reg; + unsigned char ctrl_trig_reg; + + unsigned char bits_slave_delay = 0; + /* Divide down rate for the Slave, from the mpu rate */ + unsigned char d0_trig_reg; + unsigned char delay_ctrl_orig; + unsigned char delay_ctrl; + long divider; + + if (NULL == slave || NULL == slave_pdata) { + slave_reg = 0; + slave_len = 0; + slave_endian = 0; + slave_address = 0; + } else { + slave_reg = slave->read_reg; + slave_len = slave->read_len; + slave_endian = slave->endian; + slave_address = slave_pdata->address; + slave_address |= BIT_I2C_READ; + } + + switch (slave_id) { + case EXT_SLAVE_TYPE_ACCEL: + addr_reg = MPUREG_I2C_SLV1_ADDR; + reg_reg = MPUREG_I2C_SLV1_REG; + ctrl_reg = MPUREG_I2C_SLV1_CTRL; + addr_trig_reg = 0; + reg_trig_reg = 0; + ctrl_trig_reg = 0; + bits_slave_delay = BIT_SLV1_DLY_EN; + break; + case EXT_SLAVE_TYPE_COMPASS: + addr_reg = MPUREG_I2C_SLV0_ADDR; + reg_reg = MPUREG_I2C_SLV0_REG; + ctrl_reg = MPUREG_I2C_SLV0_CTRL; + addr_trig_reg = MPUREG_I2C_SLV2_ADDR; + reg_trig_reg = MPUREG_I2C_SLV2_REG; + ctrl_trig_reg = MPUREG_I2C_SLV2_CTRL; + d0_trig_reg = MPUREG_I2C_SLV2_DO; + bits_slave_delay = BIT_SLV2_DLY_EN | BIT_SLV0_DLY_EN; + break; + case EXT_SLAVE_TYPE_PRESSURE: + addr_reg = MPUREG_I2C_SLV3_ADDR; + reg_reg = MPUREG_I2C_SLV3_REG; + ctrl_reg = MPUREG_I2C_SLV3_CTRL; + addr_trig_reg = MPUREG_I2C_SLV4_ADDR; + reg_trig_reg = MPUREG_I2C_SLV4_REG; + ctrl_trig_reg = MPUREG_I2C_SLV4_CTRL; + bits_slave_delay = BIT_SLV4_DLY_EN | BIT_SLV3_DLY_EN; + break; + default: + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + }; + + /* return if this slave has already been set */ + if ((slave_address && + ((mldl_cfg->inv_mpu_state->i2c_slaves_enabled & bits_slave_delay) + == bits_slave_delay)) || + (!slave_address && + (mldl_cfg->inv_mpu_state->i2c_slaves_enabled & bits_slave_delay) == + 0)) + return 0; + + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, true); + + /* Address */ + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + addr_reg, slave_address); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + /* Register */ + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + reg_reg, slave_reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Length, byte swapping, grouping & enable */ + if (slave_len > BITS_SLV_LENG) { + MPL_LOGW("Limiting slave burst read length to " + "the allowed maximum (15B, req. %d)\n", slave_len); + slave_len = BITS_SLV_LENG; + return INV_ERROR_INVALID_CONFIGURATION; + } + reg = slave_len; + if (slave_endian == EXT_SLAVE_LITTLE_ENDIAN) { + reg |= BIT_SLV_BYTE_SW; + if (slave_reg & 1) + reg |= BIT_SLV_GRP; + } + if (slave_address) + reg |= BIT_SLV_ENABLE; + + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + ctrl_reg, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Trigger */ + if (addr_trig_reg) { + /* If slave address is 0 this clears the trigger */ + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + addr_trig_reg, + slave_address & ~BIT_I2C_READ); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + if (slave && slave->trigger && reg_trig_reg) { + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + reg_trig_reg, + slave->trigger->reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + ctrl_trig_reg, + BIT_SLV_ENABLE | 0x01); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + d0_trig_reg, + slave->trigger->value); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } else if (ctrl_trig_reg) { + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + ctrl_trig_reg, 0x00); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + /* Data rate */ + if (slave) { + struct ext_slave_config config; + long data; + config.key = MPU_SLAVE_CONFIG_ODR_RESUME; + config.len = sizeof(long); + config.apply = false; + config.data = &data; + if (!(slave->get_config)) + return INV_ERROR_INVALID_CONFIGURATION; + + result = slave->get_config(NULL, slave, slave_pdata, &config); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + MPL_LOGI("Slave %d ODR: %ld Hz\n", slave_id, data / 1000); + divider = ((1000 * inv_mpu_get_sampling_rate_hz( + mldl_cfg->mpu_gyro_cfg)) + / data) - 1; + } else { + divider = 0; + } + + result = inv_serial_read(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + MPUREG_I2C_MST_DELAY_CTRL, + 1, &delay_ctrl_orig); + delay_ctrl = delay_ctrl_orig; + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (divider > 0 && divider <= MASK_I2C_MST_DLY) { + result = inv_serial_read(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + MPUREG_I2C_SLV4_CTRL, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if ((reg & MASK_I2C_MST_DLY) && + ((long)(reg & MASK_I2C_MST_DLY) != + (divider & MASK_I2C_MST_DLY))) { + MPL_LOGW("Changing slave divider: %ld to %ld\n", + (long)(reg & MASK_I2C_MST_DLY), + (divider & MASK_I2C_MST_DLY)); + + } + reg |= (unsigned char)(divider & MASK_I2C_MST_DLY); + result = inv_serial_single_write(gyro_handle, + mldl_cfg->mpu_chip_info->addr, + MPUREG_I2C_SLV4_CTRL, + reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + delay_ctrl |= bits_slave_delay; + } else { + delay_ctrl &= ~(bits_slave_delay); + } + if (delay_ctrl != delay_ctrl_orig) { + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_I2C_MST_DELAY_CTRL, + delay_ctrl); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + if (slave_address) + mldl_cfg->inv_mpu_state->i2c_slaves_enabled |= + bits_slave_delay; + else + mldl_cfg->inv_mpu_state->i2c_slaves_enabled &= + ~bits_slave_delay; + + return result; +} + +static int mpu_set_slave(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *slave_pdata, + int slave_id) +{ + return mpu_set_slave_mpu60xx(mldl_cfg, gyro_handle, slave, + slave_pdata, slave_id); +} +/** + * Check to see if the gyro was reset by testing a couple of registers known + * to change on reset. + * + * @mldl_cfg mldl configuration structure + * @gyro_handle handle used to communicate with the gyro + * + * @return INV_SUCCESS or non-zero error code + */ +static int mpu_was_reset(struct mldl_cfg *mldl_cfg, void *gyro_handle) +{ + int result = INV_SUCCESS; + unsigned char reg; + + result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_DMP_CFG_2, 1, ®); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (mldl_cfg->mpu_gyro_cfg->dmp_cfg2 != reg) + return true; + + if (0 != mldl_cfg->mpu_gyro_cfg->dmp_cfg1) + return false; + + /* Inconclusive assume it was reset */ + return true; +} + + +int inv_mpu_set_firmware(struct mldl_cfg *mldl_cfg, void *mlsl_handle, + const unsigned char *data, int size) +{ + int bank, offset, write_size; + int result; + unsigned char read[MPU_MEM_BANK_SIZE]; + + if (mldl_cfg->inv_mpu_state->status & MPU_DEVICE_IS_SUSPENDED) { +#if INV_CACHE_DMP == 1 + memcpy(mldl_cfg->mpu_ram->ram, data, size); + return INV_SUCCESS; +#else + LOG_RESULT_LOCATION(INV_ERROR_MEMORY_SET); + return INV_ERROR_MEMORY_SET; +#endif + } + + if (!(mldl_cfg->inv_mpu_state->status & MPU_DMP_IS_SUSPENDED)) { + LOG_RESULT_LOCATION(INV_ERROR_MEMORY_SET); + return INV_ERROR_MEMORY_SET; + } + /* Write and verify memory */ + for (bank = 0; size > 0; bank++, + size -= write_size, + data += write_size) { + if (size > MPU_MEM_BANK_SIZE) + write_size = MPU_MEM_BANK_SIZE; + else + write_size = size; + + result = inv_serial_write_mem(mlsl_handle, + mldl_cfg->mpu_chip_info->addr, + ((bank << 8) | 0x00), + write_size, + data); + if (result) { + LOG_RESULT_LOCATION(result); + MPL_LOGE("Write mem error in bank %d\n", bank); + return result; + } + result = inv_serial_read_mem(mlsl_handle, + mldl_cfg->mpu_chip_info->addr, + ((bank << 8) | 0x00), + write_size, + read); + if (result) { + LOG_RESULT_LOCATION(result); + MPL_LOGE("Read mem error in bank %d\n", bank); + return result; + } + +#define ML_SKIP_CHECK 38 + for (offset = 0; offset < write_size; offset++) { + /* skip the register memory locations */ + if (bank == 0 && offset < ML_SKIP_CHECK) + continue; + if (data[offset] != read[offset]) { + result = INV_ERROR_SERIAL_WRITE; + break; + } + } + if (result != INV_SUCCESS) { + LOG_RESULT_LOCATION(result); + MPL_LOGE("Read data mismatch at bank %d, offset %d\n", + bank, offset); + return result; + } + } + return INV_SUCCESS; +} + +static int gyro_resume(struct mldl_cfg *mldl_cfg, void *gyro_handle, + unsigned long sensors) +{ + int result; + int ii; + unsigned char reg; + unsigned char regs[7]; + + /* Wake up the part */ + result = mpu60xx_pwr_mgmt(mldl_cfg, gyro_handle, false, sensors); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Always set the INT_ENABLE and DIVIDER as the Accel Only mode for 6050 + can set these too */ + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_INT_ENABLE, (mldl_cfg->mpu_gyro_cfg->int_config)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_SMPLRT_DIV, mldl_cfg->mpu_gyro_cfg->divider); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (!(mldl_cfg->inv_mpu_state->status & MPU_GYRO_NEEDS_CONFIG) && + !mpu_was_reset(mldl_cfg, gyro_handle)) { + return INV_SUCCESS; + } + + /* Configure the MPU */ + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = mpu_set_clock_source(gyro_handle, mldl_cfg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + reg = MPUREG_GYRO_CONFIG_VALUE(0, 0, 0, + mldl_cfg->mpu_gyro_cfg->full_scale); + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_GYRO_CONFIG, reg); + reg = MPUREG_CONFIG_VALUE(mldl_cfg->mpu_gyro_cfg->ext_sync, + mldl_cfg->mpu_gyro_cfg->lpf); + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_CONFIG, reg); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_DMP_CFG_1, mldl_cfg->mpu_gyro_cfg->dmp_cfg1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_DMP_CFG_2, mldl_cfg->mpu_gyro_cfg->dmp_cfg2); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Write and verify memory */ +#if INV_CACHE_DMP != 0 + inv_mpu_set_firmware(mldl_cfg, gyro_handle, + mldl_cfg->mpu_ram->ram, mldl_cfg->mpu_ram->length); +#endif + + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_XG_OFFS_TC, + ((mldl_cfg->mpu_offsets->tc[0] << 1) & BITS_XG_OFFS_TC)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + regs[0] = ((mldl_cfg->mpu_offsets->tc[1] << 1) & BITS_YG_OFFS_TC); + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_YG_OFFS_TC, regs[0]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_single_write( + gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_ZG_OFFS_TC, + ((mldl_cfg->mpu_offsets->tc[2] << 1) & BITS_ZG_OFFS_TC)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + regs[0] = MPUREG_X_OFFS_USRH; + for (ii = 0; ii < ARRAY_SIZE(mldl_cfg->mpu_offsets->gyro); ii++) { + regs[1 + ii * 2] = + (unsigned char)(mldl_cfg->mpu_offsets->gyro[ii] >> 8) + & 0xff; + regs[1 + ii * 2 + 1] = + (unsigned char)(mldl_cfg->mpu_offsets->gyro[ii] & 0xff); + } + result = inv_serial_write(gyro_handle, mldl_cfg->mpu_chip_info->addr, + 7, regs); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Configure slaves */ + result = inv_mpu_set_level_shifter_bit(mldl_cfg, gyro_handle, + mldl_cfg->pdata->level_shifter); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->inv_mpu_state->status &= ~MPU_GYRO_NEEDS_CONFIG; + + return result; +} + +int gyro_config(void *mlsl_handle, + struct mldl_cfg *mldl_cfg, + struct ext_slave_config *data) +{ + struct mpu_gyro_cfg *mpu_gyro_cfg = mldl_cfg->mpu_gyro_cfg; + struct mpu_chip_info *mpu_chip_info = mldl_cfg->mpu_chip_info; + struct mpu_offsets *mpu_offsets = mldl_cfg->mpu_offsets; + int ii; + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_INT_CONFIG: + mpu_gyro_cfg->int_config = *((__u8 *)data->data); + break; + case MPU_SLAVE_EXT_SYNC: + mpu_gyro_cfg->ext_sync = *((__u8 *)data->data); + break; + case MPU_SLAVE_FULL_SCALE: + mpu_gyro_cfg->full_scale = *((__u8 *)data->data); + break; + case MPU_SLAVE_LPF: + mpu_gyro_cfg->lpf = *((__u8 *)data->data); + break; + case MPU_SLAVE_CLK_SRC: + mpu_gyro_cfg->clk_src = *((__u8 *)data->data); + break; + case MPU_SLAVE_DIVIDER: + mpu_gyro_cfg->divider = *((__u8 *)data->data); + break; + case MPU_SLAVE_DMP_ENABLE: + mpu_gyro_cfg->dmp_enable = *((__u8 *)data->data); + break; + case MPU_SLAVE_FIFO_ENABLE: + mpu_gyro_cfg->fifo_enable = *((__u8 *)data->data); + break; + case MPU_SLAVE_DMP_CFG1: + mpu_gyro_cfg->dmp_cfg1 = *((__u8 *)data->data); + break; + case MPU_SLAVE_DMP_CFG2: + mpu_gyro_cfg->dmp_cfg2 = *((__u8 *)data->data); + break; + case MPU_SLAVE_TC: + for (ii = 0; ii < GYRO_NUM_AXES; ii++) + mpu_offsets->tc[ii] = ((__u8 *)data->data)[ii]; + break; + case MPU_SLAVE_GYRO: + for (ii = 0; ii < GYRO_NUM_AXES; ii++) + mpu_offsets->gyro[ii] = ((__u16 *)data->data)[ii]; + break; + case MPU_SLAVE_ADDR: + mpu_chip_info->addr = *((__u8 *)data->data); + break; + case MPU_SLAVE_PRODUCT_REVISION: + mpu_chip_info->product_revision = *((__u8 *)data->data); + break; + case MPU_SLAVE_SILICON_REVISION: + mpu_chip_info->silicon_revision = *((__u8 *)data->data); + break; + case MPU_SLAVE_PRODUCT_ID: + mpu_chip_info->product_id = *((__u8 *)data->data); + break; + case MPU_SLAVE_GYRO_SENS_TRIM: + mpu_chip_info->gyro_sens_trim = *((__u16 *)data->data); + break; + case MPU_SLAVE_ACCEL_SENS_TRIM: + mpu_chip_info->accel_sens_trim = *((__u16 *)data->data); + break; + case MPU_SLAVE_RAM: + if (data->len != mldl_cfg->mpu_ram->length) + return INV_ERROR_INVALID_PARAMETER; + + memcpy(mldl_cfg->mpu_ram->ram, data->data, data->len); + break; + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + mldl_cfg->inv_mpu_state->status |= MPU_GYRO_NEEDS_CONFIG; + return INV_SUCCESS; +} + +int gyro_get_config(void *mlsl_handle, + struct mldl_cfg *mldl_cfg, + struct ext_slave_config *data) +{ + struct mpu_gyro_cfg *mpu_gyro_cfg = mldl_cfg->mpu_gyro_cfg; + struct mpu_chip_info *mpu_chip_info = mldl_cfg->mpu_chip_info; + struct mpu_offsets *mpu_offsets = mldl_cfg->mpu_offsets; + int ii; + + if (!data->data) + return INV_ERROR_INVALID_PARAMETER; + + switch (data->key) { + case MPU_SLAVE_INT_CONFIG: + *((__u8 *)data->data) = mpu_gyro_cfg->int_config; + break; + case MPU_SLAVE_EXT_SYNC: + *((__u8 *)data->data) = mpu_gyro_cfg->ext_sync; + break; + case MPU_SLAVE_FULL_SCALE: + *((__u8 *)data->data) = mpu_gyro_cfg->full_scale; + break; + case MPU_SLAVE_LPF: + *((__u8 *)data->data) = mpu_gyro_cfg->lpf; + break; + case MPU_SLAVE_CLK_SRC: + *((__u8 *)data->data) = mpu_gyro_cfg->clk_src; + break; + case MPU_SLAVE_DIVIDER: + *((__u8 *)data->data) = mpu_gyro_cfg->divider; + break; + case MPU_SLAVE_DMP_ENABLE: + *((__u8 *)data->data) = mpu_gyro_cfg->dmp_enable; + break; + case MPU_SLAVE_FIFO_ENABLE: + *((__u8 *)data->data) = mpu_gyro_cfg->fifo_enable; + break; + case MPU_SLAVE_DMP_CFG1: + *((__u8 *)data->data) = mpu_gyro_cfg->dmp_cfg1; + break; + case MPU_SLAVE_DMP_CFG2: + *((__u8 *)data->data) = mpu_gyro_cfg->dmp_cfg2; + break; + case MPU_SLAVE_TC: + for (ii = 0; ii < GYRO_NUM_AXES; ii++) + ((__u8 *)data->data)[ii] = mpu_offsets->tc[ii]; + break; + case MPU_SLAVE_GYRO: + for (ii = 0; ii < GYRO_NUM_AXES; ii++) + ((__u16 *)data->data)[ii] = mpu_offsets->gyro[ii]; + break; + case MPU_SLAVE_ADDR: + *((__u8 *)data->data) = mpu_chip_info->addr; + break; + case MPU_SLAVE_PRODUCT_REVISION: + *((__u8 *)data->data) = mpu_chip_info->product_revision; + break; + case MPU_SLAVE_SILICON_REVISION: + *((__u8 *)data->data) = mpu_chip_info->silicon_revision; + break; + case MPU_SLAVE_PRODUCT_ID: + *((__u8 *)data->data) = mpu_chip_info->product_id; + break; + case MPU_SLAVE_GYRO_SENS_TRIM: + *((__u16 *)data->data) = mpu_chip_info->gyro_sens_trim; + break; + case MPU_SLAVE_ACCEL_SENS_TRIM: + *((__u16 *)data->data) = mpu_chip_info->accel_sens_trim; + break; + case MPU_SLAVE_RAM: + if (data->len != mldl_cfg->mpu_ram->length) + return INV_ERROR_INVALID_PARAMETER; + + memcpy(data->data, mldl_cfg->mpu_ram->ram, data->len); + break; + default: + LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED); + return INV_ERROR_FEATURE_NOT_IMPLEMENTED; + }; + + return INV_SUCCESS; +} + + +/******************************************************************************* + ******************************************************************************* + * Exported functions + ******************************************************************************* + ******************************************************************************/ + +/** + * Initializes the pdata structure to defaults. + * + * Opens the device to read silicon revision, product id and whoami. + * + * @mldl_cfg + * The internal device configuration data structure. + * @mlsl_handle + * The serial communication handle. + * + * @return INV_SUCCESS if silicon revision, product id and woami are supported + * by this software. + */ +int inv_mpu_open(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + void *compass_handle, void *pressure_handle) +{ + int result; + void *slave_handle[EXT_SLAVE_NUM_TYPES]; + int ii; + + /* Default is Logic HIGH, pushpull, latch disabled, anyread to clear */ + ii = 0; + mldl_cfg->inv_mpu_cfg->ignore_system_suspend = false; + mldl_cfg->mpu_gyro_cfg->int_config = BIT_DMP_INT_EN; + mldl_cfg->mpu_gyro_cfg->clk_src = MPU_CLK_SEL_PLLGYROZ; + mldl_cfg->mpu_gyro_cfg->lpf = MPU_FILTER_42HZ; + mldl_cfg->mpu_gyro_cfg->full_scale = MPU_FS_2000DPS; + mldl_cfg->mpu_gyro_cfg->divider = 4; + mldl_cfg->mpu_gyro_cfg->dmp_enable = 1; + mldl_cfg->mpu_gyro_cfg->fifo_enable = 1; + mldl_cfg->mpu_gyro_cfg->ext_sync = 0; + mldl_cfg->mpu_gyro_cfg->dmp_cfg1 = 0; + mldl_cfg->mpu_gyro_cfg->dmp_cfg2 = 0; + mldl_cfg->inv_mpu_state->status = + MPU_DMP_IS_SUSPENDED | + MPU_GYRO_IS_SUSPENDED | + MPU_ACCEL_IS_SUSPENDED | + MPU_COMPASS_IS_SUSPENDED | + MPU_PRESSURE_IS_SUSPENDED | + MPU_DEVICE_IS_SUSPENDED; + mldl_cfg->inv_mpu_state->i2c_slaves_enabled = 0; + + slave_handle[EXT_SLAVE_TYPE_GYROSCOPE] = gyro_handle; + slave_handle[EXT_SLAVE_TYPE_ACCEL] = accel_handle; + slave_handle[EXT_SLAVE_TYPE_COMPASS] = compass_handle; + slave_handle[EXT_SLAVE_TYPE_PRESSURE] = pressure_handle; + + if (mldl_cfg->mpu_chip_info->addr == 0) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + /* + * Reset, + * Take the DMP out of sleep, and + * read the product_id, sillicon rev and whoami + */ + mldl_cfg->inv_mpu_state->status &= ~MPU_GYRO_IS_BYPASSED; + result = mpu60xx_pwr_mgmt(mldl_cfg, gyro_handle, true, + INV_THREE_AXIS_GYRO); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + result = inv_get_silicon_rev(mldl_cfg, gyro_handle); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Get the factory temperature compensation offsets */ + result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_XG_OFFS_TC, 1, + &mldl_cfg->mpu_offsets->tc[0]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_YG_OFFS_TC, 1, + &mldl_cfg->mpu_offsets->tc[1]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr, + MPUREG_ZG_OFFS_TC, 1, + &mldl_cfg->mpu_offsets->tc[2]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Into bypass mode before sleeping and calling the slaves init */ + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, true); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = inv_mpu_set_level_shifter_bit(mldl_cfg, gyro_handle, + mldl_cfg->pdata->level_shifter); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + for (ii = 0; ii < GYRO_NUM_AXES; ii++) { + mldl_cfg->mpu_offsets->tc[ii] = + (mldl_cfg->mpu_offsets->tc[ii] & BITS_XG_OFFS_TC) >> 1; + } + +#if INV_CACHE_DMP != 0 + result = mpu60xx_pwr_mgmt(mldl_cfg, gyro_handle, false, 0); +#endif + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + + return result; + +} + +/** + * Close the mpu interface + * + * @mldl_cfg pointer to the configuration structure + * @mlsl_handle pointer to the serial layer handle + * + * @return INV_SUCCESS or non-zero error code + */ +int inv_mpu_close(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle) +{ + return 0; +} + +/** + * @brief resume the MPU device and all the other sensor + * devices from their low power state. + * + * @mldl_cfg + * pointer to the configuration structure + * @gyro_handle + * the main file handle to the MPU device. + * @accel_handle + * an handle to the accelerometer device, if sitting + * onto a separate bus. Can match mlsl_handle if + * the accelerometer device operates on the same + * primary bus of MPU. + * @compass_handle + * an handle to the compass device, if sitting + * onto a separate bus. Can match mlsl_handle if + * the compass device operates on the same + * primary bus of MPU. + * @pressure_handle + * an handle to the pressure sensor device, if sitting + * onto a separate bus. Can match mlsl_handle if + * the pressure sensor device operates on the same + * primary bus of MPU. + * @resume_gyro + * whether resuming the gyroscope device is + * actually needed (if the device supports low power + * mode of some sort). + * @resume_accel + * whether resuming the accelerometer device is + * actually needed (if the device supports low power + * mode of some sort). + * @resume_compass + * whether resuming the compass device is + * actually needed (if the device supports low power + * mode of some sort). + * @resume_pressure + * whether resuming the pressure sensor device is + * actually needed (if the device supports low power + * mode of some sort). + * @return INV_SUCCESS or a non-zero error code. + */ +int inv_mpu_resume(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle, + unsigned long sensors) +{ + int result = INV_SUCCESS; + int ii; + bool resume_slave[EXT_SLAVE_NUM_TYPES]; + bool resume_dmp = sensors & INV_DMP_PROCESSOR; + void *slave_handle[EXT_SLAVE_NUM_TYPES]; + resume_slave[EXT_SLAVE_TYPE_GYROSCOPE] = + (sensors & (INV_X_GYRO | INV_Y_GYRO | INV_Z_GYRO)); + resume_slave[EXT_SLAVE_TYPE_ACCEL] = + sensors & INV_THREE_AXIS_ACCEL; + resume_slave[EXT_SLAVE_TYPE_COMPASS] = + sensors & INV_THREE_AXIS_COMPASS; + resume_slave[EXT_SLAVE_TYPE_PRESSURE] = + sensors & INV_THREE_AXIS_PRESSURE; + + slave_handle[EXT_SLAVE_TYPE_GYROSCOPE] = gyro_handle; + slave_handle[EXT_SLAVE_TYPE_ACCEL] = accel_handle; + slave_handle[EXT_SLAVE_TYPE_COMPASS] = compass_handle; + slave_handle[EXT_SLAVE_TYPE_PRESSURE] = pressure_handle; + + + mldl_print_cfg(mldl_cfg); + + /* Skip the Gyro since slave[EXT_SLAVE_TYPE_GYROSCOPE] is NULL */ + for (ii = EXT_SLAVE_TYPE_ACCEL; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (resume_slave[ii] && + ((!mldl_cfg->slave[ii]) || + (!mldl_cfg->slave[ii]->resume))) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + } + + if ((resume_slave[EXT_SLAVE_TYPE_GYROSCOPE] || resume_dmp) + && ((mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_SUSPENDED) || + (mldl_cfg->inv_mpu_state->status & MPU_GYRO_NEEDS_CONFIG))) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = dmp_stop(mldl_cfg, gyro_handle); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = gyro_resume(mldl_cfg, gyro_handle, sensors); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!mldl_cfg->slave[ii] || + !mldl_cfg->pdata_slave[ii] || + !resume_slave[ii] || + !(mldl_cfg->inv_mpu_state->status & (1 << ii))) + continue; + + if (EXT_SLAVE_BUS_SECONDARY == + mldl_cfg->pdata_slave[ii]->bus) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, + true); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = mldl_cfg->slave[ii]->resume(slave_handle[ii], + mldl_cfg->slave[ii], + mldl_cfg->pdata_slave[ii]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + mldl_cfg->inv_mpu_state->status &= ~(1 << ii); + } + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (resume_dmp && + !(mldl_cfg->inv_mpu_state->status & (1 << ii)) && + mldl_cfg->pdata_slave[ii] && + EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata_slave[ii]->bus) { + result = mpu_set_slave(mldl_cfg, + gyro_handle, + mldl_cfg->slave[ii], + mldl_cfg->pdata_slave[ii], + mldl_cfg->slave[ii]->type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + } + + /* Turn on the master i2c iterface if necessary */ + if (resume_dmp) { + result = mpu_set_i2c_bypass( + mldl_cfg, gyro_handle, + !(mldl_cfg->inv_mpu_state->i2c_slaves_enabled)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + /* Now start */ + result = dmp_start(mldl_cfg, gyro_handle); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + mldl_cfg->inv_mpu_cfg->requested_sensors = sensors; + + return result; +} + +/** + * @brief suspend the MPU device and all the other sensor + * devices into their low power state. + * @mldl_cfg + * a pointer to the struct mldl_cfg internal data + * structure. + * @gyro_handle + * the main file handle to the MPU device. + * @accel_handle + * an handle to the accelerometer device, if sitting + * onto a separate bus. Can match gyro_handle if + * the accelerometer device operates on the same + * primary bus of MPU. + * @compass_handle + * an handle to the compass device, if sitting + * onto a separate bus. Can match gyro_handle if + * the compass device operates on the same + * primary bus of MPU. + * @pressure_handle + * an handle to the pressure sensor device, if sitting + * onto a separate bus. Can match gyro_handle if + * the pressure sensor device operates on the same + * primary bus of MPU. + * @accel + * whether suspending the accelerometer device is + * actually needed (if the device supports low power + * mode of some sort). + * @compass + * whether suspending the compass device is + * actually needed (if the device supports low power + * mode of some sort). + * @pressure + * whether suspending the pressure sensor device is + * actually needed (if the device supports low power + * mode of some sort). + * @return INV_SUCCESS or a non-zero error code. + */ +int inv_mpu_suspend(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *accel_handle, + void *compass_handle, + void *pressure_handle, + unsigned long sensors) +{ + int result = INV_SUCCESS; + int ii; + struct ext_slave_descr **slave = mldl_cfg->slave; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + bool suspend_dmp = ((sensors & INV_DMP_PROCESSOR) == INV_DMP_PROCESSOR); + bool suspend_slave[EXT_SLAVE_NUM_TYPES]; + void *slave_handle[EXT_SLAVE_NUM_TYPES]; + + suspend_slave[EXT_SLAVE_TYPE_GYROSCOPE] = + ((sensors & (INV_X_GYRO | INV_Y_GYRO | INV_Z_GYRO)) + == (INV_X_GYRO | INV_Y_GYRO | INV_Z_GYRO)); + suspend_slave[EXT_SLAVE_TYPE_ACCEL] = + ((sensors & INV_THREE_AXIS_ACCEL) == INV_THREE_AXIS_ACCEL); + suspend_slave[EXT_SLAVE_TYPE_COMPASS] = + ((sensors & INV_THREE_AXIS_COMPASS) == INV_THREE_AXIS_COMPASS); + suspend_slave[EXT_SLAVE_TYPE_PRESSURE] = + ((sensors & INV_THREE_AXIS_PRESSURE) == + INV_THREE_AXIS_PRESSURE); + + slave_handle[EXT_SLAVE_TYPE_GYROSCOPE] = gyro_handle; + slave_handle[EXT_SLAVE_TYPE_ACCEL] = accel_handle; + slave_handle[EXT_SLAVE_TYPE_COMPASS] = compass_handle; + slave_handle[EXT_SLAVE_TYPE_PRESSURE] = pressure_handle; + + if (suspend_dmp) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + result = dmp_stop(mldl_cfg, gyro_handle); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + /* Gyro */ + if (suspend_slave[EXT_SLAVE_TYPE_GYROSCOPE] && + !(mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_SUSPENDED)) { + result = mpu60xx_pwr_mgmt(mldl_cfg, gyro_handle, false, + ((~sensors) & INV_ALL_SENSORS)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + bool is_suspended = mldl_cfg->inv_mpu_state->status & (1 << ii); + if (!slave[ii] || !pdata_slave[ii] || + is_suspended || !suspend_slave[ii]) + continue; + + if (EXT_SLAVE_BUS_SECONDARY == pdata_slave[ii]->bus) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + result = slave[ii]->suspend(slave_handle[ii], + slave[ii], + pdata_slave[ii]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + if (EXT_SLAVE_BUS_SECONDARY == pdata_slave[ii]->bus) { + result = mpu_set_slave(mldl_cfg, gyro_handle, + NULL, NULL, + slave[ii]->type); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + mldl_cfg->inv_mpu_state->status |= (1 << ii); + } + + /* Re-enable the i2c master if there are configured slaves and DMP */ + if (!suspend_dmp) { + result = mpu_set_i2c_bypass( + mldl_cfg, gyro_handle, + !(mldl_cfg->inv_mpu_state->i2c_slaves_enabled)); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + mldl_cfg->inv_mpu_cfg->requested_sensors = (~sensors) & INV_ALL_SENSORS; + + return result; +} + +int inv_mpu_slave_read(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + int result; + int bypass_result; + int remain_bypassed = true; + + if (NULL == slave || NULL == slave->read) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION); + return INV_ERROR_INVALID_CONFIGURATION; + } + + if ((EXT_SLAVE_BUS_SECONDARY == pdata->bus) + && (!(mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_BYPASSED))) { + remain_bypassed = false; + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = slave->read(slave_handle, slave, pdata, data); + + if (!remain_bypassed) { + bypass_result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0); + if (bypass_result) { + LOG_RESULT_LOCATION(bypass_result); + return bypass_result; + } + } + return result; +} + +int inv_mpu_slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_config *data, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + int remain_bypassed = true; + + if (NULL == slave || NULL == slave->config) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION); + return INV_ERROR_INVALID_CONFIGURATION; + } + + if (data->apply && (EXT_SLAVE_BUS_SECONDARY == pdata->bus) + && (!(mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_BYPASSED))) { + remain_bypassed = false; + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = slave->config(slave_handle, slave, pdata, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (!remain_bypassed) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +int inv_mpu_get_slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_handle, + void *slave_handle, + struct ext_slave_config *data, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + int remain_bypassed = true; + + if (NULL == slave || NULL == slave->get_config) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION); + return INV_ERROR_INVALID_CONFIGURATION; + } + + if (data->apply && (EXT_SLAVE_BUS_SECONDARY == pdata->bus) + && (!(mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_BYPASSED))) { + remain_bypassed = false; + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + result = slave->get_config(slave_handle, slave, pdata, data); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + if (!remain_bypassed) { + result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + return result; +} + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/mpu6050/mldl_print_cfg.c b/drivers/misc/inv_mpu/mpu6050/mldl_print_cfg.c new file mode 100644 index 000000000000..7379a1707610 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu6050/mldl_print_cfg.c @@ -0,0 +1,138 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, All Rights Reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @addtogroup MLDL + * + * @{ + * @file mldl_print_cfg.c + * @brief The Motion Library Driver Layer. + */ + +#include <stddef.h> +#include "mldl_cfg.h" +#include "mlsl.h" +#include "linux/mpu.h" + +#include "log.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "mldl_print_cfg:" + +#undef MPL_LOG_NDEBUG +#define MPL_LOG_NDEBUG 1 + +void mldl_print_cfg(struct mldl_cfg *mldl_cfg) +{ + struct mpu_gyro_cfg *mpu_gyro_cfg = mldl_cfg->mpu_gyro_cfg; + struct mpu_offsets *mpu_offsets = mldl_cfg->mpu_offsets; + struct mpu_chip_info *mpu_chip_info = mldl_cfg->mpu_chip_info; + struct inv_mpu_cfg *inv_mpu_cfg = mldl_cfg->inv_mpu_cfg; + struct inv_mpu_state *inv_mpu_state = mldl_cfg->inv_mpu_state; + struct ext_slave_descr **slave = mldl_cfg->slave; + struct mpu_platform_data *pdata = mldl_cfg->pdata; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + /* mpu_gyro_cfg */ + MPL_LOGV("int_config = %02x\n", mpu_gyro_cfg->int_config); + MPL_LOGV("ext_sync = %02x\n", mpu_gyro_cfg->ext_sync); + MPL_LOGV("full_scale = %02x\n", mpu_gyro_cfg->full_scale); + MPL_LOGV("lpf = %02x\n", mpu_gyro_cfg->lpf); + MPL_LOGV("clk_src = %02x\n", mpu_gyro_cfg->clk_src); + MPL_LOGV("divider = %02x\n", mpu_gyro_cfg->divider); + MPL_LOGV("dmp_enable = %02x\n", mpu_gyro_cfg->dmp_enable); + MPL_LOGV("fifo_enable = %02x\n", mpu_gyro_cfg->fifo_enable); + MPL_LOGV("dmp_cfg1 = %02x\n", mpu_gyro_cfg->dmp_cfg1); + MPL_LOGV("dmp_cfg2 = %02x\n", mpu_gyro_cfg->dmp_cfg2); + /* mpu_offsets */ + MPL_LOGV("tc[0] = %02x\n", mpu_offsets->tc[0]); + MPL_LOGV("tc[1] = %02x\n", mpu_offsets->tc[1]); + MPL_LOGV("tc[2] = %02x\n", mpu_offsets->tc[2]); + MPL_LOGV("gyro[0] = %04x\n", mpu_offsets->gyro[0]); + MPL_LOGV("gyro[1] = %04x\n", mpu_offsets->gyro[1]); + MPL_LOGV("gyro[2] = %04x\n", mpu_offsets->gyro[2]); + + /* mpu_chip_info */ + MPL_LOGV("addr = %02x\n", mldl_cfg->mpu_chip_info->addr); + + MPL_LOGV("silicon_revision = %02x\n", mpu_chip_info->silicon_revision); + MPL_LOGV("product_revision = %02x\n", mpu_chip_info->product_revision); + MPL_LOGV("product_id = %02x\n", mpu_chip_info->product_id); + MPL_LOGV("gyro_sens_trim = %02x\n", mpu_chip_info->gyro_sens_trim); + MPL_LOGV("accel_sens_trim = %02x\n", mpu_chip_info->accel_sens_trim); + + MPL_LOGV("requested_sensors = %04x\n", inv_mpu_cfg->requested_sensors); + MPL_LOGV("ignore_system_suspend= %04x\n", + inv_mpu_cfg->ignore_system_suspend); + MPL_LOGV("status = %04x\n", inv_mpu_state->status); + MPL_LOGV("i2c_slaves_enabled= %04x\n", + inv_mpu_state->i2c_slaves_enabled); + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!slave[ii]) + continue; + MPL_LOGV("SLAVE %d:\n", ii); + MPL_LOGV(" suspend = %02x\n", (int)slave[ii]->suspend); + MPL_LOGV(" resume = %02x\n", (int)slave[ii]->resume); + MPL_LOGV(" read = %02x\n", (int)slave[ii]->read); + MPL_LOGV(" type = %02x\n", slave[ii]->type); + MPL_LOGV(" reg = %02x\n", slave[ii]->read_reg); + MPL_LOGV(" len = %02x\n", slave[ii]->read_len); + MPL_LOGV(" endian = %02x\n", slave[ii]->endian); + MPL_LOGV(" range.mantissa= %02x\n", + slave[ii]->range.mantissa); + MPL_LOGV(" range.fraction= %02x\n", + slave[ii]->range.fraction); + } + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + continue; + MPL_LOGV("PDATA_SLAVE[%d]\n", ii); + MPL_LOGV(" irq = %02x\n", pdata_slave[ii]->irq); + MPL_LOGV(" adapt_num = %02x\n", pdata_slave[ii]->adapt_num); + MPL_LOGV(" bus = %02x\n", pdata_slave[ii]->bus); + MPL_LOGV(" address = %02x\n", pdata_slave[ii]->address); + MPL_LOGV(" orientation=\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n", + pdata_slave[ii]->orientation[0], + pdata_slave[ii]->orientation[1], + pdata_slave[ii]->orientation[2], + pdata_slave[ii]->orientation[3], + pdata_slave[ii]->orientation[4], + pdata_slave[ii]->orientation[5], + pdata_slave[ii]->orientation[6], + pdata_slave[ii]->orientation[7], + pdata_slave[ii]->orientation[8]); + } + + MPL_LOGV("pdata->int_config = %02x\n", pdata->int_config); + MPL_LOGV("pdata->level_shifter = %02x\n", pdata->level_shifter); + MPL_LOGV("pdata->orientation =\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n", + pdata->orientation[0], pdata->orientation[1], + pdata->orientation[2], pdata->orientation[3], + pdata->orientation[4], pdata->orientation[5], + pdata->orientation[6], pdata->orientation[7], + pdata->orientation[8]); +} diff --git a/drivers/misc/inv_mpu/mpu6050/mlsl-kernel.c b/drivers/misc/inv_mpu/mpu6050/mlsl-kernel.c new file mode 100644 index 000000000000..f1c228f48fe3 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu6050/mlsl-kernel.c @@ -0,0 +1,420 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, All Rights Reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + $ + */ + +#include "mlsl.h" +#include <linux/i2c.h> +#include "log.h" +#include "mpu6050b1.h" + +static int inv_i2c_write(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned int len, unsigned char const *data) +{ + struct i2c_msg msgs[1]; + int res; + + if (!data || !i2c_adap) { + LOG_RESULT_LOCATION(-EINVAL); + return -EINVAL; + } + + msgs[0].addr = address; + msgs[0].flags = 0; /* write */ + msgs[0].buf = (unsigned char *)data; + msgs[0].len = len; + + res = i2c_transfer(i2c_adap, msgs, 1); + if (res < 1) { + if (res == 0) + res = -EIO; + LOG_RESULT_LOCATION(res); + return res; + } else + return 0; +} + +static int inv_i2c_write_register(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned char reg, unsigned char value) +{ + unsigned char data[2]; + + data[0] = reg; + data[1] = value; + return inv_i2c_write(i2c_adap, address, 2, data); +} + +static int inv_i2c_read(struct i2c_adapter *i2c_adap, + unsigned char address, unsigned char reg, + unsigned int len, unsigned char *data) +{ + struct i2c_msg msgs[2]; + int res; + + if (!data || !i2c_adap) { + LOG_RESULT_LOCATION(-EINVAL); + return -EINVAL; + } + + msgs[0].addr = address; + msgs[0].flags = 0; /* write */ + msgs[0].buf = ® + msgs[0].len = 1; + + msgs[1].addr = address; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = data; + msgs[1].len = len; + + res = i2c_transfer(i2c_adap, msgs, 2); + if (res < 2) { + if (res >= 0) + res = -EIO; + LOG_RESULT_LOCATION(res); + return res; + } else + return 0; +} + +static int mpu_memory_read(struct i2c_adapter *i2c_adap, + unsigned char mpu_addr, + unsigned short mem_addr, + unsigned int len, unsigned char *data) +{ + unsigned char bank[2]; + unsigned char addr[2]; + unsigned char buf; + + struct i2c_msg msgs[4]; + int res; + + if (!data || !i2c_adap) { + LOG_RESULT_LOCATION(-EINVAL); + return -EINVAL; + } + + bank[0] = MPUREG_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = MPUREG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf = MPUREG_MEM_R_W; + + /* write message */ + msgs[0].addr = mpu_addr; + msgs[0].flags = 0; + msgs[0].buf = bank; + msgs[0].len = sizeof(bank); + + msgs[1].addr = mpu_addr; + msgs[1].flags = 0; + msgs[1].buf = addr; + msgs[1].len = sizeof(addr); + + msgs[2].addr = mpu_addr; + msgs[2].flags = 0; + msgs[2].buf = &buf; + msgs[2].len = 1; + + msgs[3].addr = mpu_addr; + msgs[3].flags = I2C_M_RD; + msgs[3].buf = data; + msgs[3].len = len; + + res = i2c_transfer(i2c_adap, msgs, 4); + if (res != 4) { + if (res >= 0) + res = -EIO; + LOG_RESULT_LOCATION(res); + return res; + } else + return 0; +} + +static int mpu_memory_write(struct i2c_adapter *i2c_adap, + unsigned char mpu_addr, + unsigned short mem_addr, + unsigned int len, unsigned char const *data) +{ + unsigned char bank[2]; + unsigned char addr[2]; + unsigned char buf[513]; + + struct i2c_msg msgs[3]; + int res; + + if (!data || !i2c_adap) { + LOG_RESULT_LOCATION(-EINVAL); + return -EINVAL; + } + if (len >= (sizeof(buf) - 1)) { + LOG_RESULT_LOCATION(-ENOMEM); + return -ENOMEM; + } + + bank[0] = MPUREG_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = MPUREG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf[0] = MPUREG_MEM_R_W; + memcpy(buf + 1, data, len); + + /* write message */ + msgs[0].addr = mpu_addr; + msgs[0].flags = 0; + msgs[0].buf = bank; + msgs[0].len = sizeof(bank); + + msgs[1].addr = mpu_addr; + msgs[1].flags = 0; + msgs[1].buf = addr; + msgs[1].len = sizeof(addr); + + msgs[2].addr = mpu_addr; + msgs[2].flags = 0; + msgs[2].buf = (unsigned char *)buf; + msgs[2].len = len + 1; + + res = i2c_transfer(i2c_adap, msgs, 3); + if (res != 3) { + if (res >= 0) + res = -EIO; + LOG_RESULT_LOCATION(res); + return res; + } else + return 0; +} + +int inv_serial_single_write( + void *sl_handle, + unsigned char slave_addr, + unsigned char register_addr, + unsigned char data) +{ + return inv_i2c_write_register((struct i2c_adapter *)sl_handle, + slave_addr, register_addr, data); +} +EXPORT_SYMBOL(inv_serial_single_write); + +int inv_serial_write( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char const *data) +{ + int result; + const unsigned short data_length = length - 1; + const unsigned char start_reg_addr = data[0]; + unsigned char i2c_write[SERIAL_MAX_TRANSFER_SIZE + 1]; + unsigned short bytes_written = 0; + + while (bytes_written < data_length) { + unsigned short this_len = min(SERIAL_MAX_TRANSFER_SIZE, + data_length - bytes_written); + if (bytes_written == 0) { + result = inv_i2c_write((struct i2c_adapter *) + sl_handle, slave_addr, + 1 + this_len, data); + } else { + /* manually increment register addr between chunks */ + i2c_write[0] = start_reg_addr + bytes_written; + memcpy(&i2c_write[1], &data[1 + bytes_written], + this_len); + result = inv_i2c_write((struct i2c_adapter *) + sl_handle, slave_addr, + 1 + this_len, i2c_write); + } + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + bytes_written += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_write); + +int inv_serial_read( + void *sl_handle, + unsigned char slave_addr, + unsigned char register_addr, + unsigned short length, + unsigned char *data) +{ + int result; + unsigned short bytes_read = 0; + + if ((slave_addr & 0x7E) == DEFAULT_MPU_SLAVEADDR + && (register_addr == MPUREG_FIFO_R_W || + register_addr == MPUREG_MEM_R_W)) { + LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER); + return INV_ERROR_INVALID_PARAMETER; + } + + while (bytes_read < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read); + result = inv_i2c_read((struct i2c_adapter *)sl_handle, + slave_addr, register_addr + bytes_read, + this_len, &data[bytes_read]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + bytes_read += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_read); + +int inv_serial_write_mem( + void *sl_handle, + unsigned char slave_addr, + unsigned short mem_addr, + unsigned short length, + unsigned char const *data) +{ + int result; + unsigned short bytes_written = 0; + + if ((mem_addr & 0xFF) + length > MPU_MEM_BANK_SIZE) { + pr_err("memory read length (%d B) extends beyond its" + " limits (%d) if started at location %d\n", length, + MPU_MEM_BANK_SIZE, mem_addr & 0xFF); + return INV_ERROR_INVALID_PARAMETER; + } + while (bytes_written < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_written); + result = mpu_memory_write((struct i2c_adapter *)sl_handle, + slave_addr, mem_addr + bytes_written, + this_len, &data[bytes_written]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + bytes_written += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_write_mem); + +int inv_serial_read_mem( + void *sl_handle, + unsigned char slave_addr, + unsigned short mem_addr, + unsigned short length, + unsigned char *data) +{ + int result; + unsigned short bytes_read = 0; + + if ((mem_addr & 0xFF) + length > MPU_MEM_BANK_SIZE) { + printk + ("memory read length (%d B) extends beyond its limits (%d) " + "if started at location %d\n", length, + MPU_MEM_BANK_SIZE, mem_addr & 0xFF); + return INV_ERROR_INVALID_PARAMETER; + } + while (bytes_read < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read); + result = + mpu_memory_read((struct i2c_adapter *)sl_handle, + slave_addr, mem_addr + bytes_read, + this_len, &data[bytes_read]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + bytes_read += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_read_mem); + +int inv_serial_write_fifo( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char const *data) +{ + int result; + unsigned char i2c_write[SERIAL_MAX_TRANSFER_SIZE + 1]; + unsigned short bytes_written = 0; + + if (length > FIFO_HW_SIZE) { + printk(KERN_ERR + "maximum fifo write length is %d\n", FIFO_HW_SIZE); + return INV_ERROR_INVALID_PARAMETER; + } + while (bytes_written < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_written); + i2c_write[0] = MPUREG_FIFO_R_W; + memcpy(&i2c_write[1], &data[bytes_written], this_len); + result = inv_i2c_write((struct i2c_adapter *)sl_handle, + slave_addr, this_len + 1, i2c_write); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + bytes_written += this_len; + } + return 0; +} +EXPORT_SYMBOL(inv_serial_write_fifo); + +int inv_serial_read_fifo( + void *sl_handle, + unsigned char slave_addr, + unsigned short length, + unsigned char *data) +{ + int result; + unsigned short bytes_read = 0; + + if (length > FIFO_HW_SIZE) { + printk(KERN_ERR + "maximum fifo read length is %d\n", FIFO_HW_SIZE); + return INV_ERROR_INVALID_PARAMETER; + } + while (bytes_read < length) { + unsigned short this_len = + min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read); + result = inv_i2c_read((struct i2c_adapter *)sl_handle, + slave_addr, MPUREG_FIFO_R_W, this_len, + &data[bytes_read]); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + bytes_read += this_len; + } + + return 0; +} +EXPORT_SYMBOL(inv_serial_read_fifo); + +/** + * @} + */ diff --git a/drivers/misc/inv_mpu/mpu6050/mpu-dev.c b/drivers/misc/inv_mpu/mpu6050/mpu-dev.c new file mode 100644 index 000000000000..e171dc2a7eff --- /dev/null +++ b/drivers/misc/inv_mpu/mpu6050/mpu-dev.c @@ -0,0 +1,1309 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, All Rights Reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + $ + */ +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/signal.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/version.h> +#include <linux/pm.h> +#include <linux/mutex.h> +#include <linux/suspend.h> +#include <linux/poll.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include "mpuirq.h" +#include "slaveirq.h" +#include "mlsl.h" +#include "mldl_cfg.h" +#include <linux/mpu.h> + +#include "accel/mpu6050.h" + +/* Platform data for the MPU */ +struct mpu_private_data { + struct miscdevice dev; + struct i2c_client *client; + + /* mldl_cfg data */ + struct mldl_cfg mldl_cfg; + struct mpu_ram mpu_ram; + struct mpu_gyro_cfg mpu_gyro_cfg; + struct mpu_offsets mpu_offsets; + struct mpu_chip_info mpu_chip_info; + struct inv_mpu_cfg inv_mpu_cfg; + struct inv_mpu_state inv_mpu_state; + + struct mutex mutex; + wait_queue_head_t mpu_event_wait; + struct completion completion; + struct timer_list timeout; + struct notifier_block nb; + struct mpuirq_data mpu_pm_event; + int response_timeout; /* In seconds */ + unsigned long event; + int pid; + struct module *slave_modules[EXT_SLAVE_NUM_TYPES]; +}; + +struct mpu_private_data *mpu_private_data; + +static void mpu_pm_timeout(u_long data) +{ + struct mpu_private_data *mpu = (struct mpu_private_data *)data; + struct i2c_client *client = mpu->client; + dev_dbg(&client->adapter->dev, "%s\n", __func__); + complete(&mpu->completion); +} + +static int mpu_pm_notifier_callback(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct mpu_private_data *mpu = + container_of(nb, struct mpu_private_data, nb); + struct i2c_client *client = mpu->client; + struct timeval event_time; + dev_dbg(&client->adapter->dev, "%s: %ld\n", __func__, event); + + /* Prevent the file handle from being closed before we initialize + the completion event */ + mutex_lock(&mpu->mutex); + if (!(mpu->pid) || + (event != PM_SUSPEND_PREPARE && event != PM_POST_SUSPEND)) { + mutex_unlock(&mpu->mutex); + return NOTIFY_OK; + } + + if (event == PM_SUSPEND_PREPARE) + mpu->event = MPU_PM_EVENT_SUSPEND_PREPARE; + if (event == PM_POST_SUSPEND) + mpu->event = MPU_PM_EVENT_POST_SUSPEND; + + do_gettimeofday(&event_time); + mpu->mpu_pm_event.interruptcount++; + mpu->mpu_pm_event.irqtime = + (((long long)event_time.tv_sec) << 32) + event_time.tv_usec; + mpu->mpu_pm_event.data_type = MPUIRQ_DATA_TYPE_PM_EVENT; + mpu->mpu_pm_event.data = mpu->event; + + if (mpu->response_timeout > 0) { + mpu->timeout.expires = jiffies + mpu->response_timeout * HZ; + add_timer(&mpu->timeout); + } + INIT_COMPLETION(mpu->completion); + mutex_unlock(&mpu->mutex); + + wake_up_interruptible(&mpu->mpu_event_wait); + wait_for_completion(&mpu->completion); + del_timer_sync(&mpu->timeout); + dev_dbg(&client->adapter->dev, "%s: %ld DONE\n", __func__, event); + return NOTIFY_OK; +} + +static int mpu_dev_open(struct inode *inode, struct file *file) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + int result; + int ii; + dev_dbg(&client->adapter->dev, "%s\n", __func__); + dev_dbg(&client->adapter->dev, "current->pid %d\n", current->pid); + + result = mutex_lock_interruptible(&mpu->mutex); + if (mpu->pid) { + mutex_unlock(&mpu->mutex); + return -EBUSY; + } + mpu->pid = current->pid; + + /* Reset the sensors to the default */ + if (result) { + dev_err(&client->adapter->dev, + "%s: mutex_lock_interruptible returned %d\n", + __func__, result); + return result; + } + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) + __module_get(mpu->slave_modules[ii]); + + mutex_unlock(&mpu->mutex); + return 0; +} + +/* close function - called when the "file" /dev/mpu is closed in userspace */ +static int mpu_release(struct inode *inode, struct file *file) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int result = 0; + int ii; + struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + mutex_lock(&mpu->mutex); + mldl_cfg->inv_mpu_cfg->requested_sensors = 0; + result = inv_mpu_suspend(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + INV_ALL_SENSORS); + mpu->pid = 0; + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) + module_put(mpu->slave_modules[ii]); + + mutex_unlock(&mpu->mutex); + complete(&mpu->completion); + dev_dbg(&client->adapter->dev, "mpu_release\n"); + + return result; +} + +/* read function called when from /dev/mpu is read. Read from the FIFO */ +static ssize_t mpu_read(struct file *file, + char __user *buf, size_t count, loff_t *offset) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + size_t len = sizeof(mpu->mpu_pm_event) + sizeof(unsigned long); + int err; + + if (!mpu->event && (!(file->f_flags & O_NONBLOCK))) + wait_event_interruptible(mpu->mpu_event_wait, mpu->event); + + if (!mpu->event || !buf + || count < sizeof(mpu->mpu_pm_event)) + return 0; + + err = copy_to_user(buf, &mpu->mpu_pm_event, sizeof(mpu->mpu_pm_event)); + if (err) { + dev_err(&client->adapter->dev, + "Copy to user returned %d\n", err); + return -EFAULT; + } + mpu->event = 0; + return len; +} + +static unsigned int mpu_poll(struct file *file, struct poll_table_struct *poll) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + int mask = 0; + + poll_wait(file, &mpu->mpu_event_wait, poll); + if (mpu->event) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +static int mpu_dev_ioctl_get_ext_slave_platform_data( + struct i2c_client *client, + struct ext_slave_platform_data __user *arg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct ext_slave_platform_data *pdata_slave; + struct ext_slave_platform_data local_pdata_slave; + + if (copy_from_user(&local_pdata_slave, arg, sizeof(local_pdata_slave))) + return -EFAULT; + + if (local_pdata_slave.type >= EXT_SLAVE_NUM_TYPES) + return -EINVAL; + + pdata_slave = mpu->mldl_cfg.pdata_slave[local_pdata_slave.type]; + /* All but private data and irq_data */ + if (!pdata_slave) + return -ENODEV; + if (copy_to_user(arg, pdata_slave, sizeof(*pdata_slave))) + return -EFAULT; + return 0; +} + +static int mpu_dev_ioctl_get_mpu_platform_data( + struct i2c_client *client, + struct mpu_platform_data __user *arg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mpu_platform_data *pdata = mpu->mldl_cfg.pdata; + + if (copy_to_user(arg, pdata, sizeof(*pdata))) + return -EFAULT; + return 0; +} + +static int mpu_dev_ioctl_get_ext_slave_descr( + struct i2c_client *client, + struct ext_slave_descr __user *arg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct ext_slave_descr *slave; + struct ext_slave_descr local_slave; + + if (copy_from_user(&local_slave, arg, sizeof(local_slave))) + return -EFAULT; + + if (local_slave.type >= EXT_SLAVE_NUM_TYPES) + return -EINVAL; + + slave = mpu->mldl_cfg.slave[local_slave.type]; + /* All but private data and irq_data */ + if (!slave) + return -ENODEV; + if (copy_to_user(arg, slave, sizeof(*slave))) + return -EFAULT; + return 0; +} + + +/** + * slave_config() - Pass a requested slave configuration to the slave sensor + * + * @adapter the adaptor to use to communicate with the slave + * @mldl_cfg the mldl configuration structuer + * @slave pointer to the slave descriptor + * @usr_config The configuration to pass to the slave sensor + * + * returns 0 or non-zero error code + */ +static int inv_mpu_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + + retval = copy_from_user(&config, usr_config, sizeof(config)); + if (retval) + return -EFAULT; + + if (config.len && config.data) { + void *data; + data = kmalloc(config.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)config.data, config.len); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + config.data = data; + } + retval = gyro_config(gyro_adapter, mldl_cfg, &config); + kfree(config.data); + return retval; +} + +static int inv_mpu_get_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + void *user_data; + + retval = copy_from_user(&config, usr_config, sizeof(config)); + if (retval) + return -EFAULT; + + user_data = config.data; + if (config.len && config.data) { + void *data; + data = kmalloc(config.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)config.data, config.len); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + config.data = data; + } + retval = gyro_get_config(gyro_adapter, mldl_cfg, &config); + if (!retval) + retval = copy_to_user((unsigned char __user *)user_data, + config.data, config.len); + kfree(config.data); + return retval; +} + +static int slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + if ((!slave) || (!slave->config)) + return -ENODEV; + + retval = copy_from_user(&config, usr_config, sizeof(config)); + if (retval) + return -EFAULT; + + if (config.len && config.data) { + void *data; + data = kmalloc(config.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)config.data, config.len); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + config.data = data; + } + retval = inv_mpu_slave_config(mldl_cfg, gyro_adapter, slave_adapter, + &config, slave, pdata); + kfree(config.data); + return retval; +} + +static int slave_get_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + void *user_data; + if (!(slave) || !(slave->get_config)) + return -ENODEV; + + retval = copy_from_user(&config, usr_config, sizeof(config)); + if (retval) + return -EFAULT; + + user_data = config.data; + if (config.len && config.data) { + void *data; + data = kmalloc(config.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)config.data, config.len); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + config.data = data; + } + retval = inv_mpu_get_slave_config(mldl_cfg, gyro_adapter, + slave_adapter, &config, slave, pdata); + if (retval) { + kfree(config.data); + return retval; + } + retval = copy_to_user((unsigned char __user *)user_data, + config.data, config.len); + kfree(config.data); + return retval; +} + +static int inv_slave_read(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + void __user *usr_data) +{ + int retval; + unsigned char *data; + data = kzalloc(slave->read_len, GFP_KERNEL); + if (!data) + return -EFAULT; + + retval = inv_mpu_slave_read(mldl_cfg, gyro_adapter, slave_adapter, + slave, pdata, data); + + if ((!retval) && + (copy_to_user((unsigned char __user *)usr_data, + data, slave->read_len))) + retval = -EFAULT; + + kfree(data); + return retval; +} + +static int mpu_handle_mlsl(void *sl_handle, + unsigned char addr, + unsigned int cmd, + struct mpu_read_write __user *usr_msg) +{ + int retval = 0; + struct mpu_read_write msg; + unsigned char *user_data; + retval = copy_from_user(&msg, usr_msg, sizeof(msg)); + if (retval) + return -EFAULT; + + user_data = msg.data; + if (msg.length && msg.data) { + unsigned char *data; + data = kmalloc(msg.length, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)msg.data, msg.length); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + msg.data = data; + } else { + return -EPERM; + } + + switch (cmd) { + case MPU_READ: + retval = inv_serial_read(sl_handle, addr, + msg.address, msg.length, msg.data); + break; + case MPU_WRITE: + retval = inv_serial_write(sl_handle, addr, + msg.length, msg.data); + break; + case MPU_READ_MEM: + retval = inv_serial_read_mem(sl_handle, addr, + msg.address, msg.length, msg.data); + break; + case MPU_WRITE_MEM: + retval = inv_serial_write_mem(sl_handle, addr, + msg.address, msg.length, + msg.data); + break; + case MPU_READ_FIFO: + retval = inv_serial_read_fifo(sl_handle, addr, + msg.length, msg.data); + break; + case MPU_WRITE_FIFO: + retval = inv_serial_write_fifo(sl_handle, addr, + msg.length, msg.data); + break; + + }; + if (retval) { + dev_err(&((struct i2c_adapter *)sl_handle)->dev, + "%s: i2c %d error %d\n", + __func__, cmd, retval); + kfree(msg.data); + return retval; + } + retval = copy_to_user((unsigned char __user *)user_data, + msg.data, msg.length); + kfree(msg.data); + return retval; +} + +/* ioctl - I/O control */ +static long mpu_dev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int retval = 0; + struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_descr **slave = mldl_cfg->slave; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + retval = mutex_lock_interruptible(&mpu->mutex); + if (retval) { + dev_err(&client->adapter->dev, + "%s: mutex_lock_interruptible returned %d\n", + __func__, retval); + return retval; + } + + switch (cmd) { + case MPU_GET_EXT_SLAVE_PLATFORM_DATA: + retval = mpu_dev_ioctl_get_ext_slave_platform_data( + client, + (struct ext_slave_platform_data __user *)arg); + break; + case MPU_GET_MPU_PLATFORM_DATA: + retval = mpu_dev_ioctl_get_mpu_platform_data( + client, + (struct mpu_platform_data __user *)arg); + break; + case MPU_GET_EXT_SLAVE_DESCR: + retval = mpu_dev_ioctl_get_ext_slave_descr( + client, + (struct ext_slave_descr __user *)arg); + break; + case MPU_READ: + case MPU_WRITE: + case MPU_READ_MEM: + case MPU_WRITE_MEM: + case MPU_READ_FIFO: + case MPU_WRITE_FIFO: + retval = mpu_handle_mlsl( + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + mldl_cfg->mpu_chip_info->addr, cmd, + (struct mpu_read_write __user *)arg); + break; + case MPU_CONFIG_GYRO: + retval = inv_mpu_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_ACCEL: + retval = slave_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave[EXT_SLAVE_TYPE_ACCEL], + pdata_slave[EXT_SLAVE_TYPE_ACCEL], + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_COMPASS: + retval = slave_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave[EXT_SLAVE_TYPE_COMPASS], + pdata_slave[EXT_SLAVE_TYPE_COMPASS], + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_PRESSURE: + retval = slave_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + slave[EXT_SLAVE_TYPE_PRESSURE], + pdata_slave[EXT_SLAVE_TYPE_PRESSURE], + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_GYRO: + retval = inv_mpu_get_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_ACCEL: + retval = slave_get_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave[EXT_SLAVE_TYPE_ACCEL], + pdata_slave[EXT_SLAVE_TYPE_ACCEL], + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_COMPASS: + retval = slave_get_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave[EXT_SLAVE_TYPE_COMPASS], + pdata_slave[EXT_SLAVE_TYPE_COMPASS], + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_PRESSURE: + retval = slave_get_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + slave[EXT_SLAVE_TYPE_PRESSURE], + pdata_slave[EXT_SLAVE_TYPE_PRESSURE], + (struct ext_slave_config __user *)arg); + break; + case MPU_SUSPEND: + retval = inv_mpu_suspend( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + arg); + break; + case MPU_RESUME: + retval = inv_mpu_resume( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + arg); + break; + case MPU_PM_EVENT_HANDLED: + dev_dbg(&client->adapter->dev, "%s: %d\n", __func__, cmd); + complete(&mpu->completion); + break; + case MPU_READ_ACCEL: + retval = inv_slave_read( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave[EXT_SLAVE_TYPE_ACCEL], + pdata_slave[EXT_SLAVE_TYPE_ACCEL], + (unsigned char __user *)arg); + break; + case MPU_READ_COMPASS: + retval = inv_slave_read( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave[EXT_SLAVE_TYPE_COMPASS], + pdata_slave[EXT_SLAVE_TYPE_COMPASS], + (unsigned char __user *)arg); + break; + case MPU_READ_PRESSURE: + retval = inv_slave_read( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + slave[EXT_SLAVE_TYPE_PRESSURE], + pdata_slave[EXT_SLAVE_TYPE_PRESSURE], + (unsigned char __user *)arg); + break; + case MPU_GET_REQUESTED_SENSORS: + if (copy_to_user( + (__u32 __user *)arg, + &mldl_cfg->inv_mpu_cfg->requested_sensors, + sizeof(mldl_cfg->inv_mpu_cfg->requested_sensors))) + retval = -EFAULT; + break; + case MPU_SET_REQUESTED_SENSORS: + mldl_cfg->inv_mpu_cfg->requested_sensors = arg; + break; + case MPU_GET_IGNORE_SYSTEM_SUSPEND: + if (copy_to_user( + (unsigned char __user *)arg, + &mldl_cfg->inv_mpu_cfg->ignore_system_suspend, + sizeof(mldl_cfg->inv_mpu_cfg->ignore_system_suspend))) + retval = -EFAULT; + break; + case MPU_SET_IGNORE_SYSTEM_SUSPEND: + mldl_cfg->inv_mpu_cfg->ignore_system_suspend = arg; + break; + case MPU_GET_MLDL_STATUS: + if (copy_to_user( + (unsigned char __user *)arg, + &mldl_cfg->inv_mpu_state->status, + sizeof(mldl_cfg->inv_mpu_state->status))) + retval = -EFAULT; + break; + case MPU_GET_I2C_SLAVES_ENABLED: + if (copy_to_user( + (unsigned char __user *)arg, + &mldl_cfg->inv_mpu_state->i2c_slaves_enabled, + sizeof(mldl_cfg->inv_mpu_state->i2c_slaves_enabled))) + retval = -EFAULT; + break; + default: + dev_err(&client->adapter->dev, + "%s: Unknown cmd %x, arg %lu\n", + __func__, cmd, arg); + retval = -EINVAL; + }; + + mutex_unlock(&mpu->mutex); + dev_dbg(&client->adapter->dev, "%s: %08x, %08lx, %d\n", + __func__, cmd, arg, retval); + + if (retval > 0) + retval = -retval; + + return retval; +} + +void mpu_shutdown(struct i2c_client *client) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + mutex_lock(&mpu->mutex); + (void)inv_mpu_suspend(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + INV_ALL_SENSORS); + mutex_unlock(&mpu->mutex); + dev_dbg(&client->adapter->dev, "%s\n", __func__); +} + +int mpu_dev_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + mutex_lock(&mpu->mutex); + if (!mldl_cfg->inv_mpu_cfg->ignore_system_suspend) { + dev_dbg(&client->adapter->dev, + "%s: suspending on event %d\n", __func__, mesg.event); + (void)inv_mpu_suspend(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + INV_ALL_SENSORS); + } else { + dev_dbg(&client->adapter->dev, + "%s: Already suspended %d\n", __func__, mesg.event); + } + mutex_unlock(&mpu->mutex); + return 0; +} + +int mpu_dev_resume(struct i2c_client *client) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + mutex_lock(&mpu->mutex); + if (mpu->pid && !mldl_cfg->inv_mpu_cfg->ignore_system_suspend) { + (void)inv_mpu_resume(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + mldl_cfg->inv_mpu_cfg->requested_sensors); + dev_dbg(&client->adapter->dev, + "%s for pid %d\n", __func__, mpu->pid); + } + mutex_unlock(&mpu->mutex); + return 0; +} + +/* define which file operations are supported */ +static const struct file_operations mpu_fops = { + .owner = THIS_MODULE, + .read = mpu_read, + .poll = mpu_poll, + .unlocked_ioctl = mpu_dev_ioctl, + .open = mpu_dev_open, + .release = mpu_release, +}; + +int inv_mpu_register_slave(struct module *slave_module, + struct i2c_client *slave_client, + struct ext_slave_platform_data *slave_pdata, + struct ext_slave_descr *(*get_slave_descr)(void)) +{ + struct mpu_private_data *mpu = mpu_private_data; + struct mldl_cfg *mldl_cfg; + struct ext_slave_descr *slave_descr; + struct ext_slave_platform_data **pdata_slave; + char *irq_name = NULL; + int result = 0; + + if (!slave_client || !slave_pdata || !get_slave_descr) + return -EINVAL; + + if (!mpu) { + dev_err(&slave_client->adapter->dev, + "%s: Null mpu_private_data\n", __func__); + return -EINVAL; + } + mldl_cfg = &mpu->mldl_cfg; + pdata_slave = mldl_cfg->pdata_slave; + slave_descr = get_slave_descr(); + + if (!slave_descr) { + dev_err(&slave_client->adapter->dev, + "%s: Null ext_slave_descr\n", __func__); + return -EINVAL; + } + + mutex_lock(&mpu->mutex); + if (mpu->pid) { + mutex_unlock(&mpu->mutex); + return -EBUSY; + } + + if (pdata_slave[slave_descr->type]) { + result = -EBUSY; + goto out_unlock_mutex; + } + + slave_pdata->address = slave_client->addr; + slave_pdata->irq = slave_client->irq; + slave_pdata->adapt_num = i2c_adapter_id(slave_client->adapter); + + dev_info(&slave_client->adapter->dev, + "%s: +%s Type %d: Addr: %2x IRQ: %2d, Adapt: %2d\n", + __func__, + slave_descr->name, + slave_descr->type, + slave_pdata->address, + slave_pdata->irq, + slave_pdata->adapt_num); + + switch (slave_descr->type) { + case EXT_SLAVE_TYPE_ACCEL: + irq_name = "accelirq"; + break; + case EXT_SLAVE_TYPE_COMPASS: + irq_name = "compassirq"; + break; + case EXT_SLAVE_TYPE_PRESSURE: + irq_name = "pressureirq"; + break; + default: + irq_name = "none"; + }; + if (slave_descr->init) { + result = slave_descr->init(slave_client->adapter, + slave_descr, + slave_pdata); + if (result) { + dev_err(&slave_client->adapter->dev, + "%s init failed %d\n", + slave_descr->name, result); + goto out_unlock_mutex; + } + } + + if (slave_descr->type == EXT_SLAVE_TYPE_ACCEL && + slave_descr->id == ACCEL_ID_MPU6050 && + slave_descr->config) { + /* pass a reference to the mldl_cfg data + structure to the mpu6050 accel "class" */ + struct ext_slave_config config; + config.key = MPU_SLAVE_CONFIG_INTERNAL_REFERENCE; + config.len = sizeof(struct mldl_cfg *); + config.apply = true; + config.data = mldl_cfg; + result = slave_descr->config( + slave_client->adapter, slave_descr, + slave_pdata, &config); + if (result) { + LOG_RESULT_LOCATION(result); + goto out_slavedescr_exit; + } + } + pdata_slave[slave_descr->type] = slave_pdata; + mpu->slave_modules[slave_descr->type] = slave_module; + mldl_cfg->slave[slave_descr->type] = slave_descr; + + goto out_unlock_mutex; + +out_slavedescr_exit: + if (slave_descr->exit) + slave_descr->exit(slave_client->adapter, + slave_descr, slave_pdata); +out_unlock_mutex: + mutex_unlock(&mpu->mutex); + + if (!result && irq_name && (slave_pdata->irq > 0)) { + int warn_result; + dev_info(&slave_client->adapter->dev, + "Installing %s irq using %d\n", + irq_name, + slave_pdata->irq); + warn_result = slaveirq_init(slave_client->adapter, + slave_pdata, irq_name); + if (result) + dev_WARN(&slave_client->adapter->dev, + "%s irq assigned error: %d\n", + slave_descr->name, warn_result); + } else { + dev_WARN(&slave_client->adapter->dev, + "%s irq not assigned: %d %d %d\n", + slave_descr->name, + result, (int)irq_name, slave_pdata->irq); + } + + return result; +} +EXPORT_SYMBOL(inv_mpu_register_slave); + +void inv_mpu_unregister_slave(struct i2c_client *slave_client, + struct ext_slave_platform_data *slave_pdata, + struct ext_slave_descr *(*get_slave_descr)(void)) +{ + struct mpu_private_data *mpu = mpu_private_data; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct ext_slave_descr *slave_descr; + int result; + + dev_info(&slave_client->adapter->dev, "%s\n", __func__); + + if (!slave_client || !slave_pdata || !get_slave_descr) + return; + + if (slave_pdata->irq) + slaveirq_exit(slave_pdata); + + slave_descr = get_slave_descr(); + if (!slave_descr) + return; + + mutex_lock(&mpu->mutex); + + if (slave_descr->exit) { + result = slave_descr->exit(slave_client->adapter, + slave_descr, + slave_pdata); + if (result) + dev_err(&slave_client->adapter->dev, + "Accel exit failed %d\n", result); + } + mldl_cfg->slave[slave_descr->type] = NULL; + mldl_cfg->pdata_slave[slave_descr->type] = NULL; + mpu->slave_modules[slave_descr->type] = NULL; + + mutex_unlock(&mpu->mutex); + +} +EXPORT_SYMBOL(inv_mpu_unregister_slave); + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static const struct i2c_device_id mpu_id[] = { + {"mpu3050", 0}, + {"mpu6050", 0}, + {"mpu6050_no_accel", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, mpu_id); + +int mpu_probe(struct i2c_client *client, const struct i2c_device_id *devid) +{ + struct mpu_platform_data *pdata; + struct mpu_private_data *mpu; + struct mldl_cfg *mldl_cfg; + int res = 0; + int ii = 0; + unsigned long irq_flags; + + dev_info(&client->adapter->dev, "%s: %d\n", __func__, ii++); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + res = -ENODEV; + goto out_check_functionality_failed; + } + + mpu = kzalloc(sizeof(struct mpu_private_data), GFP_KERNEL); + if (!mpu) { + res = -ENOMEM; + goto out_alloc_data_failed; + } + mldl_cfg = &mpu->mldl_cfg; + mldl_cfg->mpu_ram = &mpu->mpu_ram; + mldl_cfg->mpu_gyro_cfg = &mpu->mpu_gyro_cfg; + mldl_cfg->mpu_offsets = &mpu->mpu_offsets; + mldl_cfg->mpu_chip_info = &mpu->mpu_chip_info; + mldl_cfg->inv_mpu_cfg = &mpu->inv_mpu_cfg; + mldl_cfg->inv_mpu_state = &mpu->inv_mpu_state; + + mldl_cfg->mpu_ram->length = MPU_MEM_NUM_RAM_BANKS * MPU_MEM_BANK_SIZE; + mldl_cfg->mpu_ram->ram = kzalloc(mldl_cfg->mpu_ram->length, GFP_KERNEL); + if (!mldl_cfg->mpu_ram->ram) { + res = -ENOMEM; + goto out_alloc_ram_failed; + } + mpu_private_data = mpu; + i2c_set_clientdata(client, mpu); + mpu->client = client; + + init_waitqueue_head(&mpu->mpu_event_wait); + mutex_init(&mpu->mutex); + init_completion(&mpu->completion); + + mpu->response_timeout = 60; /* Seconds */ + mpu->timeout.function = mpu_pm_timeout; + mpu->timeout.data = (u_long) mpu; + init_timer(&mpu->timeout); + + mpu->nb.notifier_call = mpu_pm_notifier_callback; + mpu->nb.priority = 0; + res = register_pm_notifier(&mpu->nb); + if (res) { + dev_err(&client->adapter->dev, + "Unable to register pm_notifier %d\n", res); + goto out_register_pm_notifier_failed; + } + + pdata = (struct mpu_platform_data *)client->dev.platform_data; + if (!pdata) { + dev_WARN(&client->adapter->dev, + "Missing platform data for mpu\n"); + } + mldl_cfg->pdata = pdata; + + mldl_cfg->mpu_chip_info->addr = client->addr; + res = inv_mpu_open(&mpu->mldl_cfg, client->adapter, NULL, NULL, NULL); + + if (res) { + dev_err(&client->adapter->dev, + "Unable to open %s %d\n", MPU_NAME, res); + res = -ENODEV; + goto out_whoami_failed; + } + + mpu->dev.minor = MISC_DYNAMIC_MINOR; + mpu->dev.name = "mpu"; + mpu->dev.fops = &mpu_fops; + res = misc_register(&mpu->dev); + if (res < 0) { + dev_err(&client->adapter->dev, + "ERROR: misc_register returned %d\n", res); + goto out_misc_register_failed; + } + + if (client->irq) { + dev_info(&client->adapter->dev, + "Installing irq using %d\n", client->irq); + if (BIT_ACTL_LOW == ((mldl_cfg->pdata->int_config) & BIT_ACTL)) + irq_flags = IRQF_TRIGGER_FALLING; + else + irq_flags = IRQF_TRIGGER_RISING; + res = mpuirq_init(client, mldl_cfg, irq_flags); + + if (res) + goto out_mpuirq_failed; + } else { + dev_WARN(&client->adapter->dev, + "Missing %s IRQ\n", MPU_NAME); + } + if (!strcmp(mpu_id[1].name, devid->name)) { + /* Special case to re-use the inv_mpu_register_slave */ + struct ext_slave_platform_data *slave_pdata; + slave_pdata = kzalloc(sizeof(*slave_pdata), GFP_KERNEL); + if (!slave_pdata) { + res = -ENOMEM; + goto out_slave_pdata_kzalloc_failed; + } + slave_pdata->bus = EXT_SLAVE_BUS_PRIMARY; + for (ii = 0; ii < 9; ii++) + slave_pdata->orientation[ii] = pdata->orientation[ii]; + res = inv_mpu_register_slave( + NULL, client, + slave_pdata, + mpu6050_get_slave_descr); + if (res) { + /* if inv_mpu_register_slave fails there are no pointer + references to the memory allocated to slave_pdata */ + kfree(slave_pdata); + goto out_slave_pdata_kzalloc_failed; + } + } + return res; + +out_slave_pdata_kzalloc_failed: + if (client->irq) + mpuirq_exit(); +out_mpuirq_failed: + misc_deregister(&mpu->dev); +out_misc_register_failed: + inv_mpu_close(&mpu->mldl_cfg, client->adapter, NULL, NULL, NULL); +out_whoami_failed: + unregister_pm_notifier(&mpu->nb); +out_register_pm_notifier_failed: + kfree(mldl_cfg->mpu_ram->ram); + mpu_private_data = NULL; +out_alloc_ram_failed: + kfree(mpu); +out_alloc_data_failed: +out_check_functionality_failed: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, res); + return res; + +} + +static int mpu_remove(struct i2c_client *client) +{ + struct mpu_private_data *mpu = i2c_get_clientdata(client); + struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_close(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE]); + + if (mldl_cfg->slave[EXT_SLAVE_TYPE_ACCEL] && + (mldl_cfg->slave[EXT_SLAVE_TYPE_ACCEL]->id == + ACCEL_ID_MPU6050)) { + struct ext_slave_platform_data *slave_pdata = + mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_ACCEL]; + inv_mpu_unregister_slave( + client, + mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_ACCEL], + mpu6050_get_slave_descr); + kfree(slave_pdata); + } + + if (client->irq) + mpuirq_exit(); + + misc_deregister(&mpu->dev); + + unregister_pm_notifier(&mpu->nb); + + kfree(mpu->mldl_cfg.mpu_ram->ram); + kfree(mpu); + + return 0; +} + +static struct i2c_driver mpu_driver = { + .class = I2C_CLASS_HWMON, + .probe = mpu_probe, + .remove = mpu_remove, + .id_table = mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = MPU_NAME, + }, + .address_list = normal_i2c, + .shutdown = mpu_shutdown, /* optional */ + .suspend = mpu_dev_suspend, /* optional */ + .resume = mpu_dev_resume, /* optional */ + +}; + +static int __init mpu_init(void) +{ + int res = i2c_add_driver(&mpu_driver); + pr_info("%s: Probe name %s\n", __func__, MPU_NAME); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit mpu_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&mpu_driver); +} + +module_init(mpu_init); +module_exit(mpu_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("User space character device interface for MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS(MPU_NAME); diff --git a/drivers/misc/inv_mpu/mpu6050b1.h b/drivers/misc/inv_mpu/mpu6050b1.h new file mode 100644 index 000000000000..686f6e5d86a5 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu6050b1.h @@ -0,0 +1,435 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, All Rights Reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + $ + */ + +/** + * @defgroup + * @brief + * + * @{ + * @file mpu6050.h + * @brief + */ + +#ifndef __MPU_H_ +#error Do not include this file directly. Include mpu.h instead. +#endif + +#ifndef __MPU6050B1_H_ +#define __MPU6050B1_H_ + + +#define MPU_NAME "mpu6050B1" +#define DEFAULT_MPU_SLAVEADDR 0x68 + +/*==== MPU6050B1 REGISTER SET ====*/ +enum { + MPUREG_XG_OFFS_TC = 0, /* 0x00, 0 */ + MPUREG_YG_OFFS_TC, /* 0x01, 1 */ + MPUREG_ZG_OFFS_TC, /* 0x02, 2 */ + MPUREG_X_FINE_GAIN, /* 0x03, 3 */ + MPUREG_Y_FINE_GAIN, /* 0x04, 4 */ + MPUREG_Z_FINE_GAIN, /* 0x05, 5 */ + MPUREG_XA_OFFS_H, /* 0x06, 6 */ + MPUREG_XA_OFFS_L, /* 0x07, 7 */ + MPUREG_YA_OFFS_H, /* 0x08, 8 */ + MPUREG_YA_OFFS_L, /* 0x09, 9 */ + MPUREG_ZA_OFFS_H, /* 0x0a, 10 */ + MPUREG_ZA_OFFS_L, /* 0x0B, 11 */ + MPUREG_PRODUCT_ID, /* 0x0c, 12 */ + MPUREG_0D_RSVD, /* 0x0d, 13 */ + MPUREG_0E_RSVD, /* 0x0e, 14 */ + MPUREG_0F_RSVD, /* 0x0f, 15 */ + MPUREG_10_RSVD, /* 0x00, 16 */ + MPUREG_11_RSVD, /* 0x11, 17 */ + MPUREG_12_RSVD, /* 0x12, 18 */ + MPUREG_XG_OFFS_USRH, /* 0x13, 19 */ + MPUREG_XG_OFFS_USRL, /* 0x14, 20 */ + MPUREG_YG_OFFS_USRH, /* 0x15, 21 */ + MPUREG_YG_OFFS_USRL, /* 0x16, 22 */ + MPUREG_ZG_OFFS_USRH, /* 0x17, 23 */ + MPUREG_ZG_OFFS_USRL, /* 0x18, 24 */ + MPUREG_SMPLRT_DIV, /* 0x19, 25 */ + MPUREG_CONFIG, /* 0x1A, 26 */ + MPUREG_GYRO_CONFIG, /* 0x1b, 27 */ + MPUREG_ACCEL_CONFIG, /* 0x1c, 28 */ + MPUREG_ACCEL_FF_THR, /* 0x1d, 29 */ + MPUREG_ACCEL_FF_DUR, /* 0x1e, 30 */ + MPUREG_ACCEL_MOT_THR, /* 0x1f, 31 */ + MPUREG_ACCEL_MOT_DUR, /* 0x20, 32 */ + MPUREG_ACCEL_ZRMOT_THR, /* 0x21, 33 */ + MPUREG_ACCEL_ZRMOT_DUR, /* 0x22, 34 */ + MPUREG_FIFO_EN, /* 0x23, 35 */ + MPUREG_I2C_MST_CTRL, /* 0x24, 36 */ + MPUREG_I2C_SLV0_ADDR, /* 0x25, 37 */ + MPUREG_I2C_SLV0_REG, /* 0x26, 38 */ + MPUREG_I2C_SLV0_CTRL, /* 0x27, 39 */ + MPUREG_I2C_SLV1_ADDR, /* 0x28, 40 */ + MPUREG_I2C_SLV1_REG, /* 0x29, 41 */ + MPUREG_I2C_SLV1_CTRL, /* 0x2a, 42 */ + MPUREG_I2C_SLV2_ADDR, /* 0x2B, 43 */ + MPUREG_I2C_SLV2_REG, /* 0x2c, 44 */ + MPUREG_I2C_SLV2_CTRL, /* 0x2d, 45 */ + MPUREG_I2C_SLV3_ADDR, /* 0x2E, 46 */ + MPUREG_I2C_SLV3_REG, /* 0x2f, 47 */ + MPUREG_I2C_SLV3_CTRL, /* 0x30, 48 */ + MPUREG_I2C_SLV4_ADDR, /* 0x31, 49 */ + MPUREG_I2C_SLV4_REG, /* 0x32, 50 */ + MPUREG_I2C_SLV4_DO, /* 0x33, 51 */ + MPUREG_I2C_SLV4_CTRL, /* 0x34, 52 */ + MPUREG_I2C_SLV4_DI, /* 0x35, 53 */ + MPUREG_I2C_MST_STATUS, /* 0x36, 54 */ + MPUREG_INT_PIN_CFG, /* 0x37, 55 */ + MPUREG_INT_ENABLE, /* 0x38, 56 */ + MPUREG_DMP_INT_STATUS, /* 0x39, 57 */ + MPUREG_INT_STATUS, /* 0x3A, 58 */ + MPUREG_ACCEL_XOUT_H, /* 0x3B, 59 */ + MPUREG_ACCEL_XOUT_L, /* 0x3c, 60 */ + MPUREG_ACCEL_YOUT_H, /* 0x3d, 61 */ + MPUREG_ACCEL_YOUT_L, /* 0x3e, 62 */ + MPUREG_ACCEL_ZOUT_H, /* 0x3f, 63 */ + MPUREG_ACCEL_ZOUT_L, /* 0x40, 64 */ + MPUREG_TEMP_OUT_H, /* 0x41, 65 */ + MPUREG_TEMP_OUT_L, /* 0x42, 66 */ + MPUREG_GYRO_XOUT_H, /* 0x43, 67 */ + MPUREG_GYRO_XOUT_L, /* 0x44, 68 */ + MPUREG_GYRO_YOUT_H, /* 0x45, 69 */ + MPUREG_GYRO_YOUT_L, /* 0x46, 70 */ + MPUREG_GYRO_ZOUT_H, /* 0x47, 71 */ + MPUREG_GYRO_ZOUT_L, /* 0x48, 72 */ + MPUREG_EXT_SLV_SENS_DATA_00, /* 0x49, 73 */ + MPUREG_EXT_SLV_SENS_DATA_01, /* 0x4a, 74 */ + MPUREG_EXT_SLV_SENS_DATA_02, /* 0x4b, 75 */ + MPUREG_EXT_SLV_SENS_DATA_03, /* 0x4c, 76 */ + MPUREG_EXT_SLV_SENS_DATA_04, /* 0x4d, 77 */ + MPUREG_EXT_SLV_SENS_DATA_05, /* 0x4e, 78 */ + MPUREG_EXT_SLV_SENS_DATA_06, /* 0x4F, 79 */ + MPUREG_EXT_SLV_SENS_DATA_07, /* 0x50, 80 */ + MPUREG_EXT_SLV_SENS_DATA_08, /* 0x51, 81 */ + MPUREG_EXT_SLV_SENS_DATA_09, /* 0x52, 82 */ + MPUREG_EXT_SLV_SENS_DATA_10, /* 0x53, 83 */ + MPUREG_EXT_SLV_SENS_DATA_11, /* 0x54, 84 */ + MPUREG_EXT_SLV_SENS_DATA_12, /* 0x55, 85 */ + MPUREG_EXT_SLV_SENS_DATA_13, /* 0x56, 86 */ + MPUREG_EXT_SLV_SENS_DATA_14, /* 0x57, 87 */ + MPUREG_EXT_SLV_SENS_DATA_15, /* 0x58, 88 */ + MPUREG_EXT_SLV_SENS_DATA_16, /* 0x59, 89 */ + MPUREG_EXT_SLV_SENS_DATA_17, /* 0x5a, 90 */ + MPUREG_EXT_SLV_SENS_DATA_18, /* 0x5B, 91 */ + MPUREG_EXT_SLV_SENS_DATA_19, /* 0x5c, 92 */ + MPUREG_EXT_SLV_SENS_DATA_20, /* 0x5d, 93 */ + MPUREG_EXT_SLV_SENS_DATA_21, /* 0x5e, 94 */ + MPUREG_EXT_SLV_SENS_DATA_22, /* 0x5f, 95 */ + MPUREG_EXT_SLV_SENS_DATA_23, /* 0x60, 96 */ + MPUREG_ACCEL_INTEL_STATUS, /* 0x61, 97 */ + MPUREG_62_RSVD, /* 0x62, 98 */ + MPUREG_I2C_SLV0_DO, /* 0x63, 99 */ + MPUREG_I2C_SLV1_DO, /* 0x64, 100 */ + MPUREG_I2C_SLV2_DO, /* 0x65, 101 */ + MPUREG_I2C_SLV3_DO, /* 0x66, 102 */ + MPUREG_I2C_MST_DELAY_CTRL, /* 0x67, 103 */ + MPUREG_SIGNAL_PATH_RESET, /* 0x68, 104 */ + MPUREG_ACCEL_INTEL_CTRL, /* 0x69, 105 */ + MPUREG_USER_CTRL, /* 0x6A, 106 */ + MPUREG_PWR_MGMT_1, /* 0x6B, 107 */ + MPUREG_PWR_MGMT_2, /* 0x6C, 108 */ + MPUREG_BANK_SEL, /* 0x6D, 109 */ + MPUREG_MEM_START_ADDR, /* 0x6E, 100 */ + MPUREG_MEM_R_W, /* 0x6F, 111 */ + MPUREG_DMP_CFG_1, /* 0x70, 112 */ + MPUREG_DMP_CFG_2, /* 0x71, 113 */ + MPUREG_FIFO_COUNTH, /* 0x72, 114 */ + MPUREG_FIFO_COUNTL, /* 0x73, 115 */ + MPUREG_FIFO_R_W, /* 0x74, 116 */ + MPUREG_WHOAMI, /* 0x75, 117 */ + + NUM_OF_MPU_REGISTERS /* = 0x76, 118 */ +}; + +/*==== MPU6050B1 MEMORY ====*/ +enum MPU_MEMORY_BANKS { + MEM_RAM_BANK_0 = 0, + MEM_RAM_BANK_1, + MEM_RAM_BANK_2, + MEM_RAM_BANK_3, + MEM_RAM_BANK_4, + MEM_RAM_BANK_5, + MEM_RAM_BANK_6, + MEM_RAM_BANK_7, + MEM_RAM_BANK_8, + MEM_RAM_BANK_9, + MEM_RAM_BANK_10, + MEM_RAM_BANK_11, + MPU_MEM_NUM_RAM_BANKS, + MPU_MEM_OTP_BANK_0 = 16 +}; + + +/*==== MPU6050B1 parameters ====*/ + +#define NUM_REGS (NUM_OF_MPU_REGISTERS) +#define START_SENS_REGS (0x3B) +#define NUM_SENS_REGS (0x60 - START_SENS_REGS + 1) + +/*---- MPU Memory ----*/ +#define NUM_BANKS (MPU_MEM_NUM_RAM_BANKS) +#define BANK_SIZE (256) +#define MEM_SIZE (NUM_BANKS * BANK_SIZE) +#define MPU_MEM_BANK_SIZE (BANK_SIZE) /*alternative name */ + +#define FIFO_HW_SIZE (1024) + +#define NUM_EXT_SLAVES (4) + + +/*==== BITS FOR MPU6050B1 ====*/ +/*---- MPU6050B1 'XG_OFFS_TC' register (0, 1, 2) ----*/ +#define BIT_PU_SLEEP_MODE 0x80 +#define BITS_XG_OFFS_TC 0x7E +#define BIT_OTP_BNK_VLD 0x01 + +#define BIT_I2C_MST_VDDIO 0x80 +#define BITS_YG_OFFS_TC 0x7E +#define BITS_ZG_OFFS_TC 0x7E +/*---- MPU6050B1 'FIFO_EN' register (23) ----*/ +#define BIT_TEMP_OUT 0x80 +#define BIT_GYRO_XOUT 0x40 +#define BIT_GYRO_YOUT 0x20 +#define BIT_GYRO_ZOUT 0x10 +#define BIT_ACCEL 0x08 +#define BIT_SLV_2 0x04 +#define BIT_SLV_1 0x02 +#define BIT_SLV_0 0x01 +/*---- MPU6050B1 'CONFIG' register (1A) ----*/ +/*NONE 0xC0 */ +#define BITS_EXT_SYNC_SET 0x38 +#define BITS_DLPF_CFG 0x07 +/*---- MPU6050B1 'GYRO_CONFIG' register (1B) ----*/ +/* voluntarily modified label from BITS_FS_SEL to + * BITS_GYRO_FS_SEL to avoid confusion with MPU + */ +#define BITS_GYRO_FS_SEL 0x18 +/*NONE 0x07 */ +/*---- MPU6050B1 'ACCEL_CONFIG' register (1C) ----*/ +#define BITS_ACCEL_FS_SEL 0x18 +#define BITS_ACCEL_HPF 0x07 +/*---- MPU6050B1 'I2C_MST_CTRL' register (24) ----*/ +#define BIT_MULT_MST_EN 0x80 +#define BIT_WAIT_FOR_ES 0x40 +#define BIT_SLV_3_FIFO_EN 0x20 +#define BIT_I2C_MST_PSR 0x10 +#define BITS_I2C_MST_CLK 0x0F +/*---- MPU6050B1 'I2C_SLV?_ADDR' register (27,2A,2D,30) ----*/ +#define BIT_I2C_READ 0x80 +#define BIT_I2C_WRITE 0x00 +#define BITS_I2C_ADDR 0x7F +/*---- MPU6050B1 'I2C_SLV?_CTRL' register (27,2A,2D,30) ----*/ +#define BIT_SLV_ENABLE 0x80 +#define BIT_SLV_BYTE_SW 0x40 +#define BIT_SLV_REG_DIS 0x20 +#define BIT_SLV_GRP 0x10 +#define BITS_SLV_LENG 0x0F +/*---- MPU6050B1 'I2C_SLV4_ADDR' register (31) ----*/ +#define BIT_I2C_SLV4_RNW 0x80 +/*---- MPU6050B1 'I2C_SLV4_CTRL' register (34) ----*/ +#define BIT_I2C_SLV4_EN 0x80 +#define BIT_SLV4_DONE_INT_EN 0x40 +#define BIT_SLV4_REG_DIS 0x20 +#define MASK_I2C_MST_DLY 0x1F +/*---- MPU6050B1 'I2C_MST_STATUS' register (36) ----*/ +#define BIT_PASS_THROUGH 0x80 +#define BIT_I2C_SLV4_DONE 0x40 +#define BIT_I2C_LOST_ARB 0x20 +#define BIT_I2C_SLV4_NACK 0x10 +#define BIT_I2C_SLV3_NACK 0x08 +#define BIT_I2C_SLV2_NACK 0x04 +#define BIT_I2C_SLV1_NACK 0x02 +#define BIT_I2C_SLV0_NACK 0x01 +/*---- MPU6050B1 'INT_PIN_CFG' register (37) ----*/ +#define BIT_ACTL 0x80 +#define BIT_ACTL_LOW 0x80 +#define BIT_ACTL_HIGH 0x00 +#define BIT_OPEN 0x40 +#define BIT_LATCH_INT_EN 0x20 +#define BIT_INT_ANYRD_2CLEAR 0x10 +#define BIT_ACTL_FSYNC 0x08 +#define BIT_FSYNC_INT_EN 0x04 +#define BIT_BYPASS_EN 0x02 +#define BIT_CLKOUT_EN 0x01 +/*---- MPU6050B1 'INT_ENABLE' register (38) ----*/ +#define BIT_FF_EN 0x80 +#define BIT_MOT_EN 0x40 +#define BIT_ZMOT_EN 0x20 +#define BIT_FIFO_OVERFLOW_EN 0x10 +#define BIT_I2C_MST_INT_EN 0x08 +#define BIT_PLL_RDY_EN 0x04 +#define BIT_DMP_INT_EN 0x02 +#define BIT_RAW_RDY_EN 0x01 +/*---- MPU6050B1 'DMP_INT_STATUS' register (39) ----*/ +/*NONE 0x80 */ +/*NONE 0x40 */ +#define BIT_DMP_INT_5 0x20 +#define BIT_DMP_INT_4 0x10 +#define BIT_DMP_INT_3 0x08 +#define BIT_DMP_INT_2 0x04 +#define BIT_DMP_INT_1 0x02 +#define BIT_DMP_INT_0 0x01 +/*---- MPU6050B1 'INT_STATUS' register (3A) ----*/ +#define BIT_FF_INT 0x80 +#define BIT_MOT_INT 0x40 +#define BIT_ZMOT_INT 0x20 +#define BIT_FIFO_OVERFLOW_INT 0x10 +#define BIT_I2C_MST_INT 0x08 +#define BIT_PLL_RDY_INT 0x04 +#define BIT_DMP_INT 0x02 +#define BIT_RAW_DATA_RDY_INT 0x01 +/*---- MPU6050B1 'MPUREG_I2C_MST_DELAY_CTRL' register (0x67) ----*/ +#define BIT_DELAY_ES_SHADOW 0x80 +#define BIT_SLV4_DLY_EN 0x10 +#define BIT_SLV3_DLY_EN 0x08 +#define BIT_SLV2_DLY_EN 0x04 +#define BIT_SLV1_DLY_EN 0x02 +#define BIT_SLV0_DLY_EN 0x01 +/*---- MPU6050B1 'BANK_SEL' register (6D) ----*/ +#define BIT_PRFTCH_EN 0x40 +#define BIT_CFG_USER_BANK 0x20 +#define BITS_MEM_SEL 0x1f +/*---- MPU6050B1 'USER_CTRL' register (6A) ----*/ +#define BIT_DMP_EN 0x80 +#define BIT_FIFO_EN 0x40 +#define BIT_I2C_MST_EN 0x20 +#define BIT_I2C_IF_DIS 0x10 +#define BIT_DMP_RST 0x08 +#define BIT_FIFO_RST 0x04 +#define BIT_I2C_MST_RST 0x02 +#define BIT_SIG_COND_RST 0x01 +/*---- MPU6050B1 'PWR_MGMT_1' register (6B) ----*/ +#define BIT_H_RESET 0x80 +#define BIT_SLEEP 0x40 +#define BIT_CYCLE 0x20 +#define BIT_PD_PTAT 0x08 +#define BITS_CLKSEL 0x07 +/*---- MPU6050B1 'PWR_MGMT_2' register (6C) ----*/ +#define BITS_LPA_WAKE_CTRL 0xC0 +#define BITS_LPA_WAKE_1HZ 0x00 +#define BITS_LPA_WAKE_2HZ 0x40 +#define BITS_LPA_WAKE_10HZ 0x80 +#define BITS_LPA_WAKE_40HZ 0xC0 +#define BIT_STBY_XA 0x20 +#define BIT_STBY_YA 0x10 +#define BIT_STBY_ZA 0x08 +#define BIT_STBY_XG 0x04 +#define BIT_STBY_YG 0x02 +#define BIT_STBY_ZG 0x01 + +#define ACCEL_MOT_THR_LSB (32) /* mg */ +#define ACCEL_MOT_DUR_LSB (1) +#define ACCEL_ZRMOT_THR_LSB_CONVERSION(mg) ((mg * 1000) / 255) +#define ACCEL_ZRMOT_DUR_LSB (64) + +/*----------------------------------------------------------------------------*/ +/*---- Alternative names to take care of conflicts with current mpu3050.h ----*/ +/*----------------------------------------------------------------------------*/ + +/*-- registers --*/ +#define MPUREG_DLPF_FS_SYNC MPUREG_CONFIG /* 0x1A */ + +#define MPUREG_PWR_MGM MPUREG_PWR_MGMT_1 /* 0x6B */ +#define MPUREG_FIFO_EN1 MPUREG_FIFO_EN /* 0x23 */ +#define MPUREG_INT_CFG MPUREG_INT_ENABLE /* 0x38 */ +#define MPUREG_X_OFFS_USRH MPUREG_XG_OFFS_USRH /* 0x13 */ +#define MPUREG_WHO_AM_I MPUREG_WHOAMI /* 0x75 */ +#define MPUREG_23_RSVD MPUREG_EXT_SLV_SENS_DATA_00 /* 0x49 */ + +/*-- bits --*/ +/* 'USER_CTRL' register */ +#define BIT_AUX_IF_EN BIT_I2C_MST_EN +#define BIT_AUX_RD_LENG BIT_I2C_MST_EN +#define BIT_IME_IF_RST BIT_I2C_MST_RST +#define BIT_GYRO_RST BIT_SIG_COND_RST +/* 'INT_ENABLE' register */ +#define BIT_RAW_RDY BIT_RAW_DATA_RDY_INT +#define BIT_MPU_RDY_EN BIT_PLL_RDY_EN +/* 'INT_STATUS' register */ +#define BIT_INT_STATUS_FIFO_OVERLOW BIT_FIFO_OVERFLOW_INT + +/*---- MPU6050 Silicon Revisions ----*/ +#define MPU_SILICON_REV_A2 1 /* MPU6050A2 Device */ +#define MPU_SILICON_REV_B1 2 /* MPU6050B1 Device */ + +/*---- MPU6050 notable product revisions ----*/ +#define MPU_PRODUCT_KEY_B1_E1_5 105 +#define MPU_PRODUCT_KEY_B2_F1 431 + +/*---- structure containing control variables used by MLDL ----*/ +/*---- MPU clock source settings ----*/ +/*---- MPU filter selections ----*/ +enum mpu_filter { + MPU_FILTER_256HZ_NOLPF2 = 0, + MPU_FILTER_188HZ, + MPU_FILTER_98HZ, + MPU_FILTER_42HZ, + MPU_FILTER_20HZ, + MPU_FILTER_10HZ, + MPU_FILTER_5HZ, + MPU_FILTER_2100HZ_NOLPF, + NUM_MPU_FILTER +}; + +enum mpu_fullscale { + MPU_FS_250DPS = 0, + MPU_FS_500DPS, + MPU_FS_1000DPS, + MPU_FS_2000DPS, + NUM_MPU_FS +}; + +enum mpu_clock_sel { + MPU_CLK_SEL_INTERNAL = 0, + MPU_CLK_SEL_PLLGYROX, + MPU_CLK_SEL_PLLGYROY, + MPU_CLK_SEL_PLLGYROZ, + MPU_CLK_SEL_PLLEXT32K, + MPU_CLK_SEL_PLLEXT19M, + MPU_CLK_SEL_RESERVED, + MPU_CLK_SEL_STOP, + NUM_CLK_SEL +}; + +enum mpu_ext_sync { + MPU_EXT_SYNC_NONE = 0, + MPU_EXT_SYNC_TEMP, + MPU_EXT_SYNC_GYROX, + MPU_EXT_SYNC_GYROY, + MPU_EXT_SYNC_GYROZ, + MPU_EXT_SYNC_ACCELX, + MPU_EXT_SYNC_ACCELY, + MPU_EXT_SYNC_ACCELZ, + NUM_MPU_EXT_SYNC +}; + +#define MPUREG_CONFIG_VALUE(ext_sync, lpf) \ + ((ext_sync << 3) | lpf) + +#define MPUREG_GYRO_CONFIG_VALUE(x_st, y_st, z_st, full_scale) \ + ((x_st ? 0x80 : 0) | \ + (y_st ? 0x70 : 0) | \ + (z_st ? 0x60 : 0) | \ + (full_scale << 3)) + +#endif /* __MPU6050_H_ */ diff --git a/drivers/misc/inv_mpu/mpuirq.c b/drivers/misc/inv_mpu/mpuirq.c index dfd87cdabc6e..d8b721e43468 100644 --- a/drivers/misc/inv_mpu/mpuirq.c +++ b/drivers/misc/inv_mpu/mpuirq.c @@ -193,7 +193,8 @@ static struct miscdevice mpuirq_device = { .fops = &mpuirq_fops, }; -int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg) +int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg, + unsigned long irq_flags) { int res; @@ -211,15 +212,9 @@ int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg) mpuirq_dev_data.dev = &mpuirq_device; if (mpuirq_dev_data.irq) { - unsigned long flags; - if (BIT_ACTL_LOW == ((mldl_cfg->pdata->int_config) & BIT_ACTL)) - flags = IRQF_TRIGGER_FALLING; - else - flags = IRQF_TRIGGER_RISING; - - flags |= IRQF_SHARED; + irq_flags |= IRQF_SHARED; res = - request_irq(mpuirq_dev_data.irq, mpuirq_handler, flags, + request_irq(mpuirq_dev_data.irq, mpuirq_handler, irq_flags, interface, &mpuirq_dev_data.irq); if (res) { dev_err(&mpu_client->adapter->dev, @@ -241,6 +236,7 @@ int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg) return res; } +EXPORT_SYMBOL(mpuirq_init); void mpuirq_exit(void) { @@ -252,6 +248,7 @@ void mpuirq_exit(void) return; } +EXPORT_SYMBOL(mpuirq_exit); module_param(interface, charp, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(interface, "The Interface name"); diff --git a/drivers/misc/inv_mpu/mpuirq.h b/drivers/misc/inv_mpu/mpuirq.h index 33480711f79d..073867d830c5 100644 --- a/drivers/misc/inv_mpu/mpuirq.h +++ b/drivers/misc/inv_mpu/mpuirq.h @@ -31,6 +31,7 @@ #define MPUIRQ_SET_FREQUENCY_DIVIDER _IOW(MPU_IOCTL, 0x43, unsigned long) void mpuirq_exit(void); -int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg); +int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg, + unsigned long irq_flags); #endif diff --git a/drivers/misc/inv_mpu/slaveirq.c b/drivers/misc/inv_mpu/slaveirq.c index 95e690ee60cb..22cfa4e458e8 100644 --- a/drivers/misc/inv_mpu/slaveirq.c +++ b/drivers/misc/inv_mpu/slaveirq.c @@ -249,6 +249,7 @@ out_request_irq: return res; } +EXPORT_SYMBOL(slaveirq_init); void slaveirq_exit(struct ext_slave_platform_data *pdata) { @@ -264,3 +265,4 @@ void slaveirq_exit(struct ext_slave_platform_data *pdata) kfree(pdata->irq_data); pdata->irq_data = NULL; } +EXPORT_SYMBOL(slaveirq_exit); diff --git a/include/linux/mpu.h b/include/linux/mpu.h index fd66ba0db875..1977b38b8c15 100644 --- a/include/linux/mpu.h +++ b/include/linux/mpu.h @@ -158,6 +158,7 @@ enum ext_slave_id { ACCEL_ID_MPU6050, COMPASS_ID_AK8975, + COMPASS_ID_AK8963, COMPASS_ID_AK8972, COMPASS_ID_AMI30X, COMPASS_ID_AMI306, |