diff options
author | Robert Collins <rcollins@nvidia.com> | 2010-11-23 13:54:32 -0800 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:46:33 -0800 |
commit | df3ec81e193b55c04362ab2fbbf4611a4f41947c (patch) | |
tree | 7101dae0c47e60860c5f1367fa75f9a8ec93e530 /drivers/misc/mpu3050 | |
parent | 2816bee2242d6fa34586dc8043d239c2d1fd8bd6 (diff) |
[ARM/tegra] Integrate Accelerometer source code files.
Inegrate MPL libraries and the following sensors:
Accelerometers:
kxtf9
Compi:
ak8975
Original-Change-Id: I450b5b7ff018249a19bb23b78e722e9a355b7bd8
Reviewed-on: http://git-master/r/11803
Tested-by: Robert R Collins <rcollins@nvidia.com>
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Rebase-Id: R76e2da39a90190d176552fdb11aab2009964023f
Diffstat (limited to 'drivers/misc/mpu3050')
-rwxr-xr-x | drivers/misc/mpu3050/Kconfig | 54 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/Makefile | 37 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/README | 138 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/accel/kxtf9.c | 144 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/compass/ak8975.c | 146 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/log.h | 287 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mldl_cfg.c | 872 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mldl_cfg.h | 98 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mlos-kernel.c | 92 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mlos.h | 78 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mlsl-kernel.c | 171 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mlsl.h | 81 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mltypes.h | 215 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mpu-dev.c | 835 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mpu-i2c.c | 195 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mpu-i2c.h | 60 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mpuirq.c | 315 | ||||
-rwxr-xr-x | drivers/misc/mpu3050/mpuirq.h | 50 |
18 files changed, 3868 insertions, 0 deletions
diff --git a/drivers/misc/mpu3050/Kconfig b/drivers/misc/mpu3050/Kconfig new file mode 100755 index 000000000000..99ea1c6aa8e2 --- /dev/null +++ b/drivers/misc/mpu3050/Kconfig @@ -0,0 +1,54 @@ +
+menu "Motion Sensors Support"
+
+config SENSORS_MPU3050
+ tristate "MPU3050 Gyroscope Driver"
+ depends on I2C
+ help
+ If you say yes here you get support for the MPU3050 Gyroscope driver
+ This driver can also be built as a module. If so, the module
+ will be called mpu3050.
+
+config SENSORS_MPU3050_DEBUG
+ bool "MPU3050 debug"
+ depends on SENSORS_MPU3050
+ help
+ If you say yes here you get extra debug messages from the MPU3050
+ and other slave sensors.
+
+choice
+ prompt "Accelerometer Type"
+ depends on SENSORS_MPU3050
+ default SENSORS_KXTF9_MPU
+
+config SENSORS_ACCELEROMETER_NONE
+ bool "NONE"
+ help
+ This disables accelerometer support for the MPU3050
+
+config SENSORS_KXTF9_MPU
+ bool "Kionix KXTF9"
+ help
+ This enables support for the Kionix KXFT9 accelerometer
+
+endchoice
+
+choice
+ prompt "Compass Type"
+ depends on SENSORS_MPU3050
+ default SENSORS_AK8975_MPU
+
+config SENSORS_COMPASS_NONE
+ bool "NONE"
+ help
+ This disables compass support for the MPU3050
+
+config SENSORS_AK8975_MPU
+ bool "AKM ak8975"
+ help
+ This enables support for the AKM ak8975 compass
+
+endchoice
+
+endmenu
+
diff --git a/drivers/misc/mpu3050/Makefile b/drivers/misc/mpu3050/Makefile new file mode 100755 index 000000000000..99955ad487fc --- /dev/null +++ b/drivers/misc/mpu3050/Makefile @@ -0,0 +1,37 @@ +
+# Kernel makefile for motions sensors
+#
+#
+
+# MPU
+obj-$(CONFIG_SENSORS_MPU3050) += mpu3050.o
+mpu3050-objs += mpuirq.o \
+ mpu-dev.o \
+ mpu-i2c.o \
+ mlsl-kernel.o \
+ mlos-kernel.o \
+ $(MLLITE_DIR)mldl_cfg.o
+
+#
+# Accel options
+#
+ifdef CONFIG_SENSORS_KXTF9_MPU
+mpu3050-objs += $(MLLITE_DIR)accel/kxtf9.o
+endif
+
+#
+# Compass options
+#
+ifdef CONFIG_SENSORS_AK8975_MPU
+mpu3050-objs += $(MLLITE_DIR)compass/ak8975.o
+endif
+
+EXTRA_CFLAGS += -I$(M)/$(MLLITE_DIR) \
+ -I$(M)/../../include \
+ -Idrivers/misc/mpu3050 \
+ -Iinclude/linux
+
+ifdef CONFIG_SENSORS_MPU3050_DEBUG
+EXTRA_CFLAGS += -DDEBUG
+endif
+
diff --git a/drivers/misc/mpu3050/README b/drivers/misc/mpu3050/README new file mode 100755 index 000000000000..8ff0c3009eca --- /dev/null +++ b/drivers/misc/mpu3050/README @@ -0,0 +1,138 @@ +Kernel driver mpu3050 +===================== + +Supported chips: + * Invensense IMU3050 + Prefix: 'mpu3050' + Datasheet: + PS-MPU-3000A-00.2.4b.pdf + +Author: Invensense <http://invensense.com> + +Description +----------- +The mpu3050 is a motion processor that controls the mpu3050 gyroscope, a slave +accelerometer and compass. This document describes how to install the driver +into a linux kernel and a small note about how to set up the file permissions +in an android file system. + +Sysfs entries +------------- +/dev/mpu +/dev/mpuirq + +General Remarks +--------------- + +Valid addresses for the MPU3050 is 0x68. +Accelerometer must be on the secondary I2C bus. + +Programming the chip using /dev/mpu +---------------------------------- +Programming of the MPU3050 is done by first opening the /dev/mpu file and +then performing a series of IOCTLS on the handle returned. The IOCTL codes can +be found in mpu3050.h. Typically this is done by the mllite library in user +space. + +Adding to a Kernel +================== + +The mpu3050 driver is designed to be inserted in the drivers/misc part of the +kernel. Copy the mpu3050 directory and the mpu3050 include file to: + + <kernel root dir>/drivers/misc/mpu3050 + <kernel root dir>/include/linux/mpu3050.h + +respectively. + +After this is done the drivers/misc/Kconfig must be edited to add the line: + + source "drivers/misc/mpu3050/Kconfig" + +Similarly drivers/misc/Makefile must be edited to add the line: + + obj-y += mpu3050/ + +Configuration can then be done as normal. + +Board and Platform Data +----------------------- + +In order for the driver to work, board and platform data specific to the device +needs to be added to the board file. A mpu3050_platform_data structure must +be created and populatd and set in the i2c_board_info_structure. For details of +each structure member see mpu3050.h. All values below are modified for the ventana +platform. You should add these lines into the +kernel/arch/arm/mach-tegra/board-generic.c file. + +#include <linux/mpu3050.h> + +static struct mpu3050_platform_data mpu3050_data = { + .int_config = 0x10, + .orientation = { 0, -1, 0, + -1, 0, 0, + 0, 0, -1 }, //Orientation matrix for MPU on ventana + .level_shifter = 0, + .accel = { + .get_slave_descr = kxtf9_get_slave_descr, + .adapt_num = 0, + .bus = EXT_SLAVE_BUS_SECONDARY, + .address = 0x0F, + .orientation = { 0, -1, 0, + -1, 0, 0, + 0, 0, -1 }, //Orientation matrix for Kionix on ventana + }, + + + .compass = { + .get_slave_descr = ak8975_get_slave_descr, + .adapt_num = 3, //bus number 3 on ventana + .bus = EXT_SLAVE_BUS_PRIMARY, + .address = 0x0C, + .orientation = { 1, 0, 0, + 0, -1, 0, + 0, 0, -1 }, //Orientation matrix for AKM on ventana + }, + +}; + +static struct i2c_board_info __initdata mpu3050_i2c0_boardinfo[] = { + { + I2C_BOARD_INFO("mpu3050", 0x68), + /*.irq = 299,*/ + .platform_data = &mpu3050_data, + }, +}; + +Note: If you are unsure where to add this code, look for other instances of "i2c_board_info" +and copy paste this code after one of them. + +After this is done, we must register the board upon initialization. This is done in the +i2c_device_setup function in the board-generic.c file. Look for this function and add these +lines of code: + +if (ARRAY_SIZE(mpu3050_i2c0_boardinfo)) + i2c_register_board_info(0, mpu3050_i2c0_boardinfo, + ARRAY_SIZE(mpu3050_i2c0_boardinfo)); + +Before you can build the kernel, you will need to remove the existing NVidia AK8975 driver for the compass. +In the board-generic.c file, rename the instance of CONFIG_SENSORS_AK8975 to NVIDIA_CONFIG_SENSORS_AK8975. +In kernel/drivers/hwmon/Makefile, rename that instance of CONFIG_SENSORS_AK8975 to NVIDIA_CONFIG_SENSORS_AK8975 as well. +This will prevent NVidia's driver for the compass from building. + +Now you can build the new kernel. First, we must configure the kernel. Take the tegra_ventana_android_defconfig +file from this package and replace the existing one in the kernel/arch/arm/configs directory. In the root kernel +directory, you can then run: + +export CCOMPILER=/<path to android environment>/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- +make ARCH=arm CROSS_COMPILE=$CCOMPILER mrproper +make ARCH=arm CROSS_COMPILE=$CCOMPILER tegra_ventana_android_defconfig +make ARCH=arm CROSS_COMPILE=$CCOMPILER + +This should build your zImage, which you will find in kernel/arch/arm/boot/zImage. You can use the mkbootimg utility to then +make a boot.img: + + ./mkbootimg --kernel <path to android system>/kernel/arch/arm/boot/zImage --ramdisk <path to android system>/out/target/product/ventana/ramdisk.img --output boot.img + +You can then flash this boot.img using the nvflash utility. + diff --git a/drivers/misc/mpu3050/accel/kxtf9.c b/drivers/misc/mpu3050/accel/kxtf9.c new file mode 100755 index 000000000000..ecf9e7dfd3d7 --- /dev/null +++ b/drivers/misc/mpu3050/accel/kxtf9.c @@ -0,0 +1,144 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/******************************************************************************* + * + * $Id: kxtf9.c 3867 2010-10-09 01:06:18Z prao $ + * + *******************************************************************************/ + +/** + * @defgroup ACCELDL (Motion Library - Accelerometer Driver Layer) + * @brief Provides the interface to setup and handle an accelerometers + * connected to the secondary I2C interface of the gyroscope. + * + * @{ + * @file kxtf9.c + * @brief Accelerometer setup and handling methods. +**/ + +/* ------------------ */ +/* - Include Files. - */ +/* ------------------ */ + +#ifdef __KERNEL__ +#include <linux/module.h> +#endif + +#include "mpu3050.h" +#include "mlsl.h" +#include "mlos.h" + +#include <log.h> +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-acc" + +/* --------------------- */ +/* - Variables. - */ +/* --------------------- */ + +/***************************************** + Accelerometer Initialization Functions +*****************************************/ + +static int kxtf9_suspend(mlsl_handle_t mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result; + /* RAM reset */ + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, 0x1d, 0xcd); + return result; +} + +/* full scale setting - register and mask */ +#define ACCEL_KIONIX_CTRL_REG (0x1b) +#define ACCEL_KIONIX_CTRL_MASK (0x18) + +static int kxtf9_resume(mlsl_handle_t mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = ML_SUCCESS; + unsigned char reg; + + /* RAM reset */ + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, 0x1d, 0xcd); + MLOSSleep(10); + /* Wake up */ + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, 0x1b, 0x42); + /* INT_CTRL_REG1: */ + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, 0x1e, 0x14); + /* WUF_THRESH: */ + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, 0x5a, 0x00); + /* DATA_CTRL_REG */ + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, 0x21, 0x04); + /* WUF_TIMER */ + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, 0x29, 0x02); + + /* Full Scale */ + reg = 0xc2; + reg &= ~ACCEL_KIONIX_CTRL_MASK; + reg |= 0x00; /* TODO FIXME michelle */ + if (slave->range.mantissa == 2) { + reg |= 0x00; + } else if (slave->range.mantissa == 4) { + reg |= 0x08; + } else if (slave->range.mantissa == 8) { + reg |= 0x10; + } + /* Normal operation */ + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, 0x1b, reg); + MLOSSleep(50); + + return ML_SUCCESS; +} + +static int kxtf9_read(mlsl_handle_t mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + unsigned char *data) +{ + return ML_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +static struct ext_slave_descr kxtf9_descr = { + /*.suspend = */ kxtf9_suspend, + /*.resume = */ kxtf9_resume, + /*.read = */ kxtf9_read, + /*.name = */ "kxtf9", + /*.type = */ EXT_SLAVE_TYPE_ACCELEROMETER, + /*.id = */ ACCEL_ID_KXTF9, + /*.reg = */ 0x06, + /*.len = */ 6, + /*.endian = */ EXT_SLAVE_LITTLE_ENDIAN, + /*.range = */ {2, 0}, +}; + +struct ext_slave_descr *kxtf9_get_slave_descr(void) +{ + return &kxtf9_descr; +} + +#ifdef __KERNEL__ +EXPORT_SYMBOL(kxtf9_get_slave_descr); +#endif + +/** + * @} +**/ diff --git a/drivers/misc/mpu3050/compass/ak8975.c b/drivers/misc/mpu3050/compass/ak8975.c new file mode 100755 index 000000000000..fb4c033556b3 --- /dev/null +++ b/drivers/misc/mpu3050/compass/ak8975.c @@ -0,0 +1,146 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/******************************************************************************* + * + * $Id: ak8975.c 3879 2010-10-12 03:12:37Z mcaramello $ + * + *******************************************************************************/ + +/** + * @defgroup COMPASSDL (Motion Library - Accelerometer Driver Layer) + * @brief Provides the interface to setup and handle an accelerometers + * connected to the secondary I2C interface of the gyroscope. + * + * @{ + * @file AK8975.c + * @brief Magnetometer setup and handling methods for AKM 8975 compass. +**/ + +/* ------------------ */ +/* - Include Files. - */ +/* ------------------ */ + +#ifdef __KERNEL__ +#include <linux/module.h> +#endif + +#include "mpu3050.h" +#include "mlsl.h" +#include "mlos.h" + +#include <log.h> +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "MPL-compass" + +#define AK8975_REG_ST1 (0x02) +#define AK8975_REG_HXL (0x03) +#define AK8975_REG_ST2 (0x09) + +#define AK8975_REG_CNTL (0x0A) + +#define AK8975_CNTL_MODE_POWER_DOWN (0x00) +#define AK8975_CNTL_MODE_SINGLE_MEASUREMENT (0x01) + +int ak8975_suspend(mlsl_handle_t mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = ML_SUCCESS; + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_POWER_DOWN); + return result; +} + +int ak8975_resume(mlsl_handle_t mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + int result = ML_SUCCESS; + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_SINGLE_MEASUREMENT); + return result; +} + +int ak8975_read(mlsl_handle_t mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, unsigned char *data) +{ + unsigned char stat; + unsigned char stat2; + int result = ML_SUCCESS; + + result = MLSLSerialRead(mlsl_handle, pdata->address, + AK8975_REG_ST1, 1, &stat); + ERROR_CHECK(result); + if (stat & 0x01) { + result = MLSLSerialRead(mlsl_handle, pdata->address, + AK8975_REG_HXL, 6, + (unsigned char *)data); + ERROR_CHECK(result); + result = MLSLSerialRead(mlsl_handle, pdata->address, + AK8975_REG_ST2, 1, &stat2); + ERROR_CHECK(result); + if (stat2 & 0x04) { /*data error */ + return ML_ERROR_COMPASS_DATA_NOT_READY; + } + if (stat2 & 0x08) { + return ML_ERROR_COMPASS_DATA_OVERFLOW; + } + result = MLSLSerialWriteSingle(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_SINGLE_MEASUREMENT); + return ML_SUCCESS; + } else if (stat & 0x02) { + result = MLSLSerialRead(mlsl_handle, pdata->address, + AK8975_REG_ST2, 1, &stat2); + ERROR_CHECK(result); + return ML_ERROR_COMPASS_DATA_OVERFLOW; + } else { + return ML_ERROR_COMPASS_DATA_NOT_READY; + } + +} + +struct ext_slave_descr ak8975_descr = { + /*.suspend = */ ak8975_suspend, + /*.resume = */ ak8975_resume, + /*.read = */ ak8975_read, + /*.name = */ "ak8975", + /*.type = */ EXT_SLAVE_TYPE_COMPASS, + /*.id = */ COMPASS_ID_AKM, + /*.reg = */ 0x06, + /*.len = */ 6, + /*.endian = */ EXT_SLAVE_LITTLE_ENDIAN, + /*.range = */ {9830, 4000} +}; + +struct ext_slave_descr *ak8975_get_slave_descr(void) +{ + return &ak8975_descr; +} + +#ifdef __KERNEL__ +EXPORT_SYMBOL(ak8975_get_slave_descr); +#endif + +/** + * @} +**/ diff --git a/drivers/misc/mpu3050/log.h b/drivers/misc/mpu3050/log.h new file mode 100755 index 000000000000..6cb7eae4afcf --- /dev/null +++ b/drivers/misc/mpu3050/log.h @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2010 InvenSense Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* +// C/C++ logging functions. See the logging documentation for API details. +// +// We'd like these to be available from C code (in case we import some from +// somewhere), so this has a C interface. +// +// The output will be correct when the log file is shared between multiple +// threads and/or multiple processes so long as the operating system +// supports O_APPEND. These calls have mutex-protected data structures +// and so are NOT reentrant. Do not use MPL_LOG in a signal handler. +*/ +#ifndef _LIBS_CUTILS_MPL_LOG_H +#define _LIBS_CUTILS_MPL_LOG_H + +#include <stdarg.h> + +#ifdef ANDROID +#include <utils/Log.h> /* For the LOG macro */ +#endif + +#ifdef __KERNEL__ +#include <linux/kernel.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Normally we strip MPL_LOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define MPL_LOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#define MPL_LOGV /* comment this out to enable VERBOSE level logging */ +#ifndef MPL_LOG_NDEBUG +#ifdef NDEBUG +#define MPL_LOG_NDEBUG 1 +#else +#define MPL_LOG_NDEBUG 0 +#endif +#endif + +#ifdef __KERNEL__ +#define MPL_LOG_UNKNOWN MPL_LOG_VERBOSE +#define MPL_LOG_DEFAULT KERN_DEFAULT +#define MPL_LOG_VERBOSE KERN_CONT +#define MPL_LOG_DEBUG KERN_NOTICE +#define MPL_LOG_INFO KERN_INFO +#define MPL_LOG_WARN KERN_WARNING +#define MPL_LOG_ERROR KERN_ERR +#define MPL_LOG_SILENT MPL_LOG_VERBOSE + +#else + /* Based off the log priorities in android + /system/core/include/android/log.h */ +#define MPL_LOG_UNKNOWN (0) +#define MPL_LOG_DEFAULT (1) +#define MPL_LOG_VERBOSE (2) +#define MPL_LOG_DEBUG (3) +#define MPL_LOG_INFO (4) +#define MPL_LOG_WARN (5) +#define MPL_LOG_ERROR (6) +#define MPL_LOG_SILENT (8) +#endif + +/* + * This is the local tag used for the following simplified + * logging macros. You can change this preprocessor definition + * before using the other macros to change the tag. + */ +#ifndef MPL_LOG_TAG +#ifdef __KERNEL__ +#define MPL_LOG_TAG +#else +#define MPL_LOG_TAG NULL +#endif +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Simplified macro to send a verbose log message using the current MPL_LOG_TAG. + */ +#define MPL_LOGV +#ifndef MPL_LOGV +#if MPL_LOG_NDEBUG +#define MPL_LOGV(...) ((void)0) +#else +#define MPL_LOGV(...) ((void)MPL_LOG(LOG_VERBOSE, MPL_LOG_TAG, __VA_ARGS__)) +#endif +#endif + +#ifndef CONDITION +#define CONDITION(cond) ((cond) != 0) +#endif + +#ifndef MPL_LOGV_IF +#if MPL_LOG_NDEBUG +#define MPL_LOGV_IF(cond, ...) ((void)0) +#else +#define MPL_LOGV_IF(cond, ...) \ + ((CONDITION(cond)) \ + ? ((void)MPL_LOG(LOG_VERBOSE, MPL_LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif +#endif + +/* + * Simplified macro to send a debug log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGD +#define MPL_LOGD(...) ((void)MPL_LOG(LOG_DEBUG, MPL_LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef MPL_LOGD_IF +#define MPL_LOGD_IF(cond, ...) \ + ((CONDITION(cond)) \ + ? ((void)MPL_LOG(LOG_DEBUG, MPL_LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +/* + * Simplified macro to send an info log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGI +#define MPL_LOGI(...) ((void)MPL_LOG(LOG_INFO, MPL_LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef MPL_LOGI_IF +#define MPL_LOGI_IF(cond, ...) \ + ((CONDITION(cond)) \ + ? ((void)MPL_LOG(LOG_INFO, MPL_LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +/* + * Simplified macro to send a warning log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGW +#define MPL_LOGW(...) ((void)MPL_LOG(LOG_WARN, MPL_LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef MPL_LOGW_IF +#define MPL_LOGW_IF(cond, ...) \ + ((CONDITION(cond)) \ + ? ((void)MPL_LOG(LOG_WARN, MPL_LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +/* + * Simplified macro to send an error log message using the current MPL_LOG_TAG. + */ +#ifndef MPL_LOGE +#define MPL_LOGE(...) ((void)MPL_LOG(LOG_ERROR, MPL_LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef MPL_LOGE_IF +#define MPL_LOGE_IF(cond, ...) \ + ((CONDITION(cond)) \ + ? ((void)MPL_LOG(LOG_ERROR, MPL_LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Log a fatal error. If the given condition fails, this stops program + * execution like a normal assertion, but also generating the given message. + * It is NOT stripped from release builds. Note that the condition test + * is -inverted- from the normal assert() semantics. + */ +#define MPL_LOG_ALWAYS_FATAL_IF(cond, ...) \ + ((CONDITION(cond)) \ + ? ((void)android_printAssert(#cond, MPL_LOG_TAG, __VA_ARGS__)) \ + : (void)0) + +#define MPL_LOG_ALWAYS_FATAL(...) \ + (((void)android_printAssert(NULL, MPL_LOG_TAG, __VA_ARGS__))) + +/* + * Versions of MPL_LOG_ALWAYS_FATAL_IF and MPL_LOG_ALWAYS_FATAL that + * are stripped out of release builds. + */ +#if MPL_LOG_NDEBUG + +#define MPL_LOG_FATAL_IF(cond, ...) ((void)0) +#define MPL_LOG_FATAL(...) ((void)0) + +#else + +#define MPL_LOG_FATAL_IF(cond, ...) MPL_LOG_ALWAYS_FATAL_IF(cond, __VA_ARGS__) +#define MPL_LOG_FATAL(...) MPL_LOG_ALWAYS_FATAL(__VA_ARGS__) + +#endif + +/* + * Assertion that generates a log message when the assertion fails. + * Stripped out of release builds. Uses the current MPL_LOG_TAG. + */ +#define MPL_LOG_ASSERT(cond, ...) MPL_LOG_FATAL_IF(!(cond), __VA_ARGS__) +/*#define MPL_LOG_ASSERT(cond) MPL_LOG_FATAL_IF(!(cond), "Assertion failed: " #cond) */ + +/* --------------------------------------------------------------------- */ + +/* + * Basic log message macro. + * + * Example: + * MPL_LOG(MPL_LOG_WARN, NULL, "Failed with error %d", errno); + * + * The second argument may be NULL or "" to indicate the "global" tag. + */ +#ifndef MPL_LOG +#define MPL_LOG(priority, tag, ...) \ + MPL_LOG_PRI(priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef MPL_LOG_PRI +#ifdef ANDROID +#define MPL_LOG_PRI(priority, tag, ...) \ + LOG(priority, tag, __VA_ARGS__) +#elif defined __KERNEL__ +#define MPL_LOG_PRI(priority, tag, ...) \ + printk(MPL_##priority tag __VA_ARGS__) +#else +#define MPL_LOG_PRI(priority, tag, ...) \ + _MLPrintLog(MPL_##priority, tag, __VA_ARGS__) +#endif +#endif + +/* + * Log macro that allows you to pass in a varargs ("args" is a va_list). + */ +#ifndef MPL_LOG_PRI_VA +#ifdef ANDROID +#define MPL_LOG_PRI_VA(priority, tag, fmt, args) \ + android_vprintLog(priority, NULL, tag, fmt, args) +#elif defined __KERNEL__ +#define MPL_LOG_PRI_VA(priority, tag, fmt, args) \ + vprintk(MPL_##priority tag fmt, args) +#else +#define MPL_LOG_PRI_VA(priority, tag, fmt, args) \ + _MLPrintVaLog(priority, NULL, tag, fmt, args) +#endif +#endif + +/* --------------------------------------------------------------------- */ + +/* + * =========================================================================== + * + * The stuff in the rest of this file should not be used directly. + */ + +#ifndef ANDROID + int _MLPrintLog(int priority, const char *tag, const char *fmt, ...); + int _MLPrintVaLog(int priority, const char *tag, const char *fmt, + va_list args); +/* Final implementation of actual writing to a character device */ + int _MLWriteLog(const char *buf, int buflen); +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _LIBS_CUTILS_MPL_LOG_H */ diff --git a/drivers/misc/mpu3050/mldl_cfg.c b/drivers/misc/mpu3050/mldl_cfg.c new file mode 100755 index 000000000000..d96b0a72a549 --- /dev/null +++ b/drivers/misc/mpu3050/mldl_cfg.c @@ -0,0 +1,872 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/******************************************************************************* + * + * $Id: mldl_cfg.c 3881 2010-10-12 18:58:45Z prao $ + * + ******************************************************************************/ + +/** + * @addtogroup MLDL + * + * @{ + * @file mldl_cfg.c + * @brief The Motion Library Driver Layer. + */ + +/* ------------------ */ +/* - Include Files. - */ +/* ------------------ */ + +#include <stddef.h> + +#include "mldl_cfg.h" +#include "mpu3050.h" + +#include "mlsl.h" +#include "mlos.h" + +#include "log.h" +#undef MPL_LOG_TAG +#define MPL_LOG_TAG "mldl_cfg:" + +/* --------------------- */ +/* - Variables. - */ +/* --------------------- */ + +/* ---------------------- */ +/* - Static Functions. - */ +/* ---------------------- */ + +/** + * @internal + * @brief MLDLCfgDMP configures the Digital Motion Processor internal to + * the MPU. The DMP can be enabled or disabled and the start address + * can be set. + * + * @param enableRun Enables the DMP processing if set to TRUE. + * @param enableFIFO Enables DMP output to the FIFO if set to TRUE. + * + * @return Zero if the command is successful, an error code otherwise. + */ +static int MLDLCtrlDmp(struct mldl_cfg *pdata, mlsl_handle_t mlsl_handle, + bool enableRun, bool enableFIFO) +{ + unsigned char b; + + MLSLSerialRead(mlsl_handle, pdata->addr, MPUREG_USER_CTRL, 1, &b); + if (enableRun) { + b |= BIT_DMP_EN; + } else { + b &= ~BIT_DMP_EN; + } + + if (enableFIFO) { + b |= BIT_FIFO_EN; + } + + b |= BIT_DMP_RST; + + MLSLSerialWriteSingle(mlsl_handle, pdata->addr, MPUREG_USER_CTRL, b); + + return ML_SUCCESS; +} + +/** + * @brief Starts the DMP running + * + * @return ML_SUCCESS or non-zero error code + */ +static int MLDLDmpStart(struct mldl_cfg *pdata, mlsl_handle_t mlsl_handle) +{ + unsigned char fifoBuf[2]; + unsigned char tries = 0; + unsigned char userCtrlReg; + int result; + unsigned short len = !0; + + result = MLSLSerialRead(mlsl_handle, pdata->addr, + MPUREG_USER_CTRL, 1, &userCtrlReg); + + while (len != 0 && tries < 6) { + result = MLSLSerialWriteSingle(mlsl_handle, pdata->addr, + MPUREG_USER_CTRL, + ((userCtrlReg & (~BIT_FIFO_EN)) + | BIT_FIFO_RST)); + MLSLSerialRead(mlsl_handle, pdata->addr, + MPUREG_FIFO_COUNTH, 2, fifoBuf); + len = (((unsigned short)fifoBuf[0] << 8) + | (unsigned short)fifoBuf[1]); + tries++; + } + + MLSLSerialWriteSingle(mlsl_handle, pdata->addr, + MPUREG_USER_CTRL, userCtrlReg); + + return MLDLCtrlDmp(pdata, mlsl_handle, + pdata->dmp_enable, pdata->fifo_enable); +} + +/** + * @brief enables/disables the I2C pass through to the accelerometer device. + * @param enable Non-zero to enable pass through. + * @return ML_SUCCESS if the command is successful, an error code otherwise. + */ +static int MLDLSetI2CBypass(struct mldl_cfg *mldl_cfg, + mlsl_handle_t mlsl_handle, unsigned char enable) +{ + unsigned char b; + int result; + +#ifdef ML_USE_DMP_SIM + if (!MLGetGyroPresent()) /* done this way so that pc demo */ + return ML_SUCCESS; /* w/arm board works with universal api */ +#endif + + /*---- get current 'USER_CTRL' into b ----*/ + result = MLSLSerialRead(mlsl_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, 1, &b); + ERROR_CHECK(result); + + /* No change */ + if ((b & BIT_AUX_IF_EN) != (enable * BIT_AUX_IF_EN)) + return ML_SUCCESS; + + b &= ~BIT_AUX_IF_EN; + + if (!enable) { + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, + (b | BIT_AUX_IF_EN)); + ERROR_CHECK(result); + } else { + /* Coming out of I2C is tricky due to severla erratta. Do not modify + * this algorithm */ + /* + * 1) wait for the right time and send the command to change the ime + * i2c slave address to an invalid address that will get naked + * + * 0x00 is broadcast. 0x7F is ulikely to be used by any accels. + */ + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_AUX_SLV_ADDR, 0x7F); + ERROR_CHECK(result); + /* + * 2) wait enough time for a nack to occur, then go into bypass mode: + */ + MLOSSleep(2); + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, (b)); + ERROR_CHECK(result); + /* + * 3) wait for up to one MPU cycle then restore the slave address + */ + MLOSSleep(5); + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_AUX_SLV_ADDR, + mldl_cfg->pdata->accel.address); + ERROR_CHECK(result); + + /* + * 4) reset the ime interface + */ + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, + (b | BIT_AUX_IF_RST)); + ERROR_CHECK(result); + MLOSSleep(2); + } + + return result; +} + +struct tsProdRevMap { + /*unsigned char prodRev; */ + unsigned char siliconRev; + unsigned short sensTrim; +}; + +#define NUM_OF_PROD_REVS (DIM(prodRevsMap)) + +#define OLDEST_PROD_REV_SUPPORTED 11 +static struct tsProdRevMap prodRevsMap[] = { + {0, 0}, + {MPU_SILICON_REV_A4, 131}, /* 1 A? OBSOLETED */ + {MPU_SILICON_REV_A4, 131}, /* 2 | */ + {MPU_SILICON_REV_A4, 131}, /* 3 V */ + {MPU_SILICON_REV_A4, 131}, /* 4 */ + {MPU_SILICON_REV_A4, 131}, /* 5 */ + {MPU_SILICON_REV_A4, 131}, /* 6 */ + {MPU_SILICON_REV_A4, 131}, /* 7 */ + {MPU_SILICON_REV_A4, 131}, /* 8 */ + {MPU_SILICON_REV_A4, 131}, /* 9 */ + {MPU_SILICON_REV_A4, 131}, /* 10 */ + {MPU_SILICON_REV_B1, 131}, /* 11 B1 */ + {MPU_SILICON_REV_B1, 131}, /* 12 | */ + {MPU_SILICON_REV_B1, 131}, /* 13 V */ + {MPU_SILICON_REV_B1, 131}, /* 14 B4 */ + {MPU_SILICON_REV_B4, 131}, /* 15 | {MPU_SILICON_REV_B4, 131}, // 16 V */ + {MPU_SILICON_REV_B4, 131}, /* 17 */ + {MPU_SILICON_REV_B4, 131}, /* 18 */ + {MPU_SILICON_REV_B4, 115}, /* 19 */ + {MPU_SILICON_REV_B4, 115}, /* 20 */ + {MPU_SILICON_REV_B6, 131}, /* 21 B6 */ + {MPU_SILICON_REV_B4, 115}, /* 22 B4 */ +}; + +/** + * @internal + * @brief Get the silicon revision ID from OTP. + * The silicon revision number is in read from OTP bank 0, + * ADDR6[7:2]. The corresponding ID is retrieved by lookup + * in a map. + * @return The silicon revision ID (0 on error). + */ +static int MLDLGetSiliconRev(struct mldl_cfg *pdata, mlsl_handle_t mlsl_handle) +{ + int result; + unsigned char index = 0x00; + unsigned char bank = + (BIT_PRFTCH_EN | BIT_CFG_USER_BANK | MPU_MEM_OTP_BANK_0); + unsigned short memAddr = ((bank << 8) | 0x06); + + result = MLSLSerialReadMem(mlsl_handle, pdata->addr, + memAddr, 1, &index); + if (result) + return result; + index >>= 2; + + if (index < OLDEST_PROD_REV_SUPPORTED || NUM_OF_PROD_REVS < index) { + pdata->silicon_revision = 0; + return ML_ERROR_INVALID_MODULE; + } else { + pdata->silicon_revision = prodRevsMap[index].siliconRev; + pdata->trim = prodRevsMap[index].sensTrim; + } + return result; +} + +/** + * @brief Enable/Disable the use MPU's VDDIO level shifters. + * When enabled the voltage interface with AUX or other external + * accelerometer is using Vlogic instead of VDD (supply). + * + * @note Must be called after MLSerialOpen(). + * @note Typically be called before MLDmpOpen(). + * If called after MLDmpOpen(), must be followed by a call to + * MLDLApplyLevelShifterBit() to write the setting on the hw. + * + * @param[in] enable + * 1 to enable, 0 to disable + * + * @return ML_SUCCESS if successfull, a non-zero error code otherwise. +**/ +static int MLDLSetLevelShifterBit(struct mldl_cfg *pdata, + mlsl_handle_t mlsl_handle, + unsigned char enable) +{ + int result; + unsigned char reg; + unsigned char mask; + unsigned char regval; + + if (0 == pdata->silicon_revision) { + return ML_ERROR_INVALID_PARAMETER; + } + + /*-- on parts before B6 the VDDIO bit is bit 7 of ACCEL_BURST_ADDR -- + NOTE: this is incompatible with ST accelerometers where the VDDIO + bit MUST be set to enable ST's internal logic to autoincrement + the register address on burst reads --*/ + if ((pdata->silicon_revision & 0xf) < MPU_SILICON_REV_B6) { + reg = MPUREG_ACCEL_BURST_ADDR; + mask = 0x80; + } else { + /*-- on B6 parts the VDDIO bit was moved to FIFO_EN2 => + the mask is always 0x04 --*/ + reg = MPUREG_FIFO_EN2; + mask = 0x04; + } + + result = MLSLSerialRead(mlsl_handle, pdata->addr, reg, 1, ®val); + if (result) + return result; + + if (enable) + regval |= mask; + else + regval &= ~mask; + + result = MLSLSerialWriteSingle(mlsl_handle, pdata->addr, reg, regval); + + return result; +} + +/** + * @internal + * @brief This function controls the power management on the MPU device. + * The entire chip can be put to low power sleep mode, or individual + * gyros can be turned on/off. + * + * Putting the device into sleep mode depending upon the changing needs + * of the associated applications is a recommended method for reducing + * power consuption. It is a safe opearation in that sleep/wake up of + * gyros while running will not result in any interruption of data. + * + * Although it is entirely allowed to put the device into full sleep + * while running the DMP, it is not recomended because it will disrupt + * the ongoing calculations carried on inside the DMP and consequently + * the sensor fusion algorithm. Furthermore, while in sleep mode + * read & write operation from the app processor on both registers and + * memory are disabled and can only regained by restoring the MPU in + * normal power mode. + * Disabling any of the gyro axis will reduce the associated power + * consuption from the PLL but will not stop the DMP from running + * state. + * + * @param reset + * Non-zero to reset the device. Note that this setting + * is volatile and the corresponding register bit will + * clear itself right after. + * @param sleep + * Non-zero to put device into full sleep. + * @param disable_gx + * Non-zero to disable gyro X. + * @param disable_gy + * Non-zero to disable gyro Y. + * @param disable_gz + * Non-zero to disable gyro Z. + * + * @return ML_SUCCESS if successfull; a non-zero error code otherwise. + */ +static int MLDLPowerMgmtMPU(struct mldl_cfg *pdata, + mlsl_handle_t mlsl_handle, + unsigned char reset, + unsigned char sleep, + unsigned char disable_gx, + unsigned char disable_gy, unsigned char disable_gz) +{ + unsigned char b; + int result; + + result = + MLSLSerialRead(mlsl_handle, pdata->addr, MPUREG_PWR_MGM, 1, &b); + ERROR_CHECK(result); + + /* If we are awake, we need to put it in bypass before resetting */ + if ((!(b & BIT_SLEEP)) && reset) { + result = MLDLSetI2CBypass(pdata, mlsl_handle, 1); + } + + /* Reset if requested */ + if (reset) { + result = + MLSLSerialWriteSingle(mlsl_handle, pdata->addr, + MPUREG_PWR_MGM, b | BIT_H_RESET); + MLOSSleep(5); + } + + /* Some chips are awake after reset and some are asleep, check the status */ + result = + MLSLSerialRead(mlsl_handle, pdata->addr, MPUREG_PWR_MGM, 1, &b); + ERROR_CHECK(result); + + /* Update the suspended state just in case we return early */ + if (b & BIT_SLEEP) { + pdata->is_suspended = TRUE; + } else { + pdata->is_suspended = FALSE; + } + + if ((b & (BIT_SLEEP | BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG)) + == ((sleep * BIT_SLEEP) | + (disable_gz * BIT_STBY_XG) | + (disable_gy * BIT_STBY_YG) | (disable_gz * BIT_STBY_ZG))) { + return ML_SUCCESS; + } + + /* + * This specific transition between states needs to be reinterpreted: + * (1,1,1,1) -> (0,1,1,1) has to become + * (1,1,1,1) -> (1,0,0,0) -> (0,1,1,1) + * where + * (1,1,1,1) stands for (sleep=1,disable_gx=1,disable_gy=1,disable_gz=1) + */ + if ((b & (BIT_SLEEP | BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG)) == (BIT_SLEEP | BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG) /* (1,1,1,1) */ + && ((!sleep) && disable_gx && disable_gy && disable_gz)) { /* (0,1,1,1) */ + + result = MLDLPowerMgmtMPU(pdata, mlsl_handle, 0, 1, 0, 0, 0); + if (result) + return result; + b |= BIT_SLEEP; + b &= ~(BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG); + } + + if ((b & BIT_SLEEP) != (sleep * BIT_SLEEP)) { + if (sleep) { + result = MLDLSetI2CBypass(pdata, mlsl_handle, 1); + ERROR_CHECK(result); + b |= BIT_SLEEP; + result = MLSLSerialWriteSingle(mlsl_handle, pdata->addr, + MPUREG_PWR_MGM, b); + ERROR_CHECK(result); + pdata->is_suspended = TRUE; + } else { + b &= ~BIT_SLEEP; + + result = MLSLSerialWriteSingle(mlsl_handle, pdata->addr, + MPUREG_PWR_MGM, b); + ERROR_CHECK(result); + pdata->is_suspended = FALSE; + MLOSSleep(5); + } + } + /*--- + WORKAROUND FOR PUTTING GYRO AXIS in STAND-BY MODE + 1) put one axis at a time in stand-by + ---*/ + if ((b & BIT_STBY_XG) != (disable_gx * BIT_STBY_XG)) { + b ^= BIT_STBY_XG; + result = MLSLSerialWriteSingle(mlsl_handle, pdata->addr, + MPUREG_PWR_MGM, b); + ERROR_CHECK(result); + } + if ((b & BIT_STBY_YG) != (disable_gy * BIT_STBY_YG)) { + b ^= BIT_STBY_YG; + result = MLSLSerialWriteSingle(mlsl_handle, pdata->addr, + MPUREG_PWR_MGM, b); + ERROR_CHECK(result); + } + if ((b & BIT_STBY_ZG) != (disable_gz * BIT_STBY_ZG)) { + b ^= BIT_STBY_ZG; + result = MLSLSerialWriteSingle(mlsl_handle, pdata->addr, + MPUREG_PWR_MGM, b); + ERROR_CHECK(result); + } + + return ML_SUCCESS; +} + +void mpu3050_print_cfg(struct mldl_cfg *mldl_cfg) +{ + struct mpu3050_platform_data *pdata = mldl_cfg->pdata; + struct ext_slave_platform_data *accel = &mldl_cfg->pdata->accel; + struct ext_slave_platform_data *compass = &mldl_cfg->pdata->compass; + + MPL_LOGD("mldl_cfg.addr = %02x\n", mldl_cfg->addr); + MPL_LOGD("mldl_cfg.int_config = %02x\n", mldl_cfg->int_config); + MPL_LOGD("mldl_cfg.ext_sync = %02x\n", mldl_cfg->ext_sync); + MPL_LOGD("mldl_cfg.full_scale = %02x\n", mldl_cfg->full_scale); + MPL_LOGD("mldl_cfg.lpf = %02x\n", mldl_cfg->lpf); + MPL_LOGD("mldl_cfg.clk_src = %02x\n", mldl_cfg->clk_src); + MPL_LOGD("mldl_cfg.divider = %02x\n", mldl_cfg->divider); + MPL_LOGD("mldl_cfg.dmp_enable = %02x\n", mldl_cfg->dmp_enable); + MPL_LOGD("mldl_cfg.fifo_enable = %02x\n", mldl_cfg->fifo_enable); + MPL_LOGD("mldl_cfg.dmp_cfg1 = %02x\n", mldl_cfg->dmp_cfg1); + MPL_LOGD("mldl_cfg.dmp_cfg2 = %02x\n", mldl_cfg->dmp_cfg2); + MPL_LOGD("mldl_cfg.offset_tc[0] = %02x\n", mldl_cfg->offset_tc[0]); + MPL_LOGD("mldl_cfg.offset_tc[1] = %02x\n", mldl_cfg->offset_tc[1]); + MPL_LOGD("mldl_cfg.offset_tc[2] = %02x\n", mldl_cfg->offset_tc[2]); + MPL_LOGD("mldl_cfg.silicon_revision = %02x\n", + mldl_cfg->silicon_revision); + MPL_LOGD("mldl_cfg.product_id = %02x\n", mldl_cfg->product_id); + MPL_LOGD("mldl_cfg.trim = %02x\n", mldl_cfg->trim); + + if (mldl_cfg->accel) { + MPL_LOGD("slave_accel->suspend = %02x\n", + (int)mldl_cfg->accel->suspend); + MPL_LOGD("slave_accel->resume = %02x\n", + (int)mldl_cfg->accel->resume); + MPL_LOGD("slave_accel->read = %02x\n", + (int)mldl_cfg->accel->read); + MPL_LOGD("slave_accel->type = %02x\n", + mldl_cfg->accel->type); + MPL_LOGD("slave_accel->reg = %02x\n", + mldl_cfg->accel->reg); + MPL_LOGD("slave_accel->len = %02x\n", + mldl_cfg->accel->len); + MPL_LOGD("slave_accel->endian = %02x\n", + mldl_cfg->accel->endian); + MPL_LOGD("slave_accel->range.mantissa= %02lx\n", + mldl_cfg->accel->range.mantissa); + MPL_LOGD("slave_accel->range.fraction= %02lx\n", + mldl_cfg->accel->range.fraction); + } else { + MPL_LOGD("slave_accel = NULL\n"); + } + + if (mldl_cfg->compass) { + MPL_LOGD("slave_compass->suspend = %02x\n", + (int)mldl_cfg->compass->suspend); + MPL_LOGD("slave_compass->resume = %02x\n", + (int)mldl_cfg->compass->resume); + MPL_LOGD("slave_compass->read = %02x\n", + (int)mldl_cfg->compass->read); + MPL_LOGD("slave_compass->type = %02x\n", + mldl_cfg->compass->type); + MPL_LOGD("slave_compass->reg = %02x\n", + mldl_cfg->compass->reg); + MPL_LOGD("slave_compass->len = %02x\n", + mldl_cfg->compass->len); + MPL_LOGD("slave_compass->endian = %02x\n", + mldl_cfg->compass->endian); + MPL_LOGD("slave_compass->range.mantissa= %02lx\n", + mldl_cfg->compass->range.mantissa); + MPL_LOGD("slave_compass->range.fraction= %02lx\n", + mldl_cfg->compass->range.fraction); + + } else { + MPL_LOGD("slave_compass = NULL\n"); + } + MPL_LOGD("accel->get_slave_descr = %x\n", + (unsigned int)accel->get_slave_descr); + MPL_LOGD("accel->adapt_num = %02x\n", accel->adapt_num); + MPL_LOGD("accel->bus = %02x\n", accel->bus); + MPL_LOGD("accel->address = %02x\n", accel->address); + MPL_LOGD("accel->orientation = \n" + " %2d %2d %2d\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n", + accel->orientation[0], accel->orientation[1], + accel->orientation[2], accel->orientation[3], + accel->orientation[4], accel->orientation[5], + accel->orientation[6], accel->orientation[7], + accel->orientation[8]); + MPL_LOGD("compass->get_slave_descr = %x\n", + (unsigned int)compass->get_slave_descr); + MPL_LOGD("compass->adapt_num = %02x\n", compass->adapt_num); + MPL_LOGD("compass->bus = %02x\n", compass->bus); + MPL_LOGD("compass->address = %02x\n", compass->address); + MPL_LOGD("compass->orientation = \n" + " %2d %2d %2d\n" + " %2d %2d %2d\n" + " %2d %2d %2d\n", + compass->orientation[0], compass->orientation[1], + compass->orientation[2], compass->orientation[3], + compass->orientation[4], compass->orientation[5], + compass->orientation[6], compass->orientation[7], + compass->orientation[8]); + + MPL_LOGD("pdata->int_config = %02x\n", pdata->int_config); + MPL_LOGD("pdata->level_shifter = %02x\n", pdata->level_shifter); + MPL_LOGD("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]); + + MPL_LOGD("Struct sizes: mldl_cfg: %d, " + "ext_slave_descr:%d, mpu3050_platform_data:%d: RamOffset: %d\n", + sizeof(struct mldl_cfg), sizeof(struct ext_slave_descr), + sizeof(struct mpu3050_platform_data), + offsetof(struct mldl_cfg, ram)); +} + +/******************************************************************************* + ******************************************************************************* + * Exported functions + ******************************************************************************* + ******************************************************************************/ + +/** + * Initializes the pdata structure to defaults. + * + * Opens the device to read silicon revision, product id and whoami. + * + * @param mldl_cfg + * The internal device configuration data structure. + * @param mlsl_handle + * The serial communication handle. + * + * @return ML_SUCCESS if silicon revision, product id and woami are supported + * by this software. + */ +int mpu3050_open(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle) +{ + int result; + /* Default is Logic HIGH, pushpull, latch disabled, anyread to clear */ + mldl_cfg->int_config = BIT_INT_ANYRD_2CLEAR | BIT_DMP_INT_EN; + mldl_cfg->clk_src = MPU_CLK_SEL_PLLGYROZ; + mldl_cfg->lpf = MPU_FILTER_42HZ; + mldl_cfg->full_scale = MPU_FS_2000DPS; + mldl_cfg->divider = 4; + mldl_cfg->dmp_enable = 1; + mldl_cfg->fifo_enable = 1; + mldl_cfg->ext_sync = 0; + mldl_cfg->dmp_cfg1 = 0; + mldl_cfg->dmp_cfg2 = 0; + if (mldl_cfg->addr == 0) { +#ifdef __KERNEL__ + return ML_ERROR_INVALID_PARAMETER; +#else + mldl_cfg->addr = 0x68; +#endif + } + + /* + * Reset, + * Take the DMP out of sleep, and + * read the product_id, sillicon rev and whoami + */ + result = MLDLPowerMgmtMPU(mldl_cfg, mlsl_handle, 1, 0, 0, 0, 0); + ERROR_CHECK(result); + + result = MLDLGetSiliconRev(mldl_cfg, mlsl_handle); + ERROR_CHECK(result); + result = MLSLSerialRead(mlsl_handle, mldl_cfg->addr, + MPUREG_PRODUCT_ID, 1, &mldl_cfg->product_id); + ERROR_CHECK(result); + + /* Get the factory temperature compensation offsets */ + result = MLSLSerialRead(mlsl_handle, mldl_cfg->addr, + MPUREG_XG_OFFS_TC, 1, &mldl_cfg->offset_tc[0]); + ERROR_CHECK(result); + result = MLSLSerialRead(mlsl_handle, mldl_cfg->addr, + MPUREG_YG_OFFS_TC, 1, &mldl_cfg->offset_tc[1]); + ERROR_CHECK(result); + result = MLSLSerialRead(mlsl_handle, mldl_cfg->addr, + MPUREG_ZG_OFFS_TC, 1, &mldl_cfg->offset_tc[2]); + ERROR_CHECK(result); + + /* Configure the MPU */ + result = MLDLPowerMgmtMPU(mldl_cfg, mlsl_handle, 0, 1, 0, 0, 0); + ERROR_CHECK(result); + return result; +} + +int mpu3050_resume(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle, + mlsl_handle_t accel_handle, + mlsl_handle_t compass_handle, + bool resume_accel, bool resume_compass) +{ + int result; + int ii; + int jj; + unsigned char reg; + + /* mpu3050_print_cfg(mldl_cfg); */ + /* Wake up the part */ + result = MLDLPowerMgmtMPU(mldl_cfg, mlsl_handle, 1, 0, 0, 0, 0); + ERROR_CHECK(result); + + /* Configure the MPU */ + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_INT_CFG, + (mldl_cfg->int_config | mldl_cfg-> + pdata->int_config)); + ERROR_CHECK(result); + + result = MLSLSerialRead(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, 1, ®); + ERROR_CHECK(result); + reg &= ~BITS_CLKSEL; + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_PWR_MGM, mldl_cfg->clk_src | reg); + ERROR_CHECK(result); + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_SMPLRT_DIV, mldl_cfg->divider); + ERROR_CHECK(result); + + reg = DLPF_FS_SYNC_VALUE(mldl_cfg->ext_sync, + mldl_cfg->full_scale, mldl_cfg->lpf); + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_DLPF_FS_SYNC, reg); + ERROR_CHECK(result); + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_DMP_CFG_1, mldl_cfg->dmp_cfg1); + ERROR_CHECK(result); + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_DMP_CFG_2, mldl_cfg->dmp_cfg2); + ERROR_CHECK(result); + + /* Write and verify memory */ + for (ii = 0; ii < MPU_MEM_NUM_RAM_BANKS; ii++) { + unsigned char read[128]; + result = MLSLSerialWriteMem(mlsl_handle, mldl_cfg->addr, + ((ii << 8)), + MPU_MEM_BANK_SIZE / 2, + mldl_cfg->ram[ii]); + ERROR_CHECK(result); + result = MLSLSerialReadMem(mlsl_handle, mldl_cfg->addr, + ((ii << 8) | 0), + MPU_MEM_BANK_SIZE / 2, read); + ERROR_CHECK(result); + + for (jj = 0; jj < MPU_MEM_BANK_SIZE / 2; jj++) { + /* skip the register memory locations */ + if (ii == 0 && jj < 20) + continue; + if (mldl_cfg->ram[ii][jj] != read[jj]) { + result = ML_ERROR_SERIAL_WRITE; + break; + } + } + ERROR_CHECK(result); + + result = MLSLSerialWriteMem(mlsl_handle, mldl_cfg->addr, + ((ii << 8) | MPU_MEM_BANK_SIZE / 2), + MPU_MEM_BANK_SIZE / 2, + &mldl_cfg->ram[ii][MPU_MEM_BANK_SIZE + / 2]); + ERROR_CHECK(result); + result = MLSLSerialReadMem(mlsl_handle, mldl_cfg->addr, + ((ii << 8) | MPU_MEM_BANK_SIZE / 2), + MPU_MEM_BANK_SIZE / 2, read); + ERROR_CHECK(result); + for (jj = 0; jj < MPU_MEM_BANK_SIZE / 2; jj++) { + if (mldl_cfg->ram[ii][MPU_MEM_BANK_SIZE / 2 + jj] != + read[jj]) { + result = ML_ERROR_SERIAL_WRITE; + break; + } + } + ERROR_CHECK(result); + } + + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_XG_OFFS_TC, + mldl_cfg->offset_tc[0]); + ERROR_CHECK(result); + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_YG_OFFS_TC, + mldl_cfg->offset_tc[1]); + ERROR_CHECK(result); + result = MLSLSerialWriteSingle(mlsl_handle, mldl_cfg->addr, + MPUREG_ZG_OFFS_TC, + mldl_cfg->offset_tc[2]); + ERROR_CHECK(result); + + /* Configure slaves */ + result = MLDLSetLevelShifterBit(mldl_cfg, mlsl_handle, + mldl_cfg->pdata->level_shifter); + ERROR_CHECK(result); + + if (resume_accel) { + if ((!mldl_cfg->accel) || (!mldl_cfg->accel->resume)) { + return ML_ERROR_INVALID_PARAMETER; + } + result = mldl_cfg->accel->resume(accel_handle, + mldl_cfg->accel, + &mldl_cfg->pdata->accel); + ERROR_CHECK(result); + if (EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata->accel.bus) { + /* Address */ + result = + MLSLSerialWriteSingle(accel_handle, mldl_cfg->addr, + MPUREG_AUX_SLV_ADDR, + mldl_cfg->pdata-> + accel.address); + ERROR_CHECK(result); + /* Register */ + result = MLSLSerialRead(accel_handle, mldl_cfg->addr, + MPUREG_ACCEL_BURST_ADDR, 1, + ®); + ERROR_CHECK(result); + reg = ((reg & 0x80) | mldl_cfg->accel->reg); + /* Set VDDIO bit for ST accel */ + if ((ACCEL_ID_LIS331 == mldl_cfg->accel->id) + || (ACCEL_ID_LSM303 == mldl_cfg->accel->id)) { + reg |= 0x80; + } + result = + MLSLSerialWriteSingle(accel_handle, mldl_cfg->addr, + MPUREG_ACCEL_BURST_ADDR, reg); + ERROR_CHECK(result); + /* Length */ + result = MLSLSerialRead(accel_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, 1, ®); + ERROR_CHECK(result); + reg = (reg & ~BIT_AUX_RD_LENG); + result = + MLSLSerialWriteSingle(accel_handle, mldl_cfg->addr, + MPUREG_USER_CTRL, reg); + ERROR_CHECK(result); + result = MLDLSetI2CBypass(mldl_cfg, accel_handle, 0); + ERROR_CHECK(result); + } + } + + if (resume_compass) { + if ((mldl_cfg->compass) && (mldl_cfg->compass->resume)) { + result = mldl_cfg->compass->resume(compass_handle, + mldl_cfg->compass, + &mldl_cfg-> + pdata->compass); + ERROR_CHECK(result); + } + } + + /* Now start */ + result = MLDLDmpStart(mldl_cfg, mlsl_handle); + ERROR_CHECK(result); + return result; +} + +int mpu3050_suspend(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle, + mlsl_handle_t accel_handle, + mlsl_handle_t compass_handle, bool accel, bool compass) +{ + int result; + /* This puts the bus into bypass mode */ + result = MLDLPowerMgmtMPU(mldl_cfg, mlsl_handle, 0, 1, 0, 0, 0); + if (ML_SUCCESS == result && + accel && mldl_cfg->accel && mldl_cfg->accel->suspend) { + result = mldl_cfg->accel->suspend(accel_handle, + mldl_cfg->accel, + &mldl_cfg->pdata->accel); + } + + if (ML_SUCCESS == result && compass && + mldl_cfg->compass && mldl_cfg->compass->suspend) { + result = mldl_cfg->compass->suspend(compass_handle, + mldl_cfg->compass, + &mldl_cfg->pdata->compass); + } + return result; +} + +int mpu3050_read_accel(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle, + unsigned char *data) +{ + if (NULL != mldl_cfg->accel && NULL != mldl_cfg->accel->read) + return mldl_cfg->accel->read(mlsl_handle, + mldl_cfg->accel, + &mldl_cfg->pdata->accel, data); + else + return ML_ERROR_NOT_OPENED; +} + +int mpu3050_read_compass(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle, + unsigned char *data) +{ + if (NULL != mldl_cfg->compass && NULL != mldl_cfg->compass->read) + return mldl_cfg->compass->read(mlsl_handle, + mldl_cfg->compass, + &mldl_cfg->pdata->compass, data); + else + return ML_ERROR_NOT_OPENED; +} + +/***************************/ + /**@}*//* end of defgroup */ +/***************************/ diff --git a/drivers/misc/mpu3050/mldl_cfg.h b/drivers/misc/mpu3050/mldl_cfg.h new file mode 100755 index 000000000000..03f752b75966 --- /dev/null +++ b/drivers/misc/mpu3050/mldl_cfg.h @@ -0,0 +1,98 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/******************************************************************************* + * + * $Id: mldl_cfg.h 3876 2010-10-12 02:42:22Z prao $ + * + *******************************************************************************/ + +/** + * @addtogroup MLDL + * + * @{ + * @file mldl_cfg.h + * @brief The Motion Library Driver Layer Configuration header file. + */ + +#ifndef __MLDL_CFG_H__ +#define __MLDL_CFG_H__ + +/* ------------------ */ +/* - Include Files. - */ +/* ------------------ */ + +#include "mlsl.h" +#include "mpu3050.h" + +/* --------------------- */ +/* - Variables. - */ +/* --------------------- */ + +/* Platform data for the MPU */ +struct mldl_cfg { + /* MPU related configuration */ + unsigned char addr; + unsigned char int_config; + unsigned char ext_sync; + unsigned char full_scale; + unsigned char lpf; + unsigned char clk_src; + unsigned char divider; + unsigned char dmp_enable; + unsigned char fifo_enable; + unsigned char dmp_cfg1; + unsigned char dmp_cfg2; + unsigned char offset_tc[MPU_NUM_AXES]; + unsigned char __packing; + unsigned char ram[MPU_MEM_NUM_RAM_BANKS][MPU_MEM_BANK_SIZE]; + + /* MPU Related stored status and info */ + unsigned char silicon_revision; + unsigned char product_id; + unsigned short trim; + + /* Driver/Kernel related state information */ + int is_suspended; + + /* Slave related information */ + struct ext_slave_descr *accel; + struct ext_slave_descr *compass; + + struct mpu3050_platform_data *pdata; +}; + +int mpu3050_open(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle); +int mpu3050_resume(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle, + mlsl_handle_t accel_handle, + mlsl_handle_t compass_handle, + bool resume_accel, bool resume_compass); +int mpu3050_suspend(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle, + mlsl_handle_t accel_handle, + mlsl_handle_t compass_handle, + bool suspend_accel, bool suspend_compass); +int mpu3050_read_accel(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle, + unsigned char *data); +int mpu3050_read_compass(struct mldl_cfg *mldl_cfg, mlsl_handle_t mlsl_handle, + unsigned char *data); + +#endif /* __MLDL_CFG_H__ */ + +/***************************/ + /**@}*//* end of defgroup */ +/***************************/ diff --git a/drivers/misc/mpu3050/mlos-kernel.c b/drivers/misc/mpu3050/mlos-kernel.c new file mode 100755 index 000000000000..9492ec11a0bf --- /dev/null +++ b/drivers/misc/mpu3050/mlos-kernel.c @@ -0,0 +1,92 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/***************************************************************************** * + * $Id: mlos-kernel.c 3863 2010-10-08 22:05:31Z nroyer $ + ******************************************************************************/ +/** + * @defgroup + * @brief + * + * @{ + * @file mlos-kernel.c + * @brief + * + * + */ + +#include "mlos.h" +#include <linux/delay.h> +#include <linux/slab.h> + +void *MLOSMalloc(unsigned int numBytes) +{ + return kmalloc(numBytes, GFP_KERNEL); +} + +tMLError MLOSFree(void *ptr) +{ + kfree(ptr); + return ML_SUCCESS; +} + +tMLError MLOSCreateMutex(HANDLE *mutex) +{ + /* @todo implement if needed */ + return ML_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +tMLError MLOSLockMutex(HANDLE mutex) +{ + /* @todo implement if needed */ + return ML_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +tMLError MLOSUnlockMutex(HANDLE mutex) +{ + /* @todo implement if needed */ + return ML_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +tMLError MLOSDestroyMutex(HANDLE handle) +{ + /* @todo implement if needed */ + return ML_ERROR_FEATURE_NOT_IMPLEMENTED; +} + +FILE *MLOSFOpen(char *filename) +{ + /* @todo implement if needed */ + return NULL; +} + +void MLOSFClose(FILE *fp) +{ + /* @todo implement if needed */ +} + +void MLOSSleep(int mSecs) +{ + msleep(mSecs); +} + +unsigned long MLOSGetTickCount(void) +{ + /* @todo implement if needed */ + return ML_ERROR_FEATURE_NOT_IMPLEMENTED; +} diff --git a/drivers/misc/mpu3050/mlos.h b/drivers/misc/mpu3050/mlos.h new file mode 100755 index 000000000000..4a54ce236e95 --- /dev/null +++ b/drivers/misc/mpu3050/mlos.h @@ -0,0 +1,78 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/******************************************************************************* + * + * $Id: mlos.h 3863 2010-10-08 22:05:31Z nroyer $ + * + *******************************************************************************/ + +#ifndef _MLOS_H +#define _MLOS_H + +#ifndef __KERNEL__ +#include <stdio.h> +#endif + +#include "mltypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* ------------ */ + /* - Defines. - */ + /* ------------ */ + + /* - MLOSCreateFile defines. - */ + +#define MLOS_GENERIC_READ ((unsigned int)0x80000000) +#define MLOS_GENERIC_WRITE ((unsigned int)0x40000000) +#define MLOS_FILE_SHARE_READ ((unsigned int)0x00000001) +#define MLOS_FILE_SHARE_WRITE ((unsigned int)0x00000002) +#define MLOS_OPEN_EXISTING ((unsigned int)0x00000003) + + /* ---------- */ + /* - Enums. - */ + /* ---------- */ + + /* --------------- */ + /* - Structures. - */ + /* --------------- */ + + /* --------------------- */ + /* - Function p-types. - */ + /* --------------------- */ + + void *MLOSMalloc(unsigned int numBytes); + tMLError MLOSFree(void *ptr); + tMLError MLOSCreateMutex(HANDLE *mutex); + tMLError MLOSLockMutex(HANDLE mutex); + tMLError MLOSUnlockMutex(HANDLE mutex); + FILE *MLOSFOpen(char *filename); + void MLOSFClose(FILE *fp); + + tMLError MLOSDestroyMutex(HANDLE handle); + + void MLOSSleep(int mSecs); + unsigned long MLOSGetTickCount(void); + +#ifdef __cplusplus +} +#endif +#endif /* _MLOS_H */ diff --git a/drivers/misc/mpu3050/mlsl-kernel.c b/drivers/misc/mpu3050/mlsl-kernel.c new file mode 100755 index 000000000000..4b9494f1d919 --- /dev/null +++ b/drivers/misc/mpu3050/mlsl-kernel.c @@ -0,0 +1,171 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/******************************************************************************* + * + * $Id: mlsl-kernel.c 3863 2010-10-08 22:05:31Z nroyer $ + * + ******************************************************************************/ + +#include "mlsl.h" +#include "mpu-i2c.h" + +/* ------------ */ +/* - Defines. - */ +/* ------------ */ + +/* ---------------------- */ +/* - Types definitions. - */ +/* ---------------------- */ + +/* --------------------- */ +/* - Function p-types. - */ +/* --------------------- */ + +/** + * @brief used to open the I2C or SPI serial port. + * This port is used to send and receive data to the MPU device. + * @param portNum + * The COM port number associated with the device in use. + * @return ML_SUCCESS if successful, a non-zero error code otherwise. + */ +tMLError MLSLSerialOpen(char const *port, mlsl_handle_t * sl_handle) +{ + return ML_SUCCESS; +} + +/** + * @brief used to reset any buffering the driver may be doing + * @return ML_SUCCESS if successful, a non-zero error code otherwise. + */ +tMLError MLSLSerialReset(mlsl_handle_t sl_handle) +{ + return ML_SUCCESS; +} + +/** + * @brief used to close the I2C or SPI serial port. + * This port is used to send and receive data to the MPU device. + * @return ML_SUCCESS if successful, a non-zero error code otherwise. + */ +tMLError MLSLSerialClose(mlsl_handle_t sl_handle) +{ + return ML_SUCCESS; +} + +/** + * @brief used to read a single byte of data. + * This should be sent by I2C or SPI. + * + * @param slaveAddr I2C slave address of device. + * @param registerAddr Register address to read. + * @param data Single byte of data to read. + * + * @return ML_SUCCESS if the command is successful, an error code otherwise. + */ +tMLError MLSLSerialWriteSingle(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned char registerAddr, unsigned char data) +{ + return sensor_i2c_write_register((struct i2c_adapter *)sl_handle, + slaveAddr, registerAddr, data); + +} + +/** + * @brief used to read multiple bytes of data. + * This should be sent by I2C or SPI. + * + * @param slaveAddr I2C slave address of device. + * @param registerAddr Register address to read. + * @param length Length of burst data. + * @param data Pointer to block of data. + * + * @return Zero if the command is successful; an error code otherwise + */ +tMLError MLSLSerialRead(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned char registerAddr, + unsigned short length, unsigned char *data) +{ + return sensor_i2c_read((struct i2c_adapter *)sl_handle, + slaveAddr, registerAddr, length, data); +} + +/** + * @brief used to write multiple bytes of data. + * This should be sent by I2C or SPI. + * + * @param slaveAddr I2C slave address of device. + * @param length Length of burst data. + * @param data Pointer to block of data. First byte is the + * register address to write. + * + * @return ML_SUCCESS if successful, a non-zero error code otherwise. + */ +tMLError MLSLSerialWrite(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned short length, unsigned char const *data) +{ + return sensor_i2c_write((struct i2c_adapter *)sl_handle, + slaveAddr, length, data); +} + +/** + * @brief used to read multiple bytes of data. + * This should be sent by I2C or SPI. + * + * @param slaveAddr I2C slave address of device. + * @param registerAddr Register address to read. + * @param length Length of burst data. + * @param data Pointer to block of data. + * + * @return Zero if the command is successful; an error code otherwise + */ +tMLError MLSLSerialReadMem(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned short memAddr, + unsigned short length, unsigned char *data) +{ + return mpu_memory_read((struct i2c_adapter *)sl_handle, + slaveAddr, memAddr, length, data); +} + +/** + * @brief used to write multiple bytes of data. + * This should be sent by I2C or SPI. + * + * @param slaveAddr I2C slave address of device. + * @param length Length of burst data. + * @param data Pointer to block of data. First byte is the + * register address to write. + * + * @return ML_SUCCESS if successful, a non-zero error code otherwise. + */ +tMLError MLSLSerialWriteMem(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned short mem_addr, + unsigned short length, unsigned char const *data) +{ + return mpu_memory_write((struct i2c_adapter *)sl_handle, + slaveAddr, mem_addr, length, data); +} + +/***********************/ + /** @} *//* defgroup */ +/*********************/ diff --git a/drivers/misc/mpu3050/mlsl.h b/drivers/misc/mpu3050/mlsl.h new file mode 100755 index 000000000000..d41683eddb77 --- /dev/null +++ b/drivers/misc/mpu3050/mlsl.h @@ -0,0 +1,81 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/******************************************************************************* + * + * $Id: mlsl.h 3863 2010-10-08 22:05:31Z nroyer $ + * + ******************************************************************************/ + +#ifndef __MSSL_H__ +#define __MSSL_H__ + +#include "mltypes.h" +#include "mpu3050.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------ */ +/* - Defines. - */ +/* ------------ */ + +/* ---------------------- */ +/* - Types definitions. - */ +/* ---------------------- */ + + typedef void *tMLSLHandle; /* For MPL coding standards */ + +/* --------------------- */ +/* - Function p-types. - */ +/* --------------------- */ + + tMLError MLSLSerialOpen(char const *port, mlsl_handle_t * sl_handle); + tMLError MLSLSerialReset(mlsl_handle_t sl_handle); + tMLError MLSLSerialClose(mlsl_handle_t sl_handle); + tMLError MLSLSerialWriteSingle(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned char registerAddr, + unsigned char data); + tMLError MLSLSerialRead(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned char registerAddr, + unsigned short length, unsigned char *data); + tMLError MLSLSerialWrite(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned short length, + unsigned char const *data); + tMLError MLSLSerialReadMem(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned short memAddr, + unsigned short length, unsigned char *data); + tMLError MLSLSerialWriteMem(mlsl_handle_t sl_handle, + unsigned char slaveAddr, + unsigned short memAddr, + unsigned short length, + unsigned char const *data); + tMLError MLSLWriteCal(unsigned char *cal, unsigned int len); + tMLError MLSLGetCalLength(unsigned int *len); + tMLError MLSLReadCal(unsigned char *cal, unsigned int len); + +#ifdef __cplusplus +} +#endif + /***********************//** @} *//* defgroup *//*********************/ +#endif // MLSL_H diff --git a/drivers/misc/mpu3050/mltypes.h b/drivers/misc/mpu3050/mltypes.h new file mode 100755 index 000000000000..4ffc890ca7b8 --- /dev/null +++ b/drivers/misc/mpu3050/mltypes.h @@ -0,0 +1,215 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/******************************************************************************* + * + * $Id: mltypes.h 3866 2010-10-09 00:51:32Z nroyer $ + * + *******************************************************************************/ + +/** + * @defgroup MLERROR + * @brief Definition of the error codes used within the MPL and returned + * to the user. + * Every function tries to return a meaningful error code basing + * on the occuring error condition. The error code is numeric. + * + * The available error codes and their associated values are: + * - (0) ML_SUCCESS + * - (1) ML_ERROR + * - (2) ML_ERROR_INVALID_PARAMETER + * - (3) ML_ERROR_FEATURE_NOT_ENABLED + * - (4) ML_ERROR_FEATURE_NOT_IMPLEMENTED + * - (6) ML_ERROR_DMP_NOT_STARTED + * - (7) ML_ERROR_DMP_STARTED + * - (8) ML_ERROR_NOT_OPENED + * - (9) ML_ERROR_OPENED + * - (10) ML_ERROR_INVALID_MODULE + * - (11) ML_ERROR_MEMORY_EXAUSTED + * - (12) ML_ERROR_DIVIDE_BY_ZERO + * - (13) ML_ERROR_ASSERTION_FAILURE + * - (14) ML_ERROR_FILE_OPEN + * - (15) ML_ERROR_FILE_READ + * - (16) ML_ERROR_FILE_WRITE + * - (20) ML_ERROR_SERIAL_CLOSED + * - (21) ML_ERROR_SERIAL_OPEN_ERROR + * - (22) ML_ERROR_SERIAL_READ + * - (23) ML_ERROR_SERIAL_WRITE + * - (24) ML_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED + * - (25) ML_ERROR_SM_TRANSITION + * - (26) ML_ERROR_SM_IMPROPER_STATE + * - (30) ML_ERROR_FIFO_OVERFLOW + * - (31) ML_ERROR_FIFO_FOOTER + * - (32) ML_ERROR_FIFO_READ_COUNT + * - (33) ML_ERROR_FIFO_READ_DATA + * - (40) ML_ERROR_MEMORY_SET + * - (50) ML_ERROR_LOG_MEMORY_ERROR + * - (51) ML_ERROR_LOG_OUTPUT_ERROR + * - (60) ML_ERROR_OS_BAD_PTR + * - (61) ML_ERROR_OS_BAD_HANDLE + * - (62) ML_ERROR_OS_CREATE_FAILED + * - (63) ML_ERROR_OS_LOCK_FAILED + * - (70) ML_ERROR_COMPASS_DATA_OVERFLOW + * - (71) ML_ERROR_COMPASS_DATA_UNDERFLOW + * - (72) ML_ERROR_COMPASS_DATA_NOT_READY + * +**/ + +#ifndef MLTYPES_H +#define MLTYPES_H + +#ifdef __KERNEL__ +#include <linux/types.h> +#else +#include "stdint_invensense.h" +#endif +#include "log.h" + +/*--------------------------- + ML Types +---------------------------*/ + +typedef long long MLS64; +typedef int MLS32; +typedef unsigned int MLU32; +typedef short MLS16; +typedef unsigned short MLU16; +typedef char MLS8; +typedef unsigned char MLU8; +typedef unsigned char MLBOOL; +typedef double MLDBL; +typedef float MLFLT; + +typedef unsigned char tReg; + +/** + * @struct tMLError The MPL Error Code return type. + * + * @code + * typedef unsigned char tMLError; + * @endcode + */ +typedef unsigned char tMLError; + +#if defined(LINUX) || defined (__KERNEL__) +typedef unsigned int HANDLE; +#endif + +#ifdef __KERNEL__ +typedef HANDLE FILE; +#endif + +#ifndef __cplusplus +#ifndef __KERNEL__ +typedef int_fast8_t bool; +#endif +#endif + +/*--------------------------- + ML Defines +---------------------------*/ + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +/* Dimension of an array */ +#ifndef DIM +#define DIM(array) (sizeof(array)/sizeof((array)[0])) +#endif + +/* - ML Errors. - */ +#define ERROR_NAME(x) (#x) +#define ERROR_CHECK(x) \ +{ \ + if (ML_SUCCESS != x) { \ + MPL_LOGE("%s|%s|%d returning %d\n", \ + __FILE__, __func__, __LINE__, x); \ + return x; \ + } \ +} + +#define ERROR_CHECK_FIRST(first, x) {if (ML_SUCCESS == first) first = x; } + +#define ML_SUCCESS (0) +/* Generic Error code. Propritary Error Codes only */ +#define ML_ERROR (1) + +/* Compatibility and other generic error codes */ +#define ML_ERROR_INVALID_PARAMETER (2) +#define ML_ERROR_FEATURE_NOT_ENABLED (3) +#define ML_ERROR_FEATURE_NOT_IMPLEMENTED (4) +#define ML_ERROR_DMP_NOT_STARTED (6) +#define ML_ERROR_DMP_STARTED (7) +#define ML_ERROR_NOT_OPENED (8) +#define ML_ERROR_OPENED (9) +#define ML_ERROR_INVALID_MODULE (10) +#define ML_ERROR_MEMORY_EXAUSTED (11) +#define ML_ERROR_DIVIDE_BY_ZERO (12) +#define ML_ERROR_ASSERTION_FAILURE (13) +#define ML_ERROR_FILE_OPEN (14) +#define ML_ERROR_FILE_READ (15) +#define ML_ERROR_FILE_WRITE (16) + +/* Serial Communication */ +#define ML_ERROR_SERIAL_CLOSED (20) +#define ML_ERROR_SERIAL_OPEN_ERROR (21) +#define ML_ERROR_SERIAL_READ (22) +#define ML_ERROR_SERIAL_WRITE (23) +#define ML_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED (24) + +/* SM = State Machine */ +#define ML_ERROR_SM_TRANSITION (25) +#define ML_ERROR_SM_IMPROPER_STATE (26) + +/* Fifo */ +#define ML_ERROR_FIFO_OVERFLOW (30) +#define ML_ERROR_FIFO_FOOTER (31) +#define ML_ERROR_FIFO_READ_COUNT (32) +#define ML_ERROR_FIFO_READ_DATA (33) + +/* Memory & Registers, Set & Get */ +#define ML_ERROR_MEMORY_SET (40) + +#define ML_ERROR_LOG_MEMORY_ERROR (50) +#define ML_ERROR_LOG_OUTPUT_ERROR (51) + +/*OS interface errors */ +#define ML_ERROR_OS_BAD_PTR (60) +#define ML_ERROR_OS_BAD_HANDLE (61) +#define ML_ERROR_OS_CREATE_FAILED (62) +#define ML_ERROR_OS_LOCK_FAILED (63) + +/* Compass errors */ +#define ML_ERROR_COMPASS_DATA_OVERFLOW (70) +#define ML_ERROR_COMPASS_DATA_UNDERFLOW (71) +#define ML_ERROR_COMPASS_DATA_NOT_READY (72) + +/*--------------------------- + p-Types +---------------------------*/ + +#endif /* MLTYPES_H */ diff --git a/drivers/misc/mpu3050/mpu-dev.c b/drivers/misc/mpu3050/mpu-dev.c new file mode 100755 index 000000000000..dde346bc0e9b --- /dev/null +++ b/drivers/misc/mpu3050/mpu-dev.c @@ -0,0 +1,835 @@ +/* + mpu-dev.c - mpu3050 char device interface + + Copyright (C) 1995-97 Simon G. Vogl + Copyright (C) 1998-99 Frodo Looijaard <frodol@dds.nl> + Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com> + Copyright (C) 2010 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/>. +*/ +/* Code inside mpudev_ioctl_rdrw is copied from i2c-dev.c + */ +#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> + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <asm/uaccess.h> +#include <asm/io.h> + +#include "mpuirq.h" +#include "mlsl.h" +#include "mpu-i2c.h" +#include "mldl_cfg.h" +#include "mpu3050.h" + +#define MPU3050_EARLY_SUSPEND_IN_DRIVER 0 +#define MPU_NAME "mpu" +#define MPU_SLAVE_ADDR (0x68) + +#define MPU_GET_INTERRUPT_CNT (2) +#define MPU_GET_IRQ_TIME (3) +#define MPU_GET_LED_VALUE (4) +#define MPU_SET_TIMEOUT (5) + +/* Platform data for the MPU */ +struct mpu_private_data { + struct mldl_cfg mldl_cfg; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif +}; + +static int pid; + +static struct i2c_client *this_client; + +static int mpu_open(struct inode *inode, struct file *file) +{ + printk("mpu_open\n"); + printk("current->pid %d\n", current->pid); + pid = current->pid; + file->private_data = this_client; + /* we could do some checking on the flags supplied by "open" + // i.e. O_NONBLOCK + // -> set some flag to disable interruptible_sleep_on in mpu_read */ + 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 i2c_client *client = (struct i2c_client *)file->private_data; + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int result = 0; + + pid = 0; + + if (!mldl_cfg->is_suspended) { + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + accel_adapter = + i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = + i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + result = + mpu3050_suspend(mldl_cfg, client->adapter, accel_adapter, + compass_adapter, TRUE, TRUE); + } + + printk("mpu_release\n"); + return result; +} + +static noinline int mpudev_ioctl_rdrw(struct i2c_client *client, + unsigned long arg) +{ + struct i2c_rdwr_ioctl_data rdwr_arg; + struct i2c_msg *rdwr_pa; + u8 __user **data_ptrs; + int i, res; + + if (copy_from_user(&rdwr_arg, + (struct i2c_rdwr_ioctl_data __user *)arg, + sizeof(rdwr_arg))) + return -EFAULT; + + /* Put an arbitrary limit on the number of messages that can + * be sent at once */ + if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS) + return -EINVAL; + + rdwr_pa = (struct i2c_msg *) + kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL); + if (!rdwr_pa) + return -ENOMEM; + + if (copy_from_user(rdwr_pa, rdwr_arg.msgs, + rdwr_arg.nmsgs * sizeof(struct i2c_msg))) { + kfree(rdwr_pa); + return -EFAULT; + } + + data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL); + if (data_ptrs == NULL) { + kfree(rdwr_pa); + return -ENOMEM; + } + + res = 0; + for (i = 0; i < rdwr_arg.nmsgs; i++) { + /* Limit the size of the message to a sane amount; + * and don't let length change either. */ + if ((rdwr_pa[i].len > 8192) || + (rdwr_pa[i].flags & I2C_M_RECV_LEN)) { + res = -EINVAL; + break; + } + data_ptrs[i] = (u8 __user *) rdwr_pa[i].buf; + rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL); + if (rdwr_pa[i].buf == NULL) { + res = -ENOMEM; + break; + } + if (copy_from_user(rdwr_pa[i].buf, data_ptrs[i], + rdwr_pa[i].len)) { + ++i; /* Needs to be kfreed too */ + res = -EFAULT; + break; + } + } + if (res < 0) { + int j; + for (j = 0; j < i; ++j) + kfree(rdwr_pa[j].buf); + kfree(data_ptrs); + kfree(rdwr_pa); + return res; + } + + res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs); + while (i-- > 0) { + if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) { + if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf, + rdwr_pa[i].len)) + res = -EFAULT; + } + kfree(rdwr_pa[i].buf); + } + kfree(data_ptrs); + kfree(rdwr_pa); + return res; +} + +/* 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) +{ + char *tmp; + int ret; + + struct i2c_client *client = (struct i2c_client *)file->private_data; + + if (count > 8192) + count = 8192; + + tmp = kmalloc(count, GFP_KERNEL); + if (tmp == NULL) + return -ENOMEM; + + pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n", + iminor(file->f_path.dentry->d_inode), count); + +/* @todo fix this to do a i2c trasnfer from the FIFO */ + ret = i2c_master_recv(client, tmp, count); + if (ret >= 0) + ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret; + kfree(tmp); + return ret; +} + +static int mpu_ioctl_get_mpu_pdata(struct i2c_client *client, unsigned long arg) +{ + int result; + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int accel_adapt_num = mldl_cfg->pdata->accel.adapt_num; + int compass_adapt_num = mldl_cfg->pdata->compass.adapt_num; + int accel_bus = mldl_cfg->pdata->accel.bus; + int compass_bus = mldl_cfg->pdata->compass.bus; + + result = copy_from_user(mldl_cfg->pdata, + (unsigned char *)arg, + sizeof(struct mpu3050_platform_data)); + /* Don't allow userspace to change the adapter number or bus */ + mldl_cfg->pdata->accel.adapt_num = accel_adapt_num; + mldl_cfg->pdata->compass.adapt_num = compass_adapt_num; + mldl_cfg->pdata->accel.bus = accel_bus; + mldl_cfg->pdata->compass.bus = compass_bus; + + return result; +} + +static int +mpu_ioctl_set_mpu_config(struct i2c_client *client, unsigned long arg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + + printk("%s\n", __func__); + /* + * User space is not allowed to modify accel compass or pdata structs, + * as well as silicon_revision product_id or trim + */ + if (copy_from_user(mldl_cfg, + (struct mldl_cfg *)arg, + offsetof(struct mldl_cfg, silicon_revision))) + return -EFAULT; + + return 0; +} + +static int +mpu_ioctl_get_mpu_config(struct i2c_client *client, unsigned long arg) +{ + /* Have to be careful as there are 3 pointers in the mldl_cfg + * structure */ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mldl_cfg *local_mldl_cfg; + int retval = 0; + + local_mldl_cfg = kmalloc(sizeof(struct mldl_cfg), GFP_KERNEL); + if (NULL == local_mldl_cfg) + return -ENOMEM; + + retval = + copy_from_user(local_mldl_cfg, (void *)arg, + sizeof(struct mldl_cfg)); + if (retval) + goto out; + + /* Fill in the accel, compass and pdata pointers */ + if (mldl_cfg->accel) { + retval = copy_to_user(local_mldl_cfg->accel, + mldl_cfg->accel, + sizeof(*mldl_cfg->accel)); + if (retval) + goto out; + } + + if (mldl_cfg->compass) { + retval = copy_to_user(local_mldl_cfg->compass, + mldl_cfg->compass, + sizeof(*mldl_cfg->compass)); + if (retval) + goto out; + } + + if (mldl_cfg->pdata) { + retval = copy_to_user(local_mldl_cfg->pdata, + mldl_cfg->pdata, + sizeof(*mldl_cfg->pdata)); + if (retval) + goto out; + } + + /* Do not modify the accel, compass and pdata pointers */ + retval = copy_to_user((struct mldl_cfg *)arg, + mldl_cfg, offsetof(struct mldl_cfg, accel)); + + out: + kfree(local_mldl_cfg); + return retval; +} + +/* ioctl - I/O control */ +static long mpu_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct i2c_client *client = (struct i2c_client *)file->private_data; + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int retval = 0; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + + switch (cmd) { + case I2C_RDWR: + mpudev_ioctl_rdrw(client, arg); + break; + case I2C_SLAVE: + if ((arg & 0x7E) != (client->addr & 0x7E)) { + printk("%s: Invalid I2C_SLAVE arg %lu \n", + __func__, arg); + } + break; + case MPU_SET_MPU_CONFIG: + retval = mpu_ioctl_set_mpu_config(client, arg); + break; + case MPU_SET_INT_CONFIG: + mldl_cfg->int_config = (unsigned char)arg; + break; + case MPU_SET_EXT_SYNC: + mldl_cfg->ext_sync = (enum mpu_ext_sync)arg; + break; + case MPU_SET_FULL_SCALE: + mldl_cfg->full_scale = (enum mpu_fullscale)arg; + break; + case MPU_SET_LPF: + mldl_cfg->lpf = (enum mpu_filter)arg; + break; + case MPU_SET_CLK_SRC: + mldl_cfg->clk_src = (enum mpu_clock_sel)arg; + break; + case MPU_SET_DIVIDER: + mldl_cfg->divider = (unsigned char)arg; + break; + case MPU_SET_LEVEL_SHIFTER: + mldl_cfg->pdata->level_shifter = (unsigned char)arg; + break; + case MPU_SET_DMP_ENABLE: + mldl_cfg->dmp_enable = (unsigned char)arg; + break; + case MPU_SET_FIFO_ENABLE: + mldl_cfg->fifo_enable = (unsigned char)arg; + break; + case MPU_SET_DMP_CFG1: + mldl_cfg->dmp_cfg1 = (unsigned char)arg; + break; + case MPU_SET_DMP_CFG2: + mldl_cfg->dmp_cfg2 = (unsigned char)arg; + break; + case MPU_SET_OFFSET_TC: + retval = copy_from_user(mldl_cfg->offset_tc, + (unsigned char *)arg, + sizeof(mldl_cfg->offset_tc)); + break; + case MPU_SET_RAM: + retval = copy_from_user(mldl_cfg->ram, + (unsigned char *)arg, + sizeof(mldl_cfg->ram)); + break; + case MPU_SET_PLATFORM_DATA: + retval = mpu_ioctl_get_mpu_pdata(client, arg); + break; + case MPU_GET_MPU_CONFIG: + retval = mpu_ioctl_get_mpu_config(client, arg); + break; + case MPU_GET_INT_CONFIG: + mldl_cfg->int_config = (unsigned char)arg; + break; + case MPU_GET_EXT_SYNC: + mldl_cfg->ext_sync = (enum mpu_ext_sync)arg; + break; + case MPU_GET_FULL_SCALE: + mldl_cfg->full_scale = (enum mpu_fullscale)arg; + break; + case MPU_GET_LPF: + mldl_cfg->lpf = (enum mpu_filter)arg; + break; + case MPU_GET_CLK_SRC: + mldl_cfg->clk_src = (enum mpu_clock_sel)arg; + break; + case MPU_GET_DIVIDER: + mldl_cfg->divider = (unsigned char)arg; + break; + case MPU_GET_LEVEL_SHIFTER: + mldl_cfg->pdata->level_shifter = (unsigned char)arg; + break; + case MPU_GET_DMP_ENABLE: + mldl_cfg->dmp_enable = (unsigned char)arg; + break; + case MPU_GET_FIFO_ENABLE: + mldl_cfg->fifo_enable = (unsigned char)arg; + break; + case MPU_GET_DMP_CFG1: + mldl_cfg->dmp_cfg1 = (unsigned char)arg; + break; + case MPU_GET_DMP_CFG2: + mldl_cfg->dmp_cfg2 = (unsigned char)arg; + break; + case MPU_GET_OFFSET_TC: + retval = copy_to_user((unsigned char *)arg, + mldl_cfg->offset_tc, + sizeof(mldl_cfg->offset_tc)); + break; + case MPU_GET_RAM: + retval = copy_to_user((unsigned char *)arg, + mldl_cfg->ram, sizeof(mldl_cfg->ram)); + break; + case MPU_READ_MEMORY: + case MPU_WRITE_MEMORY: + case MPU_SUSPEND: + { + struct mpu_suspend_resume suspend; + retval = + copy_from_user(&suspend, + (struct mpu_suspend_resume *) + arg, sizeof(suspend)); + if (retval) + break; + if (suspend.gyro) { + retval = + mpu3050_suspend(mldl_cfg, + client->adapter, + accel_adapter, + compass_adapter, + suspend.accel, + suspend.compass); + } else { + /* Cannot suspend the compass or accel while + * the MPU is running */ + retval = ML_ERROR_FEATURE_NOT_IMPLEMENTED; + } + } + break; + case MPU_RESUME: + { + struct mpu_suspend_resume resume; + retval = + copy_from_user(&resume, + (struct mpu_suspend_resume *) + arg, sizeof(resume)); + if (retval) + break; + if (resume.gyro) { + retval = + mpu3050_resume(mldl_cfg, + client->adapter, + accel_adapter, + compass_adapter, + resume.accel, + resume.compass); + } else if (mldl_cfg->is_suspended) { + if (resume.accel) { + retval = + mldl_cfg->accel-> + resume(accel_adapter, + mldl_cfg->accel, + &mldl_cfg->pdata->accel); + if (retval) + break; + } + + if (resume.compass) + retval = + mldl_cfg->compass-> + resume(compass_adapter, + mldl_cfg->compass, + &mldl_cfg->pdata->compass); + } else { + /* Cannot resume the compass or accel while + * the MPU is running */ + retval = ML_ERROR_FEATURE_NOT_IMPLEMENTED; + } + } + break; + case MPU_READ_ACCEL: + { + unsigned char data[6]; + retval = + mpu3050_read_accel(mldl_cfg, client->adapter, data); + if (ML_SUCCESS == retval) + retval = + copy_to_user((unsigned char *)arg, + data, sizeof(data)); + } + break; + case MPU_READ_COMPASS: + { + unsigned char data[6]; + struct i2c_adapter *compass_adapt = + i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + retval = + mpu3050_read_compass(mldl_cfg, compass_adapt, data); + if (ML_SUCCESS == retval) + retval = + copy_to_user((unsigned char *)arg, + data, sizeof(data)); + } + break; + default: + printk("%s: Unknown cmd %d, arg %lu \n", __func__, cmd, arg); + retval = -EINVAL; + } + + return retval; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +void mpu3050_early_suspend(struct early_suspend *h) +{ + struct mpu_private_data *mpu = container_of(h, + struct + mpu_private_data, + early_suspend); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + + printk("%s: %d, %d\n", __func__, h->level, mpu->mldl_cfg.is_suspended); + if (MPU3050_EARLY_SUSPEND_IN_DRIVER) + (void)mpu3050_suspend(mldl_cfg, + accel_adapter, compass_adapter, + this_client->adapter, TRUE, TRUE); +} + +void mpu3050_early_resume(struct early_suspend *h) +{ + struct mpu_private_data *mpu = container_of(h, + struct + mpu_private_data, + early_suspend); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + + if (MPU3050_EARLY_SUSPEND_IN_DRIVER) { + if (pid) { + (void)mpu3050_resume(mldl_cfg, + accel_adapter, compass_adapter, + this_client->adapter, TRUE, TRUE); + printk("%s for pid %d\n", __func__, pid); + } + } + printk("%s: %d\n", __func__, h->level); +} +#endif + +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 *accel_adapter; + struct i2c_adapter *compass_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + + (void)mpu3050_suspend(mldl_cfg, this_client->adapter, + accel_adapter, compass_adapter, TRUE, TRUE); + printk("%s\n", __func__); +} + +int mpu_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 *accel_adapter; + struct i2c_adapter *compass_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + + if (!mpu->mldl_cfg.is_suspended) { + printk("%s: suspending on event %d\n", __func__, mesg.event); + (void)mpu3050_suspend(mldl_cfg, this_client->adapter, + accel_adapter, compass_adapter, + TRUE, TRUE); + } else { + printk("%s: Already suspended %d\n", __func__, mesg.event); + } + return 0; +} + +int mpu_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 *accel_adapter; + struct i2c_adapter *compass_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + + if (pid) { + (void)mpu3050_resume(mldl_cfg, this_client->adapter, + accel_adapter, compass_adapter, + TRUE, TRUE); + printk("%s for pid %d\n", __func__, pid); + } + return 0; +} + +/* define which file operations are supported */ +struct file_operations mpu_fops = { + .owner = THIS_MODULE, + .read = mpu_read, +#if HAVE_COMPAT_IOCTL + .compat_ioctl = mpu_ioctl, +#endif +#if HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = mpu_ioctl, +#endif + .open = mpu_open, + .release = mpu_release, +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) +I2C_CLIENT_INSMOD; +#endif + +static struct miscdevice i2c_mpu_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = MPU_NAME, + .fops = &mpu_fops, +}; + +int mpu3050_probe(struct i2c_client *client, const struct i2c_device_id *devid) +{ + struct mpu3050_platform_data *pdata; + struct mpu_private_data *mpu; + int res = 0; + printk("%s\n", __func__); + + 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; + } + + i2c_set_clientdata(client, mpu); + this_client = client; + + pdata = (struct mpu3050_platform_data *)client->dev.platform_data; + if (!pdata) { + printk("Warning no platform data for mpu3050\n"); + } else { + mpu->mldl_cfg.pdata = pdata; + +#ifdef CONFIG_SENSORS_MPU3050_MODULE + pdata->accel.get_slave_descr = get_accel_slave_descr; + pdata->compass.get_slave_descr = get_compass_slave_descr; +#endif + + if (pdata->accel.get_slave_descr) { + mpu->mldl_cfg.accel = pdata->accel.get_slave_descr(); + printk("MPU3050: +%s\n", mpu->mldl_cfg.accel->name); + } else { + printk("MPU3050: No Accel Present\n"); + } + + if (pdata->compass.get_slave_descr) { + mpu->mldl_cfg.compass = + pdata->compass.get_slave_descr(); + printk("MPU3050: +%s\n", mpu->mldl_cfg.compass->name); + } else { + printk("MPU3050: No Compass Present\n"); + } + } + + mpu->mldl_cfg.addr = client->addr; + res = mpu3050_open(&mpu->mldl_cfg, (tMLSLHandle) client->adapter); + + if (res) { + printk("Unable to open MPU3050 %d\n", res); + res = -ENODEV; + goto out_whoami_failed; + } +#ifdef CONFIG_HAS_EARLYSUSPEND + mpu->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + mpu->early_suspend.suspend = mpu3050_early_suspend; + mpu->early_suspend.resume = mpu3050_early_resume; + register_early_suspend(&mpu->early_suspend); +#endif + + res = misc_register(&i2c_mpu_device); + if (res < 0) { + printk("ERROR: misc_register returned %d\n", res); + goto out_misc_register_failed; + } + + if (this_client->irq > 0) { + printk("Installing irq using %d\n", this_client->irq); + res = mpuirq_init(this_client); + if (res) { + goto out_mpuirq_failed; + } + } else { + printk("WARNING: mpu3050 irq not assigned\n"); + } + + return res; + + out_mpuirq_failed: + misc_deregister(&i2c_mpu_device); + out_misc_register_failed: +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&mpu->early_suspend); +#endif + out_whoami_failed: + kfree(mpu); + out_alloc_data_failed: + out_check_functionality_failed: + printk(KERN_ERR "%s failed %d\n", __func__, res); + return res; + +} + +static int mpu3050_remove(struct i2c_client *client) +{ + struct mpu_private_data *mpu = i2c_get_clientdata(client); + printk("%s\n", __func__); + + if (client->irq) { + mpuirq_exit(); + } + + misc_deregister(&i2c_mpu_device); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&mpu->early_suspend); +#endif + kfree(mpu); + + return 0; +} + +static const struct i2c_device_id mpu3050_id[] = { + {"mpu3050", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mpu3050_id); + +static struct i2c_driver mpu3050_driver = { + .class = I2C_CLASS_HWMON, + .probe = mpu3050_probe, + .remove = mpu3050_remove, + .id_table = mpu3050_id, + .driver = { + .owner = THIS_MODULE, + .name = "mpu3050", + }, +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) + .address_data = &addr_data, +#else + .address_list = normal_i2c, +#endif + + .shutdown = mpu_shutdown, /* optional */ + .suspend = mpu_suspend, /* optional */ + .resume = mpu_resume, /* optional */ + +}; + +static int __init mpu_init(void) +{ + int res = i2c_add_driver(&mpu3050_driver); + printk("%s\n", __func__); + if (res) { + printk("%s failed\n", __func__); + } + return res; +} + +static void __exit mpu_exit(void) +{ + printk("%s\n", __func__); + i2c_del_driver(&mpu3050_driver); +} + +module_init(mpu_init); +module_exit(mpu_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("User space character device interface for MPU3050"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("mpu3050"); diff --git a/drivers/misc/mpu3050/mpu-i2c.c b/drivers/misc/mpu3050/mpu-i2c.c new file mode 100755 index 000000000000..ca21b42a9e95 --- /dev/null +++ b/drivers/misc/mpu3050/mpu-i2c.c @@ -0,0 +1,195 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/***************************************************************************** * + * $Id: mpu-i2c.c 3863 2010-10-08 22:05:31Z nroyer $ + ******************************************************************************/ +/** + * @defgroup + * @brief + * + * @{ + * @file mpu-i2c.c + * @brief + * + * + */ + +#include <linux/i2c.h> + +int sensor_i2c_write(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned int len, unsigned char *data) +{ + struct i2c_msg msgs[1]; + int res; + + if (NULL == data || NULL == i2c_adap) + 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) { + return res; + } else { + return 0; + } +} + +int sensor_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 sensor_i2c_write(i2c_adap, address, 2, data); +} + +int sensor_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 (NULL == data || NULL == i2c_adap) + 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) { + return res; + } else { + return 0; + } +} + +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 ret; + + if (NULL == data || NULL == i2c_adap) + return -EINVAL; + + bank[0] = 0x37; /*MPUREG_BANK_SEL; */ + bank[1] = mem_addr >> 8; + + addr[0] = 0x38; /*MPUREG_MEM_START_ADDR; */ + addr[1] = mem_addr & 0xFF; + + buf = 0x39; /* 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; + + ret = i2c_transfer(i2c_adap, msgs, 4); + if (ret != 4) + return ret; + else + return 0; +} + +int mpu_memory_write(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[513]; + + struct i2c_msg msgs[3]; + int ret; + + if (NULL == data || NULL == i2c_adap) + return -EINVAL; + if (len >= (sizeof(buf) - 1)) + return -ENOMEM; + + bank[0] = 0x37; /*MPUREG_BANK_SEL; */ + bank[1] = mem_addr >> 8; + + addr[0] = 0x38; /*MPUREG_MEM_START_ADDR; */ + addr[1] = mem_addr & 0xFF; + + buf[0] = 0x39; /* 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; + + ret = i2c_transfer(i2c_adap, msgs, 3); + if (ret != 3) + return ret; + else + return 0; +} diff --git a/drivers/misc/mpu3050/mpu-i2c.h b/drivers/misc/mpu3050/mpu-i2c.h new file mode 100755 index 000000000000..ac3f7e93b695 --- /dev/null +++ b/drivers/misc/mpu3050/mpu-i2c.h @@ -0,0 +1,60 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ +/***************************************************************************** * + * $Id: mpu-i2c.h 3863 2010-10-08 22:05:31Z nroyer $ + ******************************************************************************/ +/** + * @defgroup + * @brief + * + * @{ + * @file mpu-i2c.c + * @brief + * + * + */ + +#ifndef __MPU_I2C_H__ +#define __MPU_I2C_H__ + +#include <linux/i2c.h> + +int sensor_i2c_write(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned int len, unsigned char const *data); + +int sensor_i2c_write_register(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned char reg, unsigned char value); + +int sensor_i2c_read(struct i2c_adapter *i2c_adap, + unsigned char address, + unsigned char reg, unsigned int len, unsigned char *data); + +int mpu_memory_read(struct i2c_adapter *i2c_adap, + unsigned char mpu_addr, + unsigned short mem_addr, + unsigned int len, unsigned char *data); + +int mpu_memory_write(struct i2c_adapter *i2c_adap, + unsigned char mpu_addr, + unsigned short mem_addr, + unsigned int len, unsigned char const *data); + +#endif /* __MPU_I2C_H__ */ diff --git a/drivers/misc/mpu3050/mpuirq.c b/drivers/misc/mpu3050/mpuirq.c new file mode 100755 index 000000000000..bf3c10f176fe --- /dev/null +++ b/drivers/misc/mpu3050/mpuirq.c @@ -0,0 +1,315 @@ +/* + $License: + Copyright (C) 2010 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/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/signal.h> +#include <linux/miscdevice.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/workqueue.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 <asm/uaccess.h> +#include <asm/io.h> + +#include "mpu3050.h" +#include "mpuirq.h" +#include "mldl_cfg.h" +#include "mpu-i2c.h" + +#define MPUIRQ_NAME "mpuirq" + +/* function which gets accel data and sends it to MPU */ + +DECLARE_WAIT_QUEUE_HEAD(mpuirq_wait); + +struct mpuirq_dev_data { + struct work_struct work; + struct i2c_client *mpu_client; + struct miscdevice *dev; + int irq; + int pid; + int accel_divider; + int data_ready; + int timeout; +}; + +static struct mpuirq_dev_data mpuirq_dev_data; +static struct mpuirq_data mpuirq_data; +static char *interface = MPUIRQ_NAME; + +static void mpu_accel_data_work_fcn(struct work_struct *work); + +static int mpuirq_open(struct inode *inode, struct file *file) +{ + dev_dbg(mpuirq_dev_data.dev->this_device, + "%s current->pid %d\n", __func__, current->pid); + mpuirq_dev_data.pid = current->pid; + file->private_data = &mpuirq_dev_data; + /* we could do some checking on the flags supplied by "open" + // i.e. O_NONBLOCK + // -> set some flag to disable interruptible_sleep_on in mpuirq_read */ + return 0; +} + +/* close function - called when the "file" /dev/mpuirq is closed in userspace */ +static int mpuirq_release(struct inode *inode, struct file *file) +{ + dev_dbg(mpuirq_dev_data.dev->this_device, "mpuirq_release\n"); + return 0; +} + +/* read function called when from /dev/mpuirq is read */ +static ssize_t mpuirq_read(struct file *file, + char *buf, size_t count, loff_t * ppos) +{ + int len, err; + struct mpuirq_dev_data *p_mpuirq_dev_data = file->private_data; + + if (!mpuirq_dev_data.data_ready) { + wait_event_interruptible_timeout(mpuirq_wait, + mpuirq_dev_data.data_ready, + mpuirq_dev_data.timeout); + } + + if (mpuirq_dev_data.data_ready && NULL != buf + && count >= sizeof(mpuirq_data)) { + err = copy_to_user(buf, &mpuirq_data, sizeof(mpuirq_data)); + } else { + return 0; + } + if (err != 0) { + dev_err(p_mpuirq_dev_data->dev->this_device, + "Copy to user returned %d\n", err); + return -EFAULT; + } + mpuirq_dev_data.data_ready = 0; + len = sizeof(mpuirq_data); + return len; +} + +unsigned int mpuirq_poll(struct file *file, struct poll_table_struct *poll) +{ + int mask = 0; + struct mpuirq_dev_data *p_mpuirq_dev_data = file->private_data; + + poll_wait(file, &mpuirq_wait, poll); + if (mpuirq_dev_data.data_ready) + mask |= POLLIN | POLLRDNORM; + dev_dbg(p_mpuirq_dev_data->dev->this_device, + "%s: returning %d\n", __func__, mask); + return mask; +} + +/* ioctl - I/O control */ +static long mpuirq_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int data; + + switch (cmd) { + case MPUIRQ_SET_TIMEOUT: + mpuirq_dev_data.timeout = arg; + break; + + case MPUIRQ_GET_INTERRUPT_CNT: + data = mpuirq_data.interruptcount - 1; + if (mpuirq_data.interruptcount > 1) { + mpuirq_data.interruptcount = 1; + } + if (copy_to_user((int *)arg, &data, sizeof(int))) + return -EFAULT; + break; + case MPUIRQ_GET_IRQ_TIME: + if (copy_to_user((int *)arg, &mpuirq_data.irqtime, + sizeof(mpuirq_data.irqtime))) + return -EFAULT; + mpuirq_data.irqtime = 0; + break; + case MPUIRQ_SET_FREQUENCY_DIVIDER: + mpuirq_dev_data.accel_divider = arg; + break; + default: + retval = -EINVAL; + } + return retval; +} + +static void mpu_accel_data_work_fcn(struct work_struct *work) +{ + struct mpuirq_dev_data *mpuirq_dev_data = + (struct mpuirq_dev_data *)work; + struct mldl_cfg *mldl_cfg = + (struct mldl_cfg *)i2c_get_clientdata(mpuirq_dev_data->mpu_client); + struct i2c_adapter *accel_adapter; + unsigned char wbuff[16]; + unsigned char rbuff[16]; + int ii; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + mldl_cfg->accel->read((mlsl_handle_t) accel_adapter, + mldl_cfg->accel, &mldl_cfg->pdata->accel, rbuff); + + /* @todo add other data formats here as well */ + if (EXT_SLAVE_BIG_ENDIAN == mldl_cfg->accel->endian) { + for (ii = 0; ii < 3; ii++) { + wbuff[2 * ii + 1] = rbuff[2 * ii + 1]; + wbuff[2 * ii + 2] = rbuff[2 * ii + 0]; + } + } else { + memcpy(wbuff + 1, rbuff, mldl_cfg->accel->len); + } + + wbuff[7] = 0; + wbuff[8] = 1; /*set semaphore */ + + mpu_memory_write(mpuirq_dev_data->mpu_client->adapter, + mldl_cfg->addr, 0x0108, 8, wbuff); +} + +static irqreturn_t mpuirq_handler(int irq, void *dev_id) +{ + static int mycount; + struct timeval irqtime; + mycount++; + + mpuirq_data.interruptcount++; + + /* wake up (unblock) for reading data from userspace + // and ignore first interrupt generated in module init */ + if (mpuirq_data.interruptcount > 1) { + mpuirq_dev_data.data_ready = 1; + + do_gettimeofday(&irqtime); + mpuirq_data.irqtime = (((long long)irqtime.tv_sec) << 32); + mpuirq_data.irqtime += irqtime.tv_usec; + + if ((mpuirq_dev_data.accel_divider >= 0) && + (0 == (mycount % (mpuirq_dev_data.accel_divider + 1)))) { + schedule_work((struct work_struct *)(&mpuirq_dev_data)); + } + + wake_up_interruptible(&mpuirq_wait); + } + + return IRQ_HANDLED; + +} + +/* define which file operations are supported */ +struct file_operations mpuirq_fops = { + .owner = THIS_MODULE, + .read = mpuirq_read, + .poll = mpuirq_poll, + +#if HAVE_COMPAT_IOCTL + .compat_ioctl = mpuirq_ioctl, +#endif +#if HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = mpuirq_ioctl, +#endif + .open = mpuirq_open, + .release = mpuirq_release, +}; + +static struct miscdevice mpuirq_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = MPUIRQ_NAME, + .fops = &mpuirq_fops, +}; + +int mpuirq_init(struct i2c_client *mpu_client) +{ + + int res; + struct mldl_cfg *mldl_cfg = + (struct mldl_cfg *)i2c_get_clientdata(mpu_client); + + /* work_struct initialization */ + INIT_WORK((struct work_struct *)&mpuirq_dev_data, + mpu_accel_data_work_fcn); + mpuirq_dev_data.mpu_client = mpu_client; + + dev_info(&mpu_client->adapter->dev, + "Module Param interface = %s\n", interface); + + mpuirq_dev_data.irq = mpu_client->irq; + mpuirq_dev_data.pid = 0; + mpuirq_dev_data.accel_divider = -1; + mpuirq_dev_data.data_ready = 0; + mpuirq_dev_data.timeout = 0; + 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; + + res = + request_irq(mpuirq_dev_data.irq, mpuirq_handler, flags, + interface, &mpuirq_dev_data.irq); + if (res) { + dev_err(&mpu_client->adapter->dev, + "myirqtest: cannot register IRQ %d\n", + mpuirq_dev_data.irq); + } else { + res = misc_register(&mpuirq_device); + if (res < 0) { + dev_err(&mpu_client->adapter->dev, + "misc_register returned %d\n", res); + free_irq(mpuirq_dev_data.irq, + &mpuirq_dev_data.irq); + } + } + + } else { + res = 0; + } + + return res; +} + +void mpuirq_exit(void) +{ + /* Free the IRQ first before flushing the work */ + if (mpuirq_dev_data.irq > 0) { + free_irq(mpuirq_dev_data.irq, &mpuirq_dev_data.irq); + } + flush_scheduled_work(); + + dev_info(mpuirq_device.this_device, "Unregistering %s\n", MPUIRQ_NAME); + misc_deregister(&mpuirq_device); + + return; +} + +module_param(interface, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(interface, "The Interface name"); diff --git a/drivers/misc/mpu3050/mpuirq.h b/drivers/misc/mpu3050/mpuirq.h new file mode 100755 index 000000000000..69b7eaad5717 --- /dev/null +++ b/drivers/misc/mpu3050/mpuirq.h @@ -0,0 +1,50 @@ +/* + $License: + Copyright (C) 2010 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/>. + $ + */ + +#ifndef __MPUIRQ__ +#define __MPUIRQ__ + +#ifdef __KERNEL__ +#include <linux/i2c-dev.h> +#endif + +#define MPUIRQ_ENABLE_DEBUG (1) +#define MPUIRQ_GET_INTERRUPT_CNT (2) +#define MPUIRQ_GET_IRQ_TIME (3) +#define MPUIRQ_GET_LED_VALUE (4) +#define MPUIRQ_SET_TIMEOUT (5) +#define MPUIRQ_SET_ACCEL_INFO (6) +#define MPUIRQ_SET_FREQUENCY_DIVIDER (7) + +struct mpuirq_data { + int interruptcount; + unsigned long long irqtime; + int data_type; + int data_size; + void *data; +}; + +#ifdef __KERNEL__ + +void mpuirq_exit(void); +int mpuirq_init(struct i2c_client *mpu_client); + +#endif + +#endif |